Let’s pick up where we left off in Part 12. Using the same playground we worked on last time, we will learn about lighting and 3D objects today. Remember the sun eclipse we worked on a couple of weeks ago? It’s back! Well, we are going to remove the sun and just focus on the planet this time.

First, let’s clean our kernel to only include this code:

int width = output.get_width();
int height = output.get_height();
float2 uv = float2(gid) / float2(width, height);
uv = uv * 2.0 - 1.0;
float radius = 0.5;
float distance = length(uv) - radius;
output.write(distance < 0 ? float4(1) : float4(0), gid);

You surely recognize all this code from few weeks ago. The only thing we did is replace the outside circle color with black and the inside circle color with white. The output image should look like this:

alt text

So far so good. The planet looks pretty flat and the lighting is too uniformly distributed to look real. Let’s fix that next. Geometry tells us that in order to find any point on a sphere, we need the sphere equation:

alt text

In our particular case x0, y0 and z0 are all 0 because our sphere is in the center of the screen. Solving for z gives us the the value of the planet color, so let’s replace the last line in the kernel, with these lines:

float planet = float(sqrt(radius * radius - uv.x * uv.x - uv.y * uv.y));
planet /= radius;
output.write(distance < 0 ? float4(planet) : float4(0), gid);

The output image should look like this:

alt text

As you expected, the color is now calculated starting with fully white in the center of the circle and ending with fully black on the outer circle. For that to happen, we had to divide the color by the radius, in order to normalize our range to the [0, 1] interval for the z value, which gives us a full range light effect. We actually faked having a light source positioned at (0, 0, 1). This brings up to the next topic: lighting.

Lighting is what gives life to our colors. In order to have lights in our scene we need to compute the normal at each coordinate. Normals vectors that are are perpendicular on the surface, showing us where the surface “points” to at each coordinate. Replace the last two lines with these lines:

float3 normal = normalize(float3(uv.x, uv.y, planet));
output.write(distance < 0 ? float4(float3(normal), 1) : float4(0), gid);

Notice we already had the value of z in the planet variable. The output image should look like this:

alt text

This is probably not what we wanted to see, but at least we now know how normals look like when calculating the color at each normalized coordinate. Next, let’s create a source of light located to our left (negative x) and a little behind us (positive z). Replace the last line with these lines:

float3 source = normalize(float3(-1, 0, 1));
float light = dot(normal, source);
output.write(distance < 0 ? float4(float3(light), 1) : float4(0), gid); 

We adopted a basic light model called Lambertian (diffuse) light, where we need to multiply the normal with the normalized light source. We will talk more about lighting in a future article, however, if you are interested in learning more about lighting models, here is a great resource for your reference. The output image should look like this:

alt text

Remember from last time that our kernel also gives us a timer uniform? Let’s use it for fun and profit! Replace the source line with this one:

float3 source = normalize(float3(cos(timer), sin(timer), 1));

By using the cos and sin functions, we gave the light source a circular movement. x and y are both ranging from -1 to 1 using the parametric equation of a circle. The output image should look like this:

alt text

We how have a good looking, illuminated object in the scene (planet in the sky), however, the object still presents a homogeneous surface. We can make it look more realistic in two ways: either apply a texture to it, or add some noise to the planet color. The source code is posted on Github as usual.

Until next time!