render map objects: decode compiled W3DMesh art, skin and draw over terrain
Read the buildings and props a map places from its compiled art (the uncompressed worldbuilder stream + embedded DDS textures): - ra3.models: BAB asset-stream parser (lazy slices), W3DMesh + D3DHierarchy decode, DDS (DXT1/3/5 + uncompressed) decode, per-mesh bone remap and bind-pose single-joint skinning, flattened to one world-space triangle soup. - ra3.map: parse the ObjectsList chunk into (type, x, y, z, angle). - ra3.terrain: render3d rasterises the scene over the raymarched terrain with a z-buffer; gpu_terrain carries the scene for the GPU backends. - ra3.vulkan: second pipeline + depth attachment, terrain.frag writes gl_FragDepth, and a small depth bias keeps ground decals from z-fighting. - objects.fx shaders (compiled to SPIR-V), embedded like scene/terrain. Only opaque parts are drawn: FX-light billboards (DefaultW3D.fx / BasicW3D.fx) and damage-fill shells (BuildingsGenericDamageFill.fx) are skipped, since the latter paint the wrecked interior (e.g. orange CBBuilding_Wood) over the shell. Ground-decal meshes with no diffuse role and the Road templates themselves are still not drawn.
This commit is contained in:
@@ -212,6 +212,125 @@ per-cell restart is the remaining source of grid lines. Switching the atlas
|
||||
from 64 px blocks to continuous 32 px regions (or the Morton 8x8 layout) is the
|
||||
next step, pending an art correlation check.
|
||||
|
||||
### Compiled art (BinaryAssetBuilder) and map objects
|
||||
|
||||
Retail RA3 ships no `.w3x`/`.w3d` files: BinaryAssetBuilder bakes every model,
|
||||
texture and script into a *binary asset stream* — a `.manifest` index plus a
|
||||
`.bin` of relocatable instance data (and optional `.relo`/`.imp` fixups). The
|
||||
layout (little-endian) is:
|
||||
|
||||
```
|
||||
ManifestHeader (48 B) isBigEndian u8, isLinked u8, version u16,
|
||||
streamChecksum, allTypesHash, assetCount u32,
|
||||
totalInstanceDataSize, maxInstance/maxRelocation/
|
||||
maxImportsChunkSize, assetReferenceBufferSize,
|
||||
referenceManifestNameBufferSize, assetNameBufferSize,
|
||||
sourceFileNameBufferSize
|
||||
AssetEntry (48 B) * count
|
||||
typeId, instanceId, typeHash, instanceHash,
|
||||
assetReferenceOffset i32, assetReferenceCount i32,
|
||||
nameOffset i32, sourceFileNameOffset i32,
|
||||
instanceDataSize i32, relocationDataSize i32,
|
||||
importsDataSize i32, tokenized u32
|
||||
then the reference / referenced-name / asset-name / source-name buffers
|
||||
```
|
||||
|
||||
Asset names are `Type:Instance` (e.g. `W3DMesh:BB_GRASS02`). Instance pointers
|
||||
are stored as offsets from the start of the instance data (which begins at byte
|
||||
4 of `.bin`, after the stream checksum), so a slice is readable without the
|
||||
relocation stream.
|
||||
|
||||
Each multiplayer map carries its own stream (`data\maps\official\<id>\map.bin`)
|
||||
but it is **linked**: only map-specific assets (the terrain texture atlas,
|
||||
scripts, `GameMap`) have data; the rendered props are imported and therefore
|
||||
have `instanceDataSize == 0`. The complete prop art (meshes + textures) lives in
|
||||
`Data\WBData.big`'s `data\worldbuilder.bin`, which is **uncompressed** (first
|
||||
four bytes are the stream checksum, not `10 FB`), so `ra3.models` reads the
|
||||
1.1 GB stream lazily — the manifest is parsed and only the needed instance
|
||||
slices are read.
|
||||
|
||||
`W3DMesh` compiled layout (offsets from the instance start):
|
||||
|
||||
```
|
||||
+4 vertexBufferPtr +52 triangleCount +56 triangleItemPtr
|
||||
+60 shaderNameLength +64 shaderNamePtr
|
||||
vertexBuffer: +0 numVertices, +4 stride, +8 elementDataPtr,
|
||||
+12 declarationBytes, +16 declarationPtr
|
||||
declaration: text "p0:00:3f32 n0:0C:3f32 t0:1C:2f32" (D3D9 usage:index:offset:type)
|
||||
triangles: triangleCount * { u32 indexCount, u32 indexPtr } (24 B each), u32 indices
|
||||
```
|
||||
|
||||
The diffuse texture is found through the mesh's `FXShaderConstant`s
|
||||
(`+76` count, `+80` items): a texture-valued constant (TypeId `0xA59096A6`)
|
||||
names its role (`DiffuseTexture`, `NormalMap`, `SpecMap`) and points at a
|
||||
1-based index into the mesh's cross-asset references, which resolve by
|
||||
`(typeId, instanceId)`. The `Texture` instance embeds a standard DDS file (at
|
||||
`u32@+4`, or scan for `"DDS "`); `ra3.models::decode_dds` decodes DXT1/3/5 and
|
||||
uncompressed 16/24/32-bit.
|
||||
|
||||
**Vertices are stored in bone space, not object space.** A mesh whose vertex
|
||||
declaration carries blend data (`i0:..:4u8 w0:..:4u8n`, e.g. buildings and
|
||||
vehicles) must be skinned; props without it (`BB_GRASS02`, `IF_STREETSEGMENT01`)
|
||||
are already in object space. The skeleton is a `W3DHierarchy` asset named after
|
||||
the mesh's instance prefix (`W3DMesh:FI_STRUCTURE_02.NEWSKIN_CIV01` →
|
||||
`W3DHierarchy:FI_STRUCTURE_02`). Compiled layout:
|
||||
|
||||
```
|
||||
W3DHierarchy: u32 pad, u32 boneCount, u32 headerBytes, then boneCount records
|
||||
100 B each:
|
||||
u32 nameHash, i32 parent (-1 = root), f32 translation[3],
|
||||
f32 quaternion[4] (x, y, z, w), f32 matrix[12]
|
||||
```
|
||||
|
||||
The default (bind) pose is rebuilt by composing each bone's local
|
||||
translation/quaternion down the parent chain, then `skinnedPos = Σ wᵢ ·
|
||||
(Rᵢ·p + Tᵢ)` (and the normal by the rotation only). `ra3.models` does this before
|
||||
placing the mesh at the map object's `(x, y, angle)`.
|
||||
|
||||
The compiled shader (below) binds **one joint per vertex** — `WorldBones` holds
|
||||
64 bones as 2 `float4` each (quaternion `c[128+2j]`, translation `c[129+2j]`) —
|
||||
so the skin is rigid: `blendindices.x` selects the joint, remapped through the
|
||||
mesh's per-model **bone table** (vertex-descriptor `+0x14` = bone count,
|
||||
`+0x18` = `u16` bone indices into the `W3DHierarchy`). Applying the raw blend
|
||||
index without that remap tears models apart (`FI_BUILDING01`'s table is
|
||||
`[0,14,15,16,17,18]`, not `[0..5]`).
|
||||
|
||||
The map objects that lie flat on the ground (sidewalks, deck pieces) are
|
||||
coplanar with the terrain; retail biases their depth in the shader so they do
|
||||
not z-fight. The object pass reproduces that with a small negative depth bias
|
||||
(Vulkan `depthBiasConstant/SlopeFactor`, and a half-unit bias in the software
|
||||
rasteriser).
|
||||
|
||||
Meshes whose material has no diffuse texture (`DefaultW3D.fx`, `BasicW3D.fx` —
|
||||
`FXLIGHTS`/ambient helper billboards) are not opaque geometry and are skipped;
|
||||
drawing them fills the frame with garbage triangles. Likewise the
|
||||
`BuildingsGenericDamageFill.fx` **damage-fill** sub-meshes are skipped: they are
|
||||
the wrecked-interior shell (e.g. `CBBuilding_Wood`, an orange plank texture) that
|
||||
retail only reveals through damage holes, but our opaque pass would paint it over
|
||||
the main shell and tint whole buildings warm.
|
||||
|
||||
### Official shader behaviour (`Shaders.big` → `*.fxo`)
|
||||
|
||||
The compiled D3D9 effects in `Data\Shaders.big` name their parameters, so the
|
||||
model pipeline is recoverable. `buildingsgeneric.fxo` (`BuildingsGeneric.fx`)
|
||||
vertex stage: skinning (above), `World`/`ViewProjection`, and vertex color
|
||||
`c0` multiplied into the lit color
|
||||
(`(Ambient·AmbientColor + Σ DLᵢ.Color·max(N·DLᵢ,0)) · DiffuseColor · vertexColor`).
|
||||
Pixel stage samples `DiffuseTexture`/`NormalMap`/`SpecMap`/`DamagedTexture`/
|
||||
`CloudTexture` (all at **UV0**, except `DamagedTexture` at `v0.wz` = transposed
|
||||
UV1), then `final.rgb *= TintColor` and `*= ShroudTexture.rgb`. `DiffuseVelocity`
|
||||
is not used for static structures. So the diffuse texture is UV0 and is tinted by
|
||||
vertex color and `TintColor`; `basicw3d.fxo` instead modulates a single
|
||||
macro/lightmap with `(vertexColor + additive) * diffuse * 2` and has no normal
|
||||
map. (Recovered by disassembling the embedded `vs_3_0`/`ps_3_0` bytecode.)
|
||||
|
||||
The map's `ObjectsList` chunk is a list of nested `Object` assets —
|
||||
`Coord3D`, Z `angle`, `RoadType` u32, a `u16`-prefixed type-name and a property
|
||||
list whose keys index the shared name table (`ra3.map::parse_objects`). Each
|
||||
type resolves to the `W3DMesh` parts whose instance name equals it or starts
|
||||
with `<type>.`. Objects whose type is a `Road` template (the sidewalk/road
|
||||
segments) are not meshes and are not drawn yet.
|
||||
|
||||
### Map display names
|
||||
|
||||
The skirmish map list labels live in `Data\English.big`'s
|
||||
@@ -227,5 +346,9 @@ values are UTF-16 code units whose low byte is XORed with `0xFF`
|
||||
- `MPPositionList` layout (per-player starts for maps without waypoints).
|
||||
- Cliff textures and the `CliffTextureMapping` UV remap (`CliffTextures` is
|
||||
parsed but not yet drawn).
|
||||
- The `Road` network mesher (the map's sidewalk/road objects reference `Road`
|
||||
templates, not `W3DMesh` assets).
|
||||
- W3D container/hierarchy assembly and animation (props are drawn as their
|
||||
static mesh parts; skinned/animated in-game models are not).
|
||||
- Compiled asset blobs (`global.bin`, `static.*.bin`) and the `.manifest`
|
||||
schema used to deserialise them.
|
||||
|
||||
Reference in New Issue
Block a user