Now we have a working camera, and triangles are drawn from the most distant to the nearest. We’re getting very close to a complete renderer. The main issue remaining is when we walk into one of the cubes, everything goes haywire. This is down to the division by z in the projection function. If z is 0, then we get infinity as a result and odd results. If z is negative, then we get warped triangles and inverted rendering. What we need to do is detect when triangles are too close to the screen, or are behind the camera and skip rendering them.
As always, click here to give it a try, and here to download it. Here are the previous posts:
- Projection
- Wireframe
- Rotation
- Culling
- Inside Test
- Scanline Rasterisation
- Texture Mapping
- Camera
- Painter’s Algorithm
The first step is after all of the transformations on the vertices, but before projection. We now have the final z coordinate before we decide where the point is on screen. If the z value falls below a certain amount we add the value false to the list of projected vertices rather than calling the projection function. The reason we add the value false is to make sure that the index order of the vertices remains intact so that the triangle table still makes sense. If we were to just skip the vertex then we would get corrupted triangles on screen.
I chose the value 0.2 to clip at here rather than 0 simply through playing around. Clipping triangles at 0 felt too close, and left more large obscuring triangles on screen.
if v.z>0.2 then
add(proj,project(v))
else
add(proj,false)
end
After adding the false values into the projected vertex list we then detect it when building the draw list of triangles. If any of the points of the triangle we are adding to the list falls behind the near plane then we skip the entire triangle. After this we only sort and then render the triangles that are visible in front of us.
if a and b and c then
local z=a.z+b.z+c.z
add(lst,{a,b,c,z=z})
end
This leaves us with a working renderer. After this the remaining changes are about performance and simplicity. I think the next step will be around replacing our chain of rendering function calls with 4x4 matrix transformations. Doing this allows us to bake the set of calls needed per model, and then do a single matrix multiplication per vertex which itself contains all of the translation and rotation calls needed for both the model and the view.