Sol's Graphics Tutorial

13 - Simple Reflections

Here's yet another effect that may have had its origins with Amiga hardware. You can easily create a "reflection" of the screen by mirroring the screen vertically from some point onwards. Since with Amiga you could trick the video refresh to pick lines from where to read from, this was more or less free operation. Add some sine wave to the offset and you've got "water". Since we don't have tricky hardware, we'll just do a bunch of memcpy calls.

But first we need something to mirror. We could just load a picture, but that would make this a rather short chapter, so let's generate some stuff instead.

Take a copy of the previous chapter, resulting in ch13.

Make the render() function empty, and start with this init():

void init()
{
  for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; i++)
    gFrameBuffer[i] = 0xff000000;

  for (int i = 0; i < WINDOW_HEIGHT / 2; i++)
  {
    int c = (0xff * i) / (WINDOW_HEIGHT / 2);
    c = 0x010000 * c | 0xff000000;
    for (int j = 0; j < WINDOW_WIDTH; j++)
    {
        gFrameBuffer[i * WINDOW_WIDTH + j] = c;
    }
  }
}

First we clear the whole buffer to black, and then we draw a ramp of blue colors up to the half height of our buffer. That's sky dealt with.

How about a moon? Add this:

drawcircle(WINDOW_WIDTH / 2, 100, 60, 0xffff7f00);

(drawcircle was from chapter 06 - Primitives and Clipping)

The moon is quite lonely. Let's add a bunch of stars. Add this before the drawcircle:

for (int i = 0; i < 100; i++)
{
  int y = rand() % (WINDOW_HEIGHT / 2);
  int x = rand() % WINDOW_WIDTH;
  int c = rand() % 0xff;
  c = 0x010101 * c | 0xff000000;
  gFrameBuffer[y * WINDOW_WIDTH + x] = blend_add(c, gFrameBuffer[y * WINDOW_WIDTH + x]);
}

(blend_add was in chapter 05 - Blending a Bit)

For color, we take a random value between 0 and 255, and multiply it by 0x010101, replicating the value to the R, G and B channels. We use blend_add because it would look silly if stars made the sky darker.

Next, let's add some ground. Add this to the end of the init() function:

for (int i = 0; i < WINDOW_WIDTH; i++)
{
    int h = (sin(i * 0.01) * sin(i * 0.02) * sin(i * 0.03)) * WINDOW_HEIGHT / 20 + WINDOW_HEIGHT / 10;
    for (int j = WINDOW_HEIGHT / 2 - h; j < WINDOW_HEIGHT / 2; j++)
      gFrameBuffer[j * WINDOW_WIDTH + i] = 0xff114466;
}

Now we have a brown-ish plot of land growing from the middle height of our framebuffer.

Let's add some trees.

int xofs = WINDOW_WIDTH / 2;
int yofs = WINDOW_HEIGHT / 2;
int ht = WINDOW_HEIGHT / 5;

int c = 0xff001f00;

for (int i = 0; i < ht; i++)
{
    int w = (ht / 3) * (ht - i) / ht;
    for (int j = 0; j < w; j++)
    {
        int p = j - w / 2 + xofs;
        if (p >= 0 && p < WINDOW_WIDTH)
            gFrameBuffer[(yofs - i) * WINDOW_WIDTH + p] = c;
    }
}

This gives us a dark green triangle in the center. It doesn't quite look like a tree yet, so let's nudge the horizontal lines a bit. Add this after the int w line:

int nudge = (w > 3 ? rand() % 7 - 3 : 0);

and add the nudge value to p (after + ofs). Compile and run. That's a bit better.

But we need more trees. Replace the xofs, yofs and ht calculations with:

for (int count = 0; count < 60; count++)
{
  int xofs = rand() % WINDOW_WIDTH;
  int yofs = WINDOW_HEIGHT / 2 - 60 + count;
  int ht = rand() % WINDOW_HEIGHT / 10 + WINDOW_HEIGHT / 5;

also remember to close the for loop (by adding a line with } to the end of the function).

You may want to tweak the number of trees (60 here) depending on what resolution your window is, and/or what the results from your standard library rand() happen to give you.

Let's randomize the tree colors a bit too. Replace the c calculation with:

int c = rand() % 0x1f;
c = 0x000100 * c | 0xff000000;

That's good enough for now. Let's do the water. Starting render() with:

void render(Uint64 aTicks)
{
  for (int i = 0; i < WINDOW_HEIGHT / 2; i++)
  {
    int ypos = (i + WINDOW_HEIGHT / 2) * WINDOW_WIDTH;
    int srcpos = (WINDOW_HEIGHT / 2 - i) * WINDOW_WIDTH;
    memcpy(gFrameBuffer + ypos, gFrameBuffer + srcpos, WINDOW_WIDTH * sizeof(int));
  }
}

This mirrors the image vertically. Next we need to add some motion. Replace srcposcalculation with:

int wiggle = i + sin(aTicks * 0.01 + i * 0.1) * 32;
if (wiggle < 0) wiggle = 0;
if (wiggle > WINDOW_HEIGHT / 2) wiggle = WINDOW_HEIGHT / 2;
int srcpos = (WINDOW_HEIGHT / 2 - wiggle) * WINDOW_WIDTH;

We need to guard our wiggle from going out of bounds. That does something already, but we can improve things a bit.

Change the wiggle calculation to:

float z = i / (float)(WINDOW_HEIGHT / 2);
int wiggle = i + sin(aTicks * -0.001 + i * 0.1) * 32 * z;

The z is a value from 0 to 1. By multiplying the wiggle with it, the wave height is 0 at the shoreline and then scale up from there. We've also slowed the effect down a bit and reversed the wave direction by multiplying aTicks with a negative value.

Finally, let's make the wave frequency increase towards the shore. Change the int wiggle line to:

int wiggle = i + sin(aTicks * -0.001 + i * (2 - z) * 0.2) * 32 * z;

And with that, we're done.

Other things to try:

  • Alter number of stars, trees, position of the moon
  • Add rendering of other things, like a house, clouds, snowman(?)
  • Move the whole scenery drawing code to render(), and animate it

Next up: 14 - Particle System

Any comments etc. can be emailed to me.