From Render Proxy to Mesh Batches

In this part, we are talking about this step:

image

If you have forgotten the previous brief discussion, please click here.

The Necessity of Updates

Do we need to do this every frame?

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:

image

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 a FMeshBatch.
icon
If we ignore the details about 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?

image

Let’s create UMyCubeComponent and FMyCubeSceneProxy.

You can get full codes here:

Codes

You can test like this:

  • Add an actor to the scene
image
  • Add our MyCube component to the actor
image
  • Set the static mesh in the new component
image
  • You can see our new cube now!
image

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.
  • class UMyCubeComponent : public UPrimitiveComponent
  • Instead of using FStaticMeshSceneProxy, we create our own FMyCubeSceneProxy, which inherits from FPrimitiveSceneProxy.
  • class FMyCubeRenderProxy : public FPrimitiveSceneProxy
  • We override the CreateSceneProxy function in our UMyCubeComponent class to return an instance of our new scene proxy.
    • This is indicated by the first arrow in this image.
FPrimitiveSceneProxy* UMyCubeComponent::CreateSceneProxy()
{
	if (Mesh == nullptr)
		return nullptr;
	FPrimitiveSceneProxy* Proxy = ::new FMyCubeRenderProxy(this);
	return Proxy;
}
  • We override the DrawStaticElements function to build an FMeshBatch and return it, as discussed in this section.
    • This corresponds to arrow number 2 in this image.
    • 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);
      }
    • I hardcoded some data to make this example as simple as possible.
  • We override the GetViewRelevance function to mark our FMeshBatch 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.