In this part, we are talking about this step:
If you have forgotten the previous brief discussion, please click here.
The Necessity of Updates
No, we can cache the mesh batch at the beginning or when anything changes.
Since we are dealing with a static mesh, the cube's vertices will not change, and the static mesh component is not movable, so it cannot move.
It means at least in our test case, the mesh batch is always the same.
Timing
So, what is that timing? It is FScene
::UpdateAllPrimitiveSceneInfos
.
I will give you a image to let you know where it is:
This image shows:
UpdateAllPrimitiveSceneInfos
runs in the render thread, not the game thread.UpdateAllPrimitiveSceneInfos
runs before actor ticks.- The render thread does not have its own loop. Instead, the game thread sends the render commands to the render thread to drive the rendering.
- The render thread renders only when we need to redraw the viewport. For example, if you put the Unreal Engine editor into the background, the redraw may be skipped. However, scene primitive updating is not affected by this.
Processing
This is what FScene
::UpdateAllPrimitiveSceneInfos
does:
- Check if anything need to be added to the scene. In our case, yes, since this is the first time we render the cube static mesh.
- Because the cube mesh is a static mesh, it will call
FPrimitiveSceneInfo
::AddStaticMeshes
- It will call
FStaticMeshSceneProxy
::DrawStaticElements
. - This function will call
FStaticMeshSceneProxy
::GetMeshElement
, and return aFMeshBatch
.
FMeshBatch
and stop at the FStaticMeshSceneProxy::DrawStaticElements
layer, it may seem like we are drawing the scene proxy directly. This aligns with what we discussed in Render Proxy. This is a rough mental model, but it can be useful in some cases.Of course, we cannot stop here. This is not the actual rendering function; we do not draw anything here. Instead, we return the mesh batches that need to be drawn.
Information inside Mesh Batch
The most important thing in the mesh batch is the vertex data. It has a vertex factory reference, which references vertex buffer streams. Like we discussed in this chapter From Render Data to Shader Inputs.
By the way, it also have a reference of the material render proxy. But I just want to point out the ‘mesh data’ info, because we will see this again later in the mesh draw commands.
Have a try!
So, let’s test what we learned now. Can we draw a cube, without a static mesh component?
Let’s create UMyCubeComponent
and FMyCubeSceneProxy
.
You can get full codes here:
You can test like this:
- Add an actor to the scene
- Add our
MyCube
component to the actor
- Set the static mesh in the new component
- You can see our new cube now!
My code uses the default grid material, which makes it appear darker.
Here are some important code snippets:
UMyCubeComponent
is not a static mesh component, but we still render a cube mesh.- Instead of using
FStaticMeshSceneProxy
, we create our ownFMyCubeSceneProxy
, which inherits fromFPrimitiveSceneProxy
. - We override the
CreateSceneProxy
function in ourUMyCubeComponent
class to return an instance of our new scene proxy. - This is indicated by the first arrow in this image.
class UMyCubeComponent : public UPrimitiveComponent
class FMyCubeRenderProxy : public FPrimitiveSceneProxy
FPrimitiveSceneProxy* UMyCubeComponent::CreateSceneProxy()
{
if (Mesh == nullptr)
return nullptr;
FPrimitiveSceneProxy* Proxy = ::new FMyCubeRenderProxy(this);
return Proxy;
}
- We override the
DrawStaticElements
function to build anFMeshBatch
and return it, as discussed in this section. - This corresponds to arrow number 2 in this image.
- I hardcoded some data to make this example as simple as possible.
- We override the
GetViewRelevance
function to mark ourFMeshBatch
as relevant to the necessary passes. We will discuss this further later on. - To simplify things, I directly scaled the component's bound to avoid any issues with bound culling.
void FMyCubeRenderProxy::DrawStaticElements(FStaticPrimitiveDrawInterface * PDI)
{
FMeshBatch MeshBatch;
FMeshBatchElement& MeshBatchElement = MeshBatch.Elements[0];
FStaticMeshLODResources& LODModel = RenderData->LODResources[0];
const FStaticMeshVertexFactories& VFs = RenderData->LODVertexFactories[0];
MeshBatch.SegmentIndex = 0;
//...
const FVertexFactory* VertexFactory = &VFs.VertexFactory;
MeshBatch.VertexFactory = VertexFactory;
//...
PDI->DrawMesh(MeshBatch, FLT_MAX);
}