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:
EnderTheCoder
2026-09-29 18:08:11 +08:00
parent 6e5df350c7
commit 70f35d8382
18 changed files with 2027 additions and 45 deletions
+44
View File
@@ -0,0 +1,44 @@
#version 450
// Static-map model vertex stage. Draws the world-space triangle soup built by
// ra3::models (buildings and props the map places) against the exact camera the
// terrain raymarcher uses, so both passes share one projection and depth test.
//
// The camera is passed as its orthonormal basis so the same math as
// terrain.frag applies: ndc.x = dot(r, right) / (a * tan(fov/2) * aspect),
// ndc.y = -dot(r, up) / (a * tan(fov/2)), depth = (a - NEAR) / (FAR - NEAR)
// with `a = dot(r, fwd)` the view-space depth.
layout(location = 0) in vec3 in_pos;
layout(location = 1) in vec3 in_normal;
layout(location = 2) in vec2 in_uv;
layout(location = 3) in float in_layer;
layout(push_constant) uniform Push {
vec4 cam_pos; // xyz = eye position
vec4 fwd; // xyz = forward
vec4 right; // xyz = right, w = tan(fov / 2)
vec4 up; // xyz = up, w = tan(fov / 2) * aspect
vec4 sun; // xyz = sun direction, w = ambient
} pc;
layout(location = 0) out vec2 out_uv;
layout(location = 1) out vec3 out_normal;
layout(location = 2) flat out float out_layer;
const float NEAR = 10.0;
const float FAR = 60000.0;
void main() {
vec3 r = in_pos - pc.cam_pos.xyz;
float a = dot(r, pc.fwd.xyz);
float b = dot(r, pc.right.xyz);
float c = dot(r, pc.up.xyz);
float th = pc.right.w;
float th_aspect = pc.up.w;
float depth = clamp((a - NEAR) / (FAR - NEAR), 0.0, 1.0);
gl_Position = vec4(b / th_aspect, -c / th, depth * a, a);
out_uv = in_uv;
out_normal = in_normal;
out_layer = in_layer;
}