Back to case studies

Case study / Material graph rendering

Material billboard in Unreal material graph.

A material-graph approach for rendering thousands of camera-facing billboard-like visuals without spawning thousands of Material Billboard Components and actor-level draw-call overhead.

Summary

This case study demonstrates a custom material billboard approach inside Unreal Engine's material graph. The available video suggests a practical material setup used to orient or present visual elements in a scene without relying only on standard billboard components.

Focus Areas

  • Unreal Engine material graph techniques
  • Billboard-style material behavior
  • Scene-facing visual elements
  • Optimized presentation for real-time environments

System Breakdown

the thing is material billboard component is pretty useful right ? for couple of objects ( since its a component it should exist in an actor) but what if you want to spawn thousands of material billboards and render all of those in a frame ?

It will ruin your performance since you should spawn thousand of actors and each actor has atleast one material billboard component -> that means thousands of extra draw calls ! and also some vertex calculation each frame ! well we cant afford that in real time applications/games so lets dive in and find a solution for it.

First lets look at what are material billboard components and how those works under the hood in unreal engine ->

In short material billboard mostly used for debug purposes but generally those can be used for effects (sprites) that always maintain some size in screen space so in our case an emissive light that in reality is visible even from kilometers away but in game/render engines a tiny light kilometers away will definitely culled out or cant be rendered properly cause its sub pixel size ! you cant render something that is smaller than a pixel right ?

so material billboard component solution is that it will always maintain its size in screen space ( so it wont become sub pixel ) by calculating the vertices position of the sprite (quad) each frame and resize it corresponding to the screen dimension + some extra logic for opacity,facing camera,etc! That seems simple but if you multiply the cost by 1000x 2000x it can definitely be multiple "ms" in your rendering pipeline. Also using of material billboard force the RHI to register new meshes each frame which tank the CPU.

Unreal Engine Code/Logic

Unreal engine code/logic :

  • Engine\Source\Runtime\Engine\Classes\Components\MaterialBillboardComponent.h
  • Engine\Source\Runtime\Engine\Private\Components\MaterialBillboardComponent.cpp

Roughly each render frame/view, for every visible Material Billboard component:

  • Get billboard source position.
  • Get camera-to-source vector and distance.
  • Use camera axes so the quad faces the camera.
  • Calculate size from BaseSizeX/Y, distance size curve, and screen/world-space mode.
  • Calculate opacity from distance opacity curve.
  • Write 4 quad vertex positions/UVs/colors.
  • Submit the quad as a dynamic render mesh batch.

So if you have:

  • 100 material billboard components
  • 1 view
  • 1 element each

You get about:

  • 100 GetDynamicMeshElements proxy submissions
  • 100 quads
  • 400 generated vertices
  • 100 mesh batches

Ingredients

As you can see all the ingredients needed for a material billboard do exist in material graph and gpu too !

  • sprite billboard -> simple quad plane
  • Camera world position
  • Mesh(Sprit) world position
  • Camera vector
  • Screen size
  • And vertex shader to manipulate the mesh -> WPO Material Property in Unreal engine material graph

Solution

So solution ->

Replicate material billboard logic in material graph:

as we are using instanced static meshes to make the system more performant we need to transform the "Object Position (Absolute)" to "Instance & Particle Space"

"Length (Camera Position - ObjectPosition)" will give us how far the billboard is from the camera

"Distance * FOV" helps to scale the billboards correctly when we are zooming or FOV of the camera changes

"PerInstanceCustomData * Width(material parameter)" able us to scale each instance independently if we want to

"float3(1,0,0) (constant vector) Transformed from local space to world space then normalized is the "World X direction"

"ConstantBiasScale (TexCoord.R,-0.5,2.0)" covert 0.0-1.0 UV to -1.0 - 1.0 range

"WorldXDirection * UV X Offset * BillboardSize creates horizontal vertex offset

We do the same for vertical direction

Since we are in instance space we need to add instance pivot to the calculated offset:

Subtract "AbsoluteWorldPosition" ( assume this is vertex world position) from Calculated "WorldPositionOffset" then Add it to "AbsoluteWorldPosition" - ObjectPosition(transformed to Instance&ParticleSpace)

Email copied