Exit the IDE, make a copy of the previous chapter folder and rename it to ch05. Go to the ch05 folder and double-click on the project file to bring the IDE.
Note that the routines presented in this chapter do not make the slightest attempt of being efficient. Faster ways of doing things that we'll be doing here exist, and most of them are very easy to implement. The way things are presented here should make them relatively easy to comprehend.
Add the following after the int *gFrameBuffer;
bit:
int *gTempBuffer;
Next, copy and paste the following after the update()
function:
unsigned int blend_avg(int source, int target)
{
unsigned int sourcer = ((unsigned int)source >> 0) & 0xff;
unsigned int sourceg = ((unsigned int)source >> 8) & 0xff;
unsigned int sourceb = ((unsigned int)source >> 16) & 0xff;
unsigned int targetr = ((unsigned int)target >> 0) & 0xff;
unsigned int targetg = ((unsigned int)target >> 8) & 0xff;
unsigned int targetb = ((unsigned int)target >> 16) & 0xff;
targetr = (sourcer + targetr) / 2;
targetg = (sourceg + targetg) / 2;
targetb = (sourceb + targetb) / 2;
return (targetr << 0) |
(targetg << 8) |
(targetb << 16) |
0xff000000;
}
unsigned int blend_mul(int source, int target)
{
unsigned int sourcer = ((unsigned int)source >> 0) & 0xff;
unsigned int sourceg = ((unsigned int)source >> 8) & 0xff;
unsigned int sourceb = ((unsigned int)source >> 16) & 0xff;
unsigned int targetr = ((unsigned int)target >> 0) & 0xff;
unsigned int targetg = ((unsigned int)target >> 8) & 0xff;
unsigned int targetb = ((unsigned int)target >> 16) & 0xff;
targetr = (sourcer * targetr) >> 8;
targetg = (sourceg * targetg) >> 8;
targetb = (sourceb * targetb) >> 8;
return (targetr << 0) |
(targetg << 8) |
(targetb << 16) |
0xff000000;
}
unsigned int blend_add(int source, int target)
{
unsigned int sourcer = ((unsigned int)source >> 0) & 0xff;
unsigned int sourceg = ((unsigned int)source >> 8) & 0xff;
unsigned int sourceb = ((unsigned int)source >> 16) & 0xff;
unsigned int targetr = ((unsigned int)target >> 0) & 0xff;
unsigned int targetg = ((unsigned int)target >> 8) & 0xff;
unsigned int targetb = ((unsigned int)target >> 16) & 0xff;
targetr += sourcer;
targetg += sourceg;
targetb += sourceb;
if (targetr > 0xff) targetr = 0xff;
if (targetg > 0xff) targetg = 0xff;
if (targetb > 0xff) targetb = 0xff;
return (targetr << 0) |
(targetg << 8) |
(targetb << 16) |
0xff000000;
}
void scaleblit()
{
int yofs = 0;
for (int i = 0; i < WINDOW_HEIGHT; i++)
{
for (int j = 0; j < WINDOW_WIDTH; j++)
{
int c =
(int)((i * 0.95) + (WINDOW_HEIGHT * 0.025)) * WINDOW_WIDTH +
(int)((j * 0.95) + (WINDOW_WIDTH * 0.025));
gFrameBuffer[yofs + j] =
blend_avg(gFrameBuffer[yofs + j], gTempBuffer[c]);
}
yofs += WINDOW_WIDTH;
}
}
What we have here are three functions that take in two pixel values, do something with them, and return the result. Way more efficient ways of doing all of these three common blending operations exist.
The "avg" blend calculates the average of two pixel values. The operation is performed per color component. One cheaper way of doing this is to first use the bitwise AND operator to clear out the least significant bits of all of the color components, shift both colors right one bit, and then add them together. The average blend could be used, for instance, for transparent materials like stained glass.
The "mul" blend calculates the multiplication result of two pixel values. The operation is, again, performed per color component. The multiplication blend is typically used for shadows. If you multiply something by 1, you get the same value. Multiplying with a lower value gets you lower (and thus, darker) result. Note that full white (0xff, 255) isn't exactly '1' in this calculation, and thus you will always get slightly darker pixels when using this blend ((255*255)/256 = 254)
. (You may wish to dig up Jim Blinn's "Dirty Pixels" if you're interested in solving this problem. It's way outside the scope of this tutorial, however).
The "add" blend calculates the saturated sum result of two pixel values. The Additive blend again performs the operation for each color component separately. The color is guarded against overflow; i.e. adding pixels together will eventually give you full white color. The additive blend is typically used for lighting effects such as glares, sparkles, explosions, lightsabers and laser weapons.
The only super common blend operation not implemented here is alpha blend, which fades between two colors based on an alpha value.
Let's look at the scaleblit()
a bit later on.
Next, replace the init()
function with the following:
void init()
{
gTempBuffer = new int[WINDOW_WIDTH * WINDOW_HEIGHT];
for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; i++)
{
gFrameBuffer[i] = 0xff000000;
gTempBuffer[i] = 0xff000000;
}
}
And it's time to replace the whole render() function again:
void render(Uint64 aTicks)
{
for (int i = 0; i < 128; i++)
{
int d = (int)aTicks + i * 4;
drawsprite((int)(WINDOW_WIDTH / 2 + sin(d * 0.0034) * sin(d * 0.0134) * (WINDOW_WIDTH / 2 - 20)),
(int)(WINDOW_HEIGHT / 2 + sin(d * 0.0033) * sin(d * 0.0234) * (WINDOW_HEIGHT / 2 - 20)),
((int)(sin((aTicks * 0.2 + i) * 0.234897) * 127 + 128) << 16) |
((int)(sin((aTicks * 0.2 + i) * 0.123489) * 127 + 128) << 8) |
((int)(sin((aTicks * 0.2 + i) * 0.312348) * 127 + 128) << 0));
}
memcpy(gTempBuffer, gFrameBuffer, sizeof(int) * WINDOW_WIDTH * WINDOW_HEIGHT);
scaleblit();
}
Here we're drawing the little colorful "worm" with drawsprite()
again, but we're not clearing the screen before doing so. Afterwards, we copy the currently active image to the gTempBuffer[]
array, and then we're calling scaleblit()
.
Now the program is ready for compile and run. Try it out.
So what does the scaleblit()
do? In a very inefficient way, it takes 95% of the image, stretches it over the whole image (using point sampling), and blits it back, blending it with the "average" blend mode.
"Blit", or "blt", is short for "bit block transfer", which is a fancy way of saying "copy (graphics) data from one place to another". You may have heard of the Amiga blitter chip? Now you know what it means.
Since we're doing this over and over again to the same image, the result is constantly zooming the data. Also, since we're doing the blit using blending, the colors keep blending together, smoothing out.
Note that the strength of the blur effect is very dependent on the speed of the machine it's run on, and there's also a difference in speed between debug and release builds. Like with the snow simulation, modern computers are so fast that you probably get the effect in 60Hz (or whatever your screen refresh is).
Additional things to try:
init()
to fill the screen with some bright color, like white.memcpy()
and scaleblit()
in render()
, draw sprites additively, and make the colors dark (replace *127 + 128
with *12 + 12
).dst = (src1 * alpha) + (src2 * (1 - alpha))
).Next up: 06 - Primitives and Clipping..
Any comments etc. can be emailed to me.