Skip to content


  • Apply Material with MD=Arena to Objects in Scene
  • Expected result: they get culled out through a hardcoded stencil sphere

Two cases:

  1. Normal gameboard arena
  2. Half-dome portal gameboard

StencilMaskVal: 1 =>#

arena: allowed to write: stencil_val == 1 && depth_near
stadium: allowed to write: && depth_near

StencilMaskVal: 2 =>#

arena: allowed to write: stencil_val == 2 && depth_near
stadium: allowed to write: stencil_val != 2 && depth_near


Sphere write out stencil_val = 1

Draw Arena:
StencilOp = < 3
DepthOp = depth_near

Draw Stadium:
StencilOp != 2
DepthOp = depth_near

Highlevel implementation tasks#

  1. Hardcode render two spheres as the stencil geo into stencil buffer (just piggy back on custom depth)
    Case: Normal gameboard
    Case: Portal gameboard
    Case: Special (space tear)
    - Sphere one writes stencil value that masks arena geo
    - Sphere two writes stencil value that masks stadium geo

    • Nongoal: Exact stencil operations/mask bits unimportant; figure them out later. Just need to make sure it doesn't get stomped
    • Goal: See how this affects translucent/masked materials whether in the arena or stadium and others that may not be thinking of (like decals/particles/etc)
  2. Modify prepass :
    - Prepass arena geo with stencil ops: Depth Test, Depth Write, Stencil=Keep (do not write), Stencil Test
    Stencil Compare = Equal & Stencil Op = Keep, Depth Write, Depth Compare = Greater
    - Prepass everything else as normal

  3. Modify the basepass:
    - Render arena geo first
    - Render everything else as normal
    - Possible Issues: Filtering out arena geo from the normal execution flow


  1. MaterialDomain + ViewRelevance as the extension point


  • CreateSceneProxies/Renderstate
  • Sometimes dirty it/recreate it for dynamic stuff

SceneProxies implements:

  • (Everyframe for dynamic/mebbe not for static)GetDynamicMeshElements/GetStaticElements
  • ComputeViewRelevance =>

  • Mark which passes to mark this component to be included in that pass

  • This also more complicated than just what is being returned here bc we also have to take into account the material (e.g. if the material is translucent=>viewrelevance for translucency)
    NOTE: Look at how custom depth property in the component is percolated all the way to the RT and how it gets added to custom depth primset

  • Extend SetPrimitiveViewRelevance(FPrimitiveViewRelevance& OutViewRelevance) const

    • If MaterialDomain == MD_BBArena, outviewrelevance.arenarelevance = 1

Render Side:

  • Gather all viewrelevance.arena into a drawlist/primset

  • REFERENCES: custom depth, StaticMeshBatchVisibility, VisibleDynamicPrimitives, TranslucentPrimSet, CustomDepthSet
    To add to custom primsets (e.g. customdepthprimset/translucent), it's in this function:
    FRelevancePacket::ComputeRelevance() is where it updatees them (in parallel way)
    FRelevancePacket::RenderThreadFinalize() is where it writes them back out to FSceneView/FScene

  • REFERENCES: PositionOnlyDepthDrawList, DepthDrawList, BasePassUniformLightMapPolicyDrawList
    Here's where we create the drawlists:
    void FStaticMesh::AddToDrawLists(FRHICommandListImmediate& RHICmdList, FScene* Scene)

  • Depth: Prepass Render Arena

  • Prepass arena geo with stencil ops: Depth Test, Depth Write, Stencil=Keep (do not write), Stencil Test - This is done in FDeferredShadingSceneRenderer::RenderPrePassView

  • Base Pass:

  • Custom Function to render our arnea-> ?????
  • Make sure it doesn't get rendered in normal pass -> ????
    - Looks like it's used alongside ShouldIncludeDomainInMeshPass


  1. Create separate primsets like custom depth and add our arena components to that
    Advnatages: Might be simpler
    Disadvantage: Might be rigid and inflexible with lots of edge cases
    Ex: What happens when we need to draw things that are in both stadium & arena? viewrelevance bitmask sounds better

  2. Just take a look at decals/mesh decals

Helpful bits#

TStaticDepthStencilState<...>::GetStaticState() for the class defining all the stencil op state
TStaticStencilState<>::GetRHI() for getting the default stencil (iirc, stencil expects to be set back to the default but not sure)
RHICommandSetStencilRef() for setting stencil
Custom depth primset

  StencilDithering for LOD transitoins

Visiblity Culling Interesting things#

virtual bool CanBeOccluded() const override
    -Gets Called in FPrimitiveSceneInfo::AddToScene() & setting FScene.PrimitiveOcclusionFlags

FrustumCull<true, true>(Scene, View);

    FSceneBitArray PrimitiveVisibilityMap;
    ICustomVisibilityQuery* CustomVisibilityQuery

There's a separation between PrimitiveBounds & PrimitiveOcclusionBounds