“This is the 15th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

An overview of the

In the case of overlapping primitives, Metal draws primitives according to the painter algorithm, rendering them in the order in which they are submitted. This rendering method will definitely get the right result, but it is not enough to render complex 3D scenes. To solve this problem, deep testing provided by Metal is needed to determine the visibility of each fragment at render time, eliminating hidden faces.

Depth is a measure of the distance from the observed position to a particular pixel. When using depth tests, you need to add depth textures (sometimes called depth buffers) to the render channel. Depth textures store depth values for each pixel in the same way that color textures store color values. The depth values for each fragment are achieved by hardware interpolating the depth of each vertex. The GPU tests new fragments to see if they are closer to the viewing position than the current value stored in the deep texture, and if the fragment is far away, the GPU will discard it; Otherwise, it updates the pixel data to include new depth values. Because the GPU tests the depth of all fragments, it can render triangles correctly even if they are partially obscured.

The sample demonstrates depth testing by changing the depth values for each vertex of a triangle. The depth of each fragment is calculated by interpolation of the depth of triangle vertices, and GPU conducts depth testing according to the above rules. Each time the render pass is performed, the data for the deep texture is cleared, a gray square is rendered at the middle point, and the triangle is rendered. Only segments closer to the viewer than the gray square are rendered.

Create depth texture

By default, MTKView does not create deep textures. The depthStencilPixelFormat property needs to be set to match the data format of the desired depth texture. The view automatically creates and manages them.

mtkView.depthStencilPixelFormat = MTLPixelFormatDepth32Float;
Copy the code

This example uses a 32-bit floating-point depth value for each pixel.

Specify the depth format in the render pipeline

To enable the depth of the rendering pipeline testing, need to create the rendering pipeline, set depthAttachmentPixelFormat properties in the descriptor, as shown below:

pipelineStateDescriptor.depthAttachmentPixelFormat = mtkView.depthStencilPixelFormat;
Copy the code

As with color formats, the render pipeline needs information about the depth texture format so that it can read or write values in the texture. When adding a depth texture, the format of the depth texture needs to be the same as the depth format of the view, and Metal will use the same depth value for the rest of the rendering pipeline:

Configuring depth testing

In Metal, render pipes and depth tests are configured independently and can be mixed and matched with each other.

As with the render pipeline, the MTLDepthStencilState object is used to specify the depth test when the App is initialized, and a reference to it is retained when the test needs to be executed.

As described in the flow chart above, the depth test can be passed when the new depth value is less than the existing value of the target pixel in the depth texture, indicating that the segment is closer to the viewer than anything previously rendered. When the depth test passes, the fragment’s color value is written to the color render attachment, and the new depth value is written to the depth attachment. The following code describes how to configure deep testing:

MTLDepthStencilDescriptor *depthDescriptor = [MTLDepthStencilDescriptor new];

depthDescriptor.depthCompareFunction = MTLCompareFunctionLessEqual;

depthDescriptor.depthWriteEnabled = YES;

_depthState = [_device newDepthStencilStateWithDescriptor:depthDescriptor];
Copy the code
Note: Metal combines depth testing with template testing, performing similar tests by storing a count per pixel to indicate how many times a fragment has passed the depth test. Template operations are useful for implementing certain 3D algorithms. Template testing is disabled by default and is not enabled in this example.Copy the code

Generates depth values in shaders

After the initialization step is complete, you can write the vertex shader code. In Metal Framework’s Rendering Pipeline Renderers, we learned that Metal’s normalized device Coordinate (NDC) system uses 4-dimensional coordinates, in which the vertex shader must provide a position for each vertex. This example ignores the Z coordinate, but to implement depth testing, you need to provide a value for the Z coordinate.

So this example provides a user interface to configure the z values and pass them to the vertex shader, which then takes the Z values on the input data and passes them to the output Z component.

out.clipSpacePosition.z = vertices[vertexID].position.z;
Copy the code

When the rasterizer computes the data to be sent to the fragment shader, it interpolates between these Z values:

The chip function can read, ignore, or modify the z value as needed. The GPU can sometimes perform additional optimizations without modifying the values calculated by the rasterizer. For example, it can perform z tests before running the fragment shader so that the fragment shader is not run for hidden fragments. When changing the depth value in a fragment shader, the GPU must first execute the fragment shader, which can cause performance degradation.

Clear depth texture

The render channel has a list of target textures, including (optionally) depth textures. When using depth tests, you need to configure the depth texture attachment for the render channel. The view is configured to contain a depth texture, and when the view’s render channel descriptor is accessed, the descriptor’s render depth target is automatically configured to point to the depth texture, and the render channel is configured to clear the depth texture at the beginning of each frame. All you need to do is provide a starting depth value for the depth texture.

MtkView. ClearDepth = 1.0;Copy the code

When the render channel starts, each pixel in the depth texture is initialized to 1.0.

Encoding drawing command

Like other Metal rendering examples, this example creates a render command encoder and then encodes a series of draw commands.

[renderEncoder setDepthStencilState:_depthState];
Copy the code

The rest of the code that sets the parameters and encodes the draw command is similar to what you have seen in rendering primitives using the render pipeline.

This example uses shaders to encode two draw commands. First, it renders a depth value of 0.5 quadrilateral in the view. Since all vertex depth values of the quadrilateral are less than the default, the quadrilateral is always drawn to the render target and the depth values are always updated. The example then renders a triangle using the depth value set by the user interface. If you increase the depth value of any vertex of the triangle above 0.5, the triangle partially disappears because some segments are behind the quadrilateral and fail the depth test.

Try using the slider and see how the results change.

conclusion

This article introduced depth testing under Metal, which was demonstrated by changing the depth values for each vertex of a triangle. The depth of each fragment is calculated by interpolation of the depth of triangle vertices, and GPU conducts depth testing according to the above rules.

Download the sample code for this article