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>
85 lines
5.6 KiB
Markdown
85 lines
5.6 KiB
Markdown
---
|
||
id: rendering
|
||
title: "Rendering — the D3D9 pipeline, materials, sky, beams, performance"
|
||
status: established
|
||
source_sections: "PROGRESS_LOG.md §10 (render-fidelity notes); docs/BGF_FORMAT.md"
|
||
related_topics: [bgf-format, asset-formats, wintesla-port, locomotion, pod-hardware]
|
||
key_terms: [L4D3D, material-ramp, BSL, LOD, DPL, beam]
|
||
open_questions:
|
||
- "Replacement-LOD for NON-additive multi-LOD models; day/night material path priority (BT_MATPRI)"
|
||
---
|
||
|
||
# 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-on `BT_MATPRI` for
|
||
the ramps): the `mat\day` libs carry the warm ramps; the generic GEO libs are gray. Terrain/mesas
|
||
render warm tan; the sky's `dsky_mtl` ramp (0→0.99) gives bright clouds. `hasNormals` gate keeps
|
||
vehicles LIT (colored trucks). Ramp tint rule: explicit diffuse + NEUTRAL ramp ⇒ tint; coloured ramp
|
||
⇒ white. [T2]
|
||
- **Sky:** a `*sky.bgf` dome/plane drawn in a dedicated fullbright **PASS_SKY** (LIGHTING off, extended
|
||
far=9000 + world fog); tagged `drawAsSky` at load (the `.x` sidecar path doesn't exist). [T2]
|
||
- **The env pipeline** (`DPLReadEnvironment`→`DPLReadINIPage`, reads **`content/BTDPL.INI`** via
|
||
`L4DPLCFG`): 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 with `debug=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).
|