Rendering notes
📝Overview
- UE Mesh Drawing Pipeline
- UE source code
- ChatGPT
1. FPrimitiveSceneProxy
UE provide UPrimitiveComponent - a base class for all actor components "that contains or generate some sort of geometry, generally to be rendered or used as collision data". That class is created and managed by the Game Thread.
To be rendered that component create the class FPrimitiveSceneProxy - an Unreal’s render-thread representation of a primitive. It acts as a bridge between the game-thread and the render-thread, storing minimal data needed to render the mesh efficiently.
// copied code from UPrimitiveComponent class UE5.2 /** * Creates a proxy to represent the primitive to the scene manager * in the rendering thread. * @return The proxy object. */ virtual FPrimitiveSceneProxy* CreateSceneProxy() { return NULL; }
For every new class derived from the UPrimitiveComponent UE implements a new primitive proxy class derived from FPrimitiveSceneProxy: FSpriteSceneProxy, FSphereSceneProxy, FTextRenderSceneProxy, FParticleSystemSceneProxy, FWidget3DSceneProxy etc.
The same way for creating this objects each new class derived from the UPrimitiveComponent overrides CreateSceneProxy() function.
FPrimitiveSceneProxy is responsible for submitting FMeshBatch's to the renderer through the callbacks to GetDynamicMeshElements() and DrawStaticElements().
2. FMeshBatch
FMeshBatch describes a single draw call to the GPU. It specifies:
- Vertex buffers, index buffers
- Material/shader references
- Render states (depth, stencil, blending)
- LOD and mesh pass information
When the rendering pipeline prepares to draw meshes, it assembles them into batches (FMeshBatch) grouped by compatible render states and materials.
One mesh can generate multiple batches:
- Multiple materials → multiple batches.
- Different rendering passes (shadow, depth pre-pass, base pass) → separate batches.
Mesh: SM_Rock
- Material: M_Stone → FMeshBatch #1 (Base Pass)
- Shadow Pass → FMeshBatch #2 (Shadow Pass)
FMeshBatch decouples the FPrimitiveSceneProxy implementation (user code) from mesh passes (private renderer module). It contains everything the pass needs to figure out final shader bindings and render state, so the proxy never knows what passes it will be rendered in.
3. Mesh Pass
A Mesh Pass is a step (or "stage") of rendering, each with different goals:
______________________________________________________________________ Mesh Pass Name | Goal --------------------+------------------------------------------------- Depth Pre-Pass |Fill depth buffer early (efficient depth testing Base Pass |Main color/material rendering Shadow Pass |Shadow map generation Translucency Pass |Draw transparent objects Post-Processing Pass|Effects (lighting, bloom, reflections) ----------------------------------------------------------------------
Each mesh pass collects and processes specific FMeshBatches.
4. FMeshDrawCommand
The next step is to convert FMeshBatch into a mesh pass specific FMeshDrawCommand .
FMeshDrawCommand is an interface between FMeshBatch and the RHI. It's a fully stateless draw description that stores everything that the RHI needs to know about a mesh draw:
This enables caching and merging the draw calls just above the RHI level. FMeshDrawCommand is created from a FMeshBatch by a mesh pass specific FMeshPassProcessor.
Finally, SubmitMeshDrawCommands() is used to convert FMeshDrawCommand into a series of RHI commands set on a RHICommandList.
5. RHI
RHI is Unreal’s abstraction layer over graphics APIs (DirectX, Vulkan, Metal, etc.):
- It standardizes rendering commands across platforms.
- Allows Unreal to support multiple graphics APIs without rewriting rendering logic.
- Handles buffers, shaders, textures, and GPU communication.
[Game Thread (Component)] ↓ [Render Thread (FPrimitiveSceneProxy -> FMeshBatch -> MeshPasses)] ↓ [RHI Commands] ↓ [Vulkan / DirectX / Metal API calls] ↓ [GPU]
6. Under the UE hood
⚙️ What UE5 Does by Default
When you have multiple identical StaticMeshActors:
- Rendering pipeline creates a separate FPrimitiveSceneProxy and separate FMeshBatch for each actor/component.
- Each separate FMeshBatch typically becomes a separate GPU draw call.
- More actors → More draw calls → Higher CPU overhead → Lower frame rate.
UE5 optimizes many things (occlusion culling, frustum culling, Nanite for high-density geometry), but does NOT automatically combine separate actors into batches. Each actor is considered a unique primitive.