
Mesh Shaders and Meshlets support on Low-level API
In the latest stable version of Evergine, we’ve expanded Shader Model support within the engine. We’ve moved from Shader Model 6.3 (in the previous version) to Shader Model 6.7, which allows us to take advantage of a broader set of instructions in our shaders.
But the most exciting part of this update is that it opens the door to implementing Mesh Shaders in Evergine’s low-level layer, with a unified interface that already works in both DirectX 12 and Vulkan.
¿What are Mesh Shaders?
The concept of Mesh Shaders was introduced by NVIDIA in 2018, although it wasn’t implemented until 2020 with DirectX 12 Ultimate, and shortly after as an extension in Vulkan.
This new pipeline proposes dividing renderable meshes into small fragments called meshlets, enabling very interesting optimizations:
- Lower memory usage.
- More efficient culling techniques (frustum and occlusion).
- Higher performance in complex scenes, since the GPU can process a much larger number of meshes.
In other words: Mesh Shaders rethink how geometry is generated and processed on the GPU, replacing parts of the traditional rasterization pipeline with a more compact and flexible flow.
Comparing Pipelines
The classic rasterization pipeline consists of 3 fixed-function stages and 5 programmable stages, while the new pipeline with Mesh Shaders is simplified to 2 fixed-function stages and 3 programmable stages.
The Pixel Shader is still present in both pipelines, but the key difference lies in geometry generation, where two new stages appear:
- Task Shader (also known as Amplification Shader in DX12, or Object Function in Metal).
- Mesh Shader (replaces and extends the role of the Vertex, Geometry, and Hull Shaders).
Practical Example in Evergine
To illustrate how it works, in Evergine you only need a Mesh Shader and a Pixel Shader to draw a triangle.
Result:
The Mesh Shader defines the three vertices with their position and color and specifies the indices for triangulation. Unlike traditional Vertex, Geometry, and Hull Shaders, here we have much more freedom, including the possibility of automatically subdividing meshes into meshlets.
¿What is a Meshlet?
A meshlet is a small group of vertices and triangles (ideally 64 vertices and 126 triangles) that the GPU can process very efficiently.
To automatically generate these groups, there are several libraries available, but the one that has become the industry standard is MeshOptimizer by Arseny Kapoulkine.
In Evergine we have created a C# binding for this library, available as a NuGet package, which allows you to easily subdivide your meshes into meshlets.
Example usage with our binding:
Once the mesh is subdivided, the shader can unpack the meshlet data and render it optimally.
Our Mesh Shader that renders a mesh from its meshlet collection would be:
Task Shader: Efficient Culling
The Task Shader is optional, but particularly interesting because it allows us to decide which meshlets will be sent to the Mesh Shader stage.
This makes it very easy to implement occlusion culling techniques, rendering only the visible meshlets. In large scenes with many objects, the resource savings can be very significant.
Example: a Task Shader that discards all meshlets located on the left side of the screen, rendering only those on the right half.
Demo and Open Source
The new pipeline with Mesh Shaders is already available in Evergine’s low-level layer, ready for you to start experimenting with in DX12 and Vulkan.
You can download the complete source code of the demo used in this article from our repository:
Other examples of mesh subdivision into meshlets and rendering with this new pipeline are also included.
Conclusion
The arrival of Mesh Shaders marks a very important step for Evergine, making it possible to fully leverage modern GPUs. With this technique, you can render more geometry more efficiently, and you also have tools like our MeshOptimizer binding to start working with meshlets right away.