OHAO · Implementation Monograph

Monograph · 08 · Hybrid RT

08

Pipelines · full depth

Hybrid ray tracing

Chapter contract

Document RT shadow and RT GI as first-class techniques: how they init (pipeline + SBT), what they bind each frame, and exactly where deferred inserts them. Not a third world model.

Injection model

Deferred still owns beauty. Hybrid answers two questions with rays on the same TLAS the path tracer uses: “is this lit?” and “what one-bounce indirect arrives?”

DEFERRED SPINE CSM → GBuffer → SSAO → Light → Post owns framebuffer RT TECHNIQUES Shadow · 1-bounce GI shared TLAS + GBuffer reads FIG. HY-1 · INJECT, DON’T FORK
Fig. HY-1

Dashed arrow: results feed lighting; no second scene upload.

Technique init lifecycle

Both RTShadowTechnique and RTGITechnique follow the same skeleton (see their init):

init(device, physicalDevice, …)
  1. Load KHR ray-tracing function pointers
  2. createOutputImage — full-res storage image for visibility or GI
  3. GI only: material albedo buffer for secondary shading
  4. createDescriptorResources — TLAS + GBuffer views + outputs
  5. createRTPipeline — compact raygen/miss/(hit) group
  6. createShaderBindingTable
  7. resize when framebuffer size changes
Why separate techniques (not PathTracer)

Full PT carries 30+ AOVs and multi-bounce state. Hybrid needs thin pipelines that only answer V or Lᵢ¹ and fit inside the deferred graph’s time budget.

Shadow render workflow

RTShadowTechnique::render(cmd, ShadowInput)
  1. Fill ShadowInput: position/normal/depth views, view/proj, camera pos, size, light direction/type
  2. Update descriptors if views changed
  3. Push constants / uniforms for light
  4. traceRays — primary work is visibility rays from GBuffer world pos
  5. Output: soft/hard visibility factor image
  6. Lighting samples it as Vₖ
\[ V(x\!\to\!\ell)=\begin{cases}0&\text{any hit before light}\\1&\text{miss}\end{cases} \]

Ray flags typically prioritize TerminateOnFirstHit and skip shading — pure occlusion. Soft shadows average multiple samples toward an area/sphere light (same idea as PT NEE, cheaper continuation).

GI render workflow

RTGITechnique::render(cmd, GIInput)
  1. Inputs: position, normal, albedo, depth + camera + TLAS
  2. Optional material albedo SSBO for hit shading
  3. For each pixel: sample hemisphere at normal; trace one secondary ray
  4. On hit: shade with direct / emissive / simple lighting; on miss: env if bound
  5. Weight by BRDF×cosine/pdf; write indirect RGB
  6. Lighting adds as bounce fill (biased vs full path trace)
\[ L_i^{(1)}(x)\approx\frac{f_r\,L(y\!\to\!x)\,(n\!\cdot\!\omega)}{p(\omega)}\,V \]
Why one bounce only

Realtime. Multi-bounce color bleeding is PathTracer’s job (Ch. 06). Hybrid targets contact GI and soft fill for deferred iteration.

Placement in deferred graph

DeferredRenderer::render (relevant slice)
  1. CSM (raster cascades)
  2. GBuffer MRT
  3. SSAO → bind into lighting
  4. if AS supported && instanceCount > 0: addComputePass RTShadow
  5. if same: RT GI pass
  6. Deferred lighting consumes V and Lᵢ¹
  7. Sky / post

Graph declares texture reads so barriers place GBuffer in SHADER_READ before RT techniques sample it.

Host enable path

render_dispatch.cpp · conceptual
// each deferred frame
if (m_rtAccel && m_rtAccel->isSupported()) {
  m_deferredRenderer->setAccelerationStructure(m_rtAccel.get());
  m_deferredRenderer->setRTShadowsEnabled(true);
}
// techniques only run if instanceCount > 0 inside deferred graph

Limits

Honest scope

Static BLAS in trunk (animation removed). Hybrid ≠ PT parity. Noise depends on spp/filter — often combined with temporal post, not offline OIDN.

rt_shadow_technique.cppinit · SBT · render
rt_gi_technique.cppinit · materials · render
deferred_renderer.cppgraph inject
render_dispatch.cpphost enable
rt_acceleration_structure.*shared TLAS

Design units in this module

Each card is a focused design page (what / how / why + sources). Full tree: Sitemap.