Draw tfrag per-vertex colour, unlit, and record why the frame is dark
The tfrag RGBA array now modulates the texture. It is indexed by *position*, not by vertex-info: several vertex-info entries can share a position, and the array is sized vert_count, which is a position count. Colour follows the same rule as the vertex itself. Morphing blends the position only -- the game keeps the high-detail colour -- while collapse replaces the vertex outright, colour included. Getting that the other way round would tint distant terrain. The material is unlit, and that is a decision rather than a shortcut. Level 0's colours really are dark (patch 0 runs (25,26,26) to (108,144,141)) and the game's lighting is what brightens them, so what is on screen is the true albedo. Worth recording: the obvious move -- add a directional light, or crank ambient brightness -- does nothing at all. With no normals the PBR path is degenerate, so the brightness change has no effect, and a host light would have hidden the gap rather than shown it. The colour data was always reaching the mesh; what is missing is lighting, not a material flag. Written up in plan/level-assets.md, including what LightTfrags actually needs: the light bank from the level's gameplay extent, the 256-entry normal table at BOOT_NORMAL_TABLE_VADDR (0x165500) which every level overlay carries, and the already-parsed azimuth/elevation light records.
This commit is contained in:
@@ -516,12 +516,40 @@ Lesson for the rest of the project: a parser can be entirely self-consistent
|
||||
and still describe the wrong thing. Where a value is absolute rather than
|
||||
relative, check it against an independent source -- here, the bounding spheres.
|
||||
|
||||
### Why the first frame is dark
|
||||
|
||||
The renderer draws unlit: texture times the tfrag's own per-vertex colour.
|
||||
That product is genuinely dark -- level 0 patch 0's colours are `(25, 26, 26)`
|
||||
and `(92, 121, 119)` in 0..255 -- and the game's lighting is what brightens it.
|
||||
|
||||
Trying a host directional light or cranking ambient brightness was the wrong
|
||||
instinct and it showed: with no normals the PBR path is degenerate, so
|
||||
brightness changes did nothing at all. Faking it would also have hidden the
|
||||
gap. The colour attribute is picked up from the mesh automatically, so it is
|
||||
the *colour* data that is missing lighting, not a flag that was left off.
|
||||
|
||||
The real model is `LightTfrags`, which needs three things this project has not
|
||||
parsed yet:
|
||||
|
||||
* the **light bank** from the level's `gameplay` extent (directional and point
|
||||
light sets, and the per-vertex light `select` field);
|
||||
* the **normal table**, 256 pairs of cos/sin read from an ELF at
|
||||
`BOOT_NORMAL_TABLE_VADDR` (`0x165500`) -- every level overlay carries one, so
|
||||
it does not need the boot ELF;
|
||||
* the **light records** themselves, which are already parsed: azimuth and
|
||||
elevation at pi/128 radians per unit, and the base colour.
|
||||
|
||||
Given the table, a normal is `(cos(az)cos(el), sin(az)cos(el), sin(el))`,
|
||||
negated, exactly as `decode_normal` builds it. The lighting pass itself is
|
||||
`base + A.rgb * max(dA, dA*wA) + B.rgb * max(dB, dB*wB)` per vertex, and it
|
||||
must be reproduced in the PS2's float model rather than in Rust `f32`, per
|
||||
the fidelity rule.
|
||||
|
||||
### Still open
|
||||
|
||||
The GS register values the level load builds from the texture entry (TBP0/TBP1
|
||||
and so on), which a renderer needs if it emulates the GS rather than resolving
|
||||
texture addresses itself, and the guard-band clip test that sends patches
|
||||
crossing the guard band to the clipping program.
|
||||
The lighting above, the GS register values the level load builds from the
|
||||
texture entry (TBP0/TBP1 and so on), and the guard-band clip test that sends
|
||||
patches crossing the guard band to the clipping program.
|
||||
|
||||
A tfrag can now be turned into positioned, UV-mapped, textured triangles,
|
||||
told which LOD to draw, and morphed toward its parents at the right rate.
|
||||
|
||||
+10
-2
@@ -31,8 +31,16 @@ early, while they are still cheap to retire.
|
||||
passed: the vertex count matched, the triangle count matched, only the
|
||||
*place* was wrong. Fixed by taking the STROW immediately preceding the
|
||||
first position unpack rather than the last one.
|
||||
- [ ] Next: the per-vertex light records, which is why the first frame is flat
|
||||
and dark. Then the GS register set and the guard-band clip test.
|
||||
- [x] **Per-vertex colour.** The tfrag's RGBA array now modulates the texture,
|
||||
drawn unlit so the frame shows real albedo rather than a faked lighting
|
||||
model. Verified against the stored values (level 0 patch 0 runs
|
||||
`(25,26,26)` to `(108,144,141)`), which are genuinely dark.
|
||||
- [ ] **tfrag lighting.** Needs the level light bank from `gameplay`, the
|
||||
256-entry normal table from the overlay ELF at `0x165500`, and the
|
||||
already-parsed azimuth/elevation light records. The pass itself is
|
||||
`base + A*max(dA, dA*wA) + B*max(dB, dB*wB)` and has to run in the PS2
|
||||
float model. Detail in `plan/level-assets.md`.
|
||||
- [ ] Then the GS register set and the guard-band clip test.
|
||||
|
||||
The disc being the exact build the reference projects pin is the single
|
||||
biggest favourable fact so far: no disc conversion, no regional timing work,
|
||||
|
||||
Reference in New Issue
Block a user