1.11 Front and Back Face Culling

This section explores the crucial rendering concepts of front and back faces. You may have noticed the cullMode field in the pipeline descriptor's primitive settings, where we've set it to either back or none. Let's discuss the meaning of cullMode and the significance of culling.

Launch Playground - 1_11_front_and_back_faces

Culling is the process of removing unnecessary geometries that aren't currently visible due to being facing away from the observer. In the primitive settings, cullMode specifically refers to the face culling mode. Setting it to none preserves all triangles, while back instructs the GPU to discard back-facing triangles.

Culling occurs before rasterization. The vertex order for the front face is specified by the frontFace parameter in the topology configuration, such as cw for clockwise and ccw for counter-clockwise. The GPU examines the order of triangle vertices; if the face direction matches our culling setup, the triangle is not rasterized.

Another concept closely related to culling is clipping, which also serves to remove invisible geometries and reduce rendering workloads. While culling deals with face orientation, clipping focuses on the spatial relationship between geometry and the view frustum. Clipping checks each triangle against the boundaries of the view frustum. Any triangle that falls entirely outside this viewing volume is discarded, or "clipped," before further processing.

When culling is disabled, we can determine if a rasterized fragment is part of a triangle facing toward or away from us using the boolean @builtin(front_facing). This information allows us to assign different colors to fragments based on their orientation, enabling distinct rendering of outside and inside surfaces. While these concepts may seem straightforward, they form the foundation for understanding depth (Z-buffer), which we'll explore in the next chapter.

Visualizing front and back faces differently helps us grasp the artifacts that can arise when depth testing is disabled. Let's examine the shader-side changes:

@fragment
fn fs_main(in: VertexOutput,  @builtin(front_facing) face: bool) -> @location(0) vec4<f32> {
    if (face) {
        return vec4<f32>(0.0, 0.0, 1.0 ,1.0);
    }
    else {
        return vec4<f32>(0.0, 1.0, 0.0 ,1.0);
    }
}

In the JavaScript code, we need to disable back face culling by setting cullMode to none in the primitive section of the pipeline descriptor. This ensures that flipped faces reach the fragment stage:

primitive: {
    topology: 'triangle-strip',
    frontFace: 'ccw',
    cullMode: 'none'
}

Some Faces That Should Be Occluded by Others Penetrating Through
Some Faces That Should Be Occluded by Others Penetrating Through

You may notice that the rendering appears inaccurate, with some faces penetrating through others that should occlude them. This occurs because we haven't enabled depth in our setup. Without depth, we can't determine which triangles should occlude others. In this situation, occlusion is determined solely by the order of draw commands. We'll address this issue in the next tutorial.

Leave a Comment on Github