The death/respawn "blue whirlwind" (tsphere) now matches the original cabinet
photo (capture.png): a smooth spinning lavender vortex with a bright core.
Root cause of the long-standing "radial spokes" artifact was NOT the warp code
but a general engine bug: L4D3D::SetTextureScrolling computed its texture-matrix
offset as scrollDelta * absolute_time, which grows unbounded and collapses UV
float precision -> a smooth scrolled cloud shatters into grainy radial steps.
Wrapped with fmodf(..., 1.0f) (identical under REPEAT tiling, full precision).
This also cleans the scrolling bexp beam grit and any other SCROLL material.
Visual reconstruction (verified against the real 45-vtx TSPHERE.BGF bicone,
offline-rasterized then ported):
- view ON-AXIS (eye centred on the throat) + spin in place -> concentric rings
(decomp FUN_00453dc4 does spin-about-local-Z + submit; the port had stubbed it)
- bintA cloud through a WIDE lavender ramp at full contrast (drawn as SKY);
no geometry "bands", no log-polar twist, no off-axis tornado (all discarded)
- tessellate the 12-facet bicone smooth; isotropic + trilinear; ramp baked into
the texture and drawn SELECTARG1(TEXTURE) to avoid double-tinting
Env knobs (BT_WARP_*) default to the verified values; BT_WARP_SELFTEST/SELFSHOT
are an off-by-default visual-verification harness (backbuffer frame dump).
Docs: new context/translocation-warp.md (geometry/material/visual/lifecycle/env);
reconstruction-gotchas.md gains the accumulated-time precision-collapse bug class;
rendering.md / multiplayer.md / decomp-reference.md / CLAUDE.md cross-linked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.6 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| rendering | Rendering — the D3D9 pipeline, materials, sky, beams, performance | established | PROGRESS_LOG.md §10 (render-fidelity notes); docs/BGF_FORMAT.md |
|
|
|
Rendering
The port renders via the WinTesla L4D3D/L4VIDEO Direct3D9 path (wintesla-port), carrying our
BT loader (bgfload.cpp) + fidelity work. The goal: match the authentic pod footage. Geometry/LOD
mechanics are in bgf-format; this topic covers the D3D9 draw path + the fidelity fixes. Full
detail: docs/PROGRESS_LOG.md §10.
The IG-board shading model (from libDPL DPLTYPES.H)
Shading is selected PER-GEOMETRY by vertex type: no-normal geometry (terrain/mesas/sky/buildings/
mech — WHITE baked verts) is UNLIT, colorized by the material's 2-endpoint material-ramp
(dpl_SetMaterialRamp — texture luminance indexes a low→high gradient); normal-bearing geometry
(~150 vehicle/missile files) is LIT by the map light. Applying the ramp to lit geometry = the "dusty
white blobs" bug (gate on hasNormals). [T2]
The big render-fidelity fixes (dbase/desert matched to pod footage)
- Geometry: CONN is a flat trilist (not fan); CONN+PCONN coexist — see bgf-format. Malformed fan-garbage was the "faceted mountains" bug. [T2]
- RAMP colorize (default-on
BT_RAMP) + day/night material priority (default-onBT_MATPRIfor the ramps): themat\daylibs carry the warm ramps; the generic GEO libs are gray. Terrain/mesas render warm tan; the sky'sdsky_mtlramp (0→0.99) gives bright clouds.hasNormalsgate keeps vehicles LIT (colored trucks). Ramp tint rule: explicit diffuse + NEUTRAL ramp ⇒ tint; coloured ramp ⇒ white. [T2] - Sky: a
*sky.bgfdome/plane drawn in a dedicated fullbright PASS_SKY (LIGHTING off, extended far=9000 + world fog); taggeddrawAsSkyat load (the.xsidecar path doesn't exist). [T2] - The env pipeline (
DPLReadEnvironment→DPLReadINIPage, readscontent/BTDPL.INIviaL4DPLCFG): resolves map+time → clip/fog/ambient + a directional sun. Bring-up stubs (EnsureValidProjection / per-frame ambient / uninit cloud colours) had clobbered it — now they RE-ASSERT the env values. Debug: copy BTDPL.INI → *DBG.INI withdebug=True. [T2]
Backface culling (default CW, BT_CULL)
The bring-up D3DCULL_NONE drew interior/back faces (the "dark wedge" shapes = mesh insides). CW is
correct for the BGF quad triangulation [a b c][a c d]. [T2]
LODs (default: shipping-engine first-LOD + ADDITIVE_LODS; BT_ADDLOD/BT_LOD_SCALE/BT_PUNCH)
See bgf-format for the √3 range decode, the ADDITIVE_LODS cumulative rule, PUNCH cutouts, and the coplanar-shell submission-order depth bias. Replacement LOD selection for NON-additive multi-LOD models is deferred (needs the board's hot-spot semantics). [T2]
Mech skins (BSL bit-slice — the rainbow-mech fix)
.BSL is a bit-sliced container (6 grayscale sub-images per file); the BMF 0x18 BITSLICE tag picks
the slice. Reading it as RGB overlaid 2-3 gray sheets = the "rainbow/graffiti" bug. Fixed:
image.cpp decodeBSL slice-decodes; bgfload threads the 0x18 channel; a cross-library RAMP registry
resolves the mech-skin softer ramp (defined in OTHER libs). Every mech renders authentic gray-metal
paint now. See asset-formats. [T2]
Weapon beams (the unported dpl_* layer)
The dpl_* beam renderable was never ported → beams are drawn by BTPushBeam/BTDrawBeams
(L4VIDEO). The real look is the ermlaser.bgf TUBE (native Z 0..−2000, UVs tiled) with the scrolling
bexp grit texture (from the .VMF SCROLL). ER lasers red, PPC blue, standard yellow. The beam
origin re-anchors to the live muzzle every frame (can't run out from under your own beam). See
combat-damage for fire-rate/muzzle. [T2]
Translocation warp (the respawn vortex) — see translocation-warp
The death/respawn "blue whirlwind" is tsphere.bgf (a 12-facet bicone) spun on-axis with the bintA
cloud through a wide lavender ramp, drawn as SKY. Full geometry/material/lifecycle/env in
translocation-warp. ⚠ Rendering-wide fix that came out of it: L4D3D::SetTextureScrolling
computed its texture-matrix offset as scrollDelta × absolute_time, which grew unbounded and collapsed
UV float precision → grainy/spoked scrolled textures the longer a session ran. Wrapped with fmodf(…, 1.0f). This also cleans up the scrolling bexp beam grit and any other SCROLL material. [T2]
(Bug class: reconstruction-gotchas §13.)
Performance — the 10fps→60fps fix (~50-80× render speedup)
The 10fps baseline had THREE stacked causes, all fixed (engine tree): (1) D3DCREATE_SOFTWARE_ VERTEXPROCESSING — every vertex transformed+lit on the CPU → HARDWARE first; (2) D3DX DrawSubset
attribute-table scan (our double-sided tris broke OptimizeInplace) → explicit SetAttributeTable +
direct DrawIndexedPrimitive; (3) no frustum culling → bounding-sphere culling per frame. ⚠ LESSON:
the user plays the DEBUG build, but the 10fps was NOT the debug build (RelWithDebInfo identical) —
split draw-vs-present + check the DEVICE flags before blaming the compiler. Diagnostics kept:
[spike]/[loadobj]/[rstat]. [T2]
Key Relationships
- Geometry/LOD: bgf-format. Base: wintesla-port (L4D3D). Shadow/visual-conform: locomotion.
- Renders on: pod-hardware (main 3D view).