MOOR security

Dieser Eintrag ist auf Englisch verfasst.

A room with light shafts, checked against a ray tracer

Entry 023No full test run

A game-style frame with sun shadows and light shafts renders correctly through the layer, and an independent Python ray tracer agrees exactly on every pixel it can vouch for.

What changed

I added a test sample, room, built the way a game builds a frame. The earlier samples each checked one feature; this one chains four passes that depend on each other:

  1. A depth-only pass renders the scene from the sun into a 1024² shadow map (a depth image that records, for each direction, how far the sun's light gets). It uses depth bias, a small offset that stops surfaces from shadowing themselves.
  2. The scene pass draws 13 boxes as instances of one cube into a 16-bit float target. It shades each pixel with Lambert lighting (brightness proportional to the cosine between surface and light) and a filtered shadow-map lookup.
  3. A compute pass walks 48 steps along each pixel's view ray, up to the depth the scene pass stored. At each step it asks the shadow map whether that point of air is in sunlight. The lit steps, weighted by a Schlick phase function (how much light scatters towards the eye), add up to the light shafts.
  4. A fullscreen pass adds the shafts to the scene and tonemaps with c / (1 + c) and gamma 1/2.2 into the 8-bit target.

Along the way it uses typeless depth resources read back as textures, a comparison sampler with a border colour, per-instance vertex data, a root constant buffer, descriptor tables, a 16-bit float UAV and the barriers between the passes.

The four passes of the room sample and the resources that flow between them.13 boxes one cube, instanced → 1. shadow pass depth only from the sun 1024² D32, depth bias; 13 boxes one cube, instanced → 2. scene pass Lambert + ambient PCF shadow lookup FP16 target, D32 depth; 1. shadow pass depth only from the sun 1024² D32, depth bias → 2. scene pass Lambert + ambient PCF shadow lookup FP16 target, D32 depth (R32 SRV); 1. shadow pass depth only from the sun 1024² D32, depth bias → 3. compute pass 48 dithered samples per ray shadow test at each Schlick phase (R32 SRV); 2. scene pass Lambert + ambient PCF shadow lookup FP16 target, D32 depth → 3. compute pass 48 dithered samples per ray shadow test at each Schlick phase (depth as R32 SRV); 2. scene pass Lambert + ambient PCF shadow lookup FP16 target, D32 depth → 4. fullscreen pass scene + shafts c / (1 + c), gamma 1/2.2 (FP16 SRV); 3. compute pass 48 dithered samples per ray shadow test at each Schlick phase → 4. fullscreen pass scene + shafts c / (1 + c), gamma 1/2.2 (FP16 UAV → SRV); 4. fullscreen pass scene + shafts c / (1 + c), gamma 1/2.2 → 640×360 RGBA813 boxesone cube, instanced1. shadow passdepth only from the sun1024² D32, depth bias2. scene passLambert + ambientPCF shadow lookupFP16 target, D32 depth3. compute pass48 dithered samples per rayshadow test at eachSchlick phase4. fullscreen passscene + shaftsc / (1 + c), gamma 1/2.2640×360 RGBA8R32 SRVR32 SRVdepth as R32 SRVFP16 SRVFP16 UAV → SRV
The four passes of the room sample and the resources that flow between them. (Mermaid source)

The interesting part

Expected values for a frame like this can't be worked out by hand, and no D3D12 machine is available for a WARP reference. So a small Python script ray-traces the same boxes in Python with exact sun visibility instead of a shadow map, and marches the same 48 samples with the same per-pixel offsets.

A shadow map is only an approximation, so the two images must differ near shadow edges. The script therefore picks test pixels only where its answer can't depend on that approximation. Each surface point and every march sample must be at least 0.025 units from a sun/shadow boundary, which is 1.5 shadow-map texels plus margin. The 7×7 pixels around each probe must also lie on one face.

All 11 probes match exactly. They cover the back, left and right walls, the ceiling, the pillar, the crate, the shaded floor, the sky, the sunlit floor, and shafts over a wall and over the floor.

Three views of the same room side by side. Left, our render: grey walls, a brown floor, a dark crate and sunlight falling through a four-pane window in visible shafts onto a bright patch on the floor. Middle, the Python reference, which looks the same. Right, their difference on black: thin white lines only along the edges of the shafts and of the sunlit patch.
Our render under Wine on Metal (left), the analytic ray-traced reference (middle), and their difference ×8 (right): only the edges of the shafts and of the sunlit patch differ.

Numbers

  • Probes: 11 of 11 match with a difference of 0, although their tolerances are 2–3.
  • Whole image: 218,954 of 230,400 pixels (95.0%) are identical to the reference, and 309 differ by more than 20 levels. The differences lie only on the shafts and on the rim of the sunlit patch, where the shadow map's resolution decides.
  • Three runs produced bit-identical output.
  • This entry changes only a test sample. There was no full conformance run, so there is no new test count.
Room sample: pixels that differ from the analytic reference (230,400 in all) (data as CSV)

difference per channel, in 8-bit levels · pixels

  1. any (> 0)11,446
  2. > 15,857
  3. > 23,662
  4. > 32,260
  5. > 51,155
  6. > 10513
  7. > 20309