Design sample — temporary writing used to test the blog layout

· 2 min read

What Belongs in a Ray Payload?

A small design exercise in keeping hit information explicit while avoiding unnecessary state in a Vulkan ray-tracing payload.

Vulkan · Path Tracing · Shaders

A ray payload crosses shader stages, so every field deserves a reason to exist. RTVK’s current hit payload carries the result needed by the ray-generation shader and one piece of state that must survive nested visibility queries.

struct HitPayload
{
    float hitT;
    uint drawIndex;
    int primitiveID;
    uint rngState;
    vec2 bary;
};

This is temporary sample writing for the first website draft. It tests the article typography and code presentation using a real RTVK structure; it is not intended as the first published post.

Return the minimum hit record

The closest-hit shader writes the distance, draw index, primitive index, and barycentric coordinates. The ray-generation shader can then reconstruct the surface from scene buffers. Keeping that reconstruction in one place avoids copying a larger material or shading record through the payload.

The miss convention is equally useful: a negative hitT means the trace missed. That gives the caller a direct branch without adding another status field.

Preserve state that a trace can overwrite

rngState looks different from the hit fields because it belongs to the path, not the surface. It stays in the payload because an any-hit shader can consume a random number while testing stochastic alpha. When control returns to the ray-generation shader, the updated sequence must continue from that point.

The useful question is not whether a value can fit in the payload. It is whether another shader stage must mutate or return that value across traceRayEXT.

Keep transport state local

Radiance, throughput, ray-cone state, and the current direction remain local to the ray-generation shader. They do not need to cross the shader-stage boundary for the current design, so carrying them in the payload would make the contract larger without improving the data flow.

This small split produces a practical rule for RTVK: use the payload as an explicit return record and cross-stage state, then keep the rest of the integrator state close to the loop that owns it.

Sponza at 128 samples per pixel

The same principle applies when evaluating the renderer: keep the comparison direct. Drag the divider to compare RTVK’s 128-sample output with the converged result used by the benchmark suite.

Converged high-sample Sponza image from the RTVK benchmark suite Noisy 128-sample path-traced Sponza image from RTVK 128 spp Converged
Sponza rendered by RTVK. The converged result averages eight 32,768-spp batches.