3D Math and a Software Rasterizer
Where Does Brightness Come From
In one line
The brightness of a surface is proportional to the cosine of the angle between the normal and the light direction. This is the Lambert model, and it is computed with a single dot product.
Why this was needed
After the previous steps, you can fill triangles with a color. But if you paint a cube in a single color, you see only a hexagonal silhouette with no sense of volume. This is because the cue people use to read form is differences in brightness.
How do you decide brightness? Physically, what matters is the amount of light reaching the surface, and even for light of the same intensity, if the surface is tilted, less light falls on the same area. It is brightest when it receives the light head on, and it is 0 when tilted 90 degrees. This relationship is exactly the cosine.
How it works
Lambertian diffuse reflection is computed like this.
n = normalize(표면 법선)
l = normalize(빛이 오는 방향)
k = max(0, dot(n, l))
색 = 기본색 * (ambient + (1 - ambient) * k)
The reason max(0, ...) is needed is that if the dot product is negative, the light comes from behind the surface. If you leave it as is, you get negative brightness and the color overflows the other way. And ambient is a constant that lumps together the light bounced from other surfaces; without it, a face turned away from the light becomes completely black and the form disappears. It is not a physically exact value, but a correction added in exchange for not computing global illumination.
Where you get the normal is the next problem. If you keep one per triangle (a face normal), each face gets uniform brightness and you get an angular look. For a cube, this is right. Conversely, if you keep a normal per vertex and interpolate it with barycentric coordinates (Phong shading), curved surfaces look smooth. This is the method you need when approximating a sphere with triangles.
A normal must rotate along with the model transformation. This is because when you rotate an object, its surface turns with it. However, a normal is a direction, not a position, so you must multiply it with w=0. Otherwise the translation gets added too, the normal points somewhere absurd, and the farther the object gets from the origin, the stranger the lighting becomes. It is a kind of bug that testing near the origin does not catch.
Animation is nothing special. You just change the angle and draw the same scene several times. However, if you multiply a rotation matrix every frame and accumulate it, floating-point error builds up and the object slowly gets distorted, so it is safer to keep the angle as state and build the matrix anew every frame.
Lambert alone cannot produce the shine of metal or a wet surface. Diffuse reflection is a model that bounces light back evenly in all directions, so it does not depend on the viewing angle. Shine is specular reflection, and the viewing direction is involved. The Phong model uses the angle between the light's reflection direction and the view direction, and the Blinn-Phong model uses the angle between the normal and the halfway vector between light and view. Either way, the point is to raise the cosine to a large exponent so that it is bright only at narrow angles, and that exponent expresses how smooth the surface is.
Also, writing the brightness-multiplied value to the file as is has a problem. The 0–255 written to the screen is not proportional to light intensity but a value that has gone through a gamma curve. To be physically exact, you must compute lighting in linear space and apply gamma at the end, and if you leave this out, bright areas wash out to white and dark areas get crushed. This lab leaves it as is for simplicity, but if the result looks somehow muddy, this is the right place to suspect.
What it looks like in the field
Most bugs where the lighting looks reversed come from the sign of the normal. If the order in which a triangle's vertices go around is flipped, the result of the cross product points inward, and then the face receiving light becomes dark and the face turned away becomes bright. The way to diagnose it is to paint the normals as colors. If you change the drawing to (n * 0.5 + 0.5) * 255, which way each face is looking shows up in the picture.
Another is forgetting to normalize. If the normal's length is not 1, the dot product is no longer the cosine, and the brightness doubles or dies. It happens especially often after a non-uniform scale has been applied.
It is also worth telling the kinds of light apart. What this lab uses is a directional light. It is treated as being very far away like the sun, so the direction is the same at every point and there is no falloff with distance. The computation is the cheapest and shadows are easy to handle. A point light, on the other hand, has a position, the light direction must be recomputed at each point on the surface, and it weakens in inverse proportion to the square of the distance. A spotlight adds an angle limit on top of that. The reason the lab starts with a directional light is to see separately what direction does and what distance does in a lighting calculation.
And brightness and shadows are different problems. A Lambert coefficient of 0 only means that the face is turned away from the light; it does not mean it is in shade because another object blocks it. To know about occlusion, you have to check separately whether light actually reaches the light source from that point, and that is what shadow maps or shadow rays do. In the last lab of the shader course, you will do this check yourself.
What you will do in the next lab
You build a cuboid mesh and find a normal for each face, and then paint the six faces in different colors and brightness with the Lambert model. Next, you change the angle by 45 degrees at a time to draw eight frames and play them in the browser, and finally you make a contact sheet that joins the eight frames into one image.