feat(d3d): add opt-in sun shadows

Merged
NO
9d ago
+854-35

Summary

Adds opt-in sun shadows to the D3D11 presenter. When Sun shadows is ticked in Launcher.cmd (or RECOMP_D3D_SUN_SHADOW=1), characters and the ball cast real shadow-map shadows onto the ground instead of the game's flat stencil shadow. Off by default, so lower-powered PCs keep the original shadows and the existing draw path.

per frame (only when enabled)
  camera pass   pick the scene camera (largest host-lit draw, sky domes rejected)
  caster pass   skinned draws + compact rigid draws (ball) -> 4096² D32 map
                light = the game's own shadow direction (guest 0x00333870)
  visible pass
    game shadow darkening pass  -> stencil only (color writes off)
    ground receivers            -> color *= 1 - game_alpha * (1 - visibility)
    models (skinned, ball)      -> unchanged, keep the game's shading
  • Ground darkens by the game's per-scene shadow opacity, read from last frame's hidden darkening pass. Many stages light the sand with a separate rig or mostly baked light, so dimming only the sun's direct term was invisible on morning courts.
  • Nothing receives on frames where the game drew no shadow (hotel, menus), and a light at or below the horizon falls back to the game's shadow.
  • Draw commands now carry world matrices and view-projection; split-rate pose replay refreshes them.
  • See docs/sun-shadow.md for limits (one 64-unit box, no cascades, models never receive).

Evidence

  • Before: characters and the ball use the game's projected stencil shadow.
    After: a user play test accepted the shadows on the morning, dusk, sunset, Niki Beach and private beach courts.
  • Attract-demo captures across seven scenes: the hidden-pass count was always a whole number of actor/ball shadow sets (0, 19, 20, 39 … 77), so no other draw matched.
  • Perf, user play log at 2160p, 8x MSAA, SMAA, vsync: 59.9 fps average after startup (min 55), 11 late frames in 6.4 minutes. Muted 3-minute captures: render-thread cost about 0.65 ms/frame over main; the hitching seen in captures also occurs on main and lines up with disc reads.
  • Presenter tests cover opt-out parity, camera selection, direction and override validation, GPU visibility, ground opacity, compact and no-game-shadow bypasses, 0–3 weight casters and resource release.
  • Local public-ci: tools unit tests, public_export verify and verify-archive, MSVC Debug ctest (36/36) with both fail-closed authentication checks, llvm-mingw clang ctest (35/35). macOS job not run locally.

Merge Danger

Door: two-way

Opt-in feature; the default path creates no shadow resources or prepasses.

Blast Radius: presenter

Always-on parts: the draw adapter now reads the game's shadow direction and view/world matrices into every draw command, and the launcher gains a checkbox plus a sun_shadows field in private/launcher.json (older files load it as off). The hotel was not reached in testing.


Summary by cubic

Adds opt-in sun shadows to the D3D11 presenter. When enabled from Launcher.cmd (or RECOMP_D3D_SUN_SHADOW=1), characters and the ball cast real shadow-map shadows onto the ground instead of the game's flat stencil shadow. Off by default, so lower-powered PCs keep the original shadows and the existing draw path.

The render worker runs camera selection and a caster pass through the same pose/split replay as the visible pass. A single 4096² D32 map covers a 64-unit box centered 20 units down the camera's ray, lit by the game's own projected-shadow direction (guest 0x00333870). Ground darkens by the game's per-scene shadow opacity read from last frame's hidden darkening pass. Models never receive (skinned and compact rigid draws only cast), and when the game drew no shadow (hotel, menus) or the sun is below the horizon, nothing receives and the game's shadow stays. The game's shadow also stays until a frame has recorded the opacity and on darkening passes from another camera; the WARP device retry keeps the opt-in on.

Always-on changes

  • Draw commands now carry world matrices and view-projection; split-rate pose replay refreshes them, and the draw adapter copies the game's shadow direction into every draw.
  • Launcher gains a Sun shadows checkbox and a sun_shadows field in private/launcher.json; older files load it as off.