Begin with three points#
Three vertices define a triangle. Large surfaces are split into triangles because a triangle always lies on one plane, and a GPU can consistently test its coverage. A curved object is usually an approximation made from many small triangles rather than a perfectly curved mathematical surface. A vertex can carry position, color, UV coordinates, and other attributes. Position determines shape. The other values tell later stages how the shape should look. Connecting vertices with indices lets triangles share data where their attributes agree.
Project the shape onto the screen#
A camera defines the viewing position and direction. Projection maps the visible three-dimensional coordinates into a two-dimensional image. Perspective makes nearby objects appear larger than similarly sized distant objects. The next chapter separates camera and model transforms so you can change one without confusing it with the other. After clipping, the rasterizer examines triangle coverage. The lab simplifies this to a visible coarse pixel grid, with a test at each pixel center. Real GPUs support more elaborate sample patterns and coverage rules, but the basic question is the same: which samples lie inside this triangle?
A triangle becomes pixels This illustration uses canvas. The explanation below describes the same process.
Values between the corners#
The triangle’s three corner colors are different. Barycentric coordinates describe a point as weights of those corners. Inside the triangle the weights are nonnegative and sum to one. Applying the weights to each color creates a smooth gradient. For a perspective-projected surface, many attributes need perspective-correct interpolation: interpolate the attribute divided by clip w and the reciprocal of w, then divide the results. Otherwise a textured triangle can look like it bends or slides as the camera moves. The simple lab keeps each triangle at one depth, while the scene renderer performs reciprocal-depth interpolation.
The mathematics, when you want it
point = a * vertex0 + b * vertex1 + c * vertex2
a + b + c = 1
color = a * color0 + b * color1 + c * color2Those weights are a coordinate system for a triangle. They do not mean the pixel stores three colors. The shader produces one final output for each covered sample, possibly after sampling textures or calculating lighting.
Which triangle is in front?#
A depth buffer remembers the nearest accepted depth at each sample. With a conventional increasing-distance depth setup, a new fragment passes a LESS test if its depth is smaller than the stored value. A passing opaque fragment updates depth and color. A farther fragment can be rejected. Turn depth testing off. The later triangle can overwrite the earlier one even when it should be behind it. Turn it on and change the second triangle’s depth. The winner changes with distance instead of draw order. Transparency needs additional decisions because a partly transparent surface should not simply behave like an opaque wall.
Inside QubicEngine#
MeshAsset supplies geometry; CameraData supplies view and projection; RenderGraph supplies color/depth attachments and their usages. The DX12 backend binds vertex/index buffers, a pipeline, and shaders, then records a draw. Those commands describe the process; the GPU executes them later.
Troubleshooting the idea#
Jagged edges are a sampling issue, not proof that the model has too few triangles. Missing faces can come from winding and back-face culling. A surface visible through another may have no depth attachment or mismatched depth settings. First ask which stage is wrong: shape, projection, coverage, or depth. Continue with transforms and cameras or open the first native triangle.