|
3 | 3 |
|
4 | 4 | // PAL5 terrain splat — fragment stage. |
5 | 5 | // |
6 | | -// Blends up to four terrain textures per-texel by a weight atlas, then |
7 | | -// applies the same dynamic Lambert lighting as `actor_lit.frag`. |
| 6 | +// Composites up to four terrain textures per-texel by a weight atlas using the |
| 7 | +// original engine's LINEAR WEIGHTED SUM (reverse-engineered from the shipped |
| 8 | +// `terra{N}.psh`): `color = Σ_i tex_i(tileUV) * weight_i`. Unused slots carry |
| 9 | +// zero weight; an overlay patch's active-layer weights sum to ~1, and an |
| 10 | +// untextured patch is full weight on slot 0 (its base ground). Then applies the |
| 11 | +// same dynamic Lambert lighting as `actor_lit.frag`. |
8 | 12 | // |
9 | 13 | // Texture bindings (set 2, `texSampler[5]`): |
10 | 14 | // texSampler[0..3] = terrain-texture layers for slots 0..3 (unused slots |
11 | | -// are padded with slot 0's texture) |
| 15 | +// are padded with the base texture; their atlas weight is |
| 16 | +// zeroed so they never blend in) |
12 | 17 | // texSampler[4] = per-block weight atlas; RGBA = slots 0,1,2,3 weights |
13 | 18 | // |
14 | 19 | // The weight atlas is sampled with the per-block UV (`fragAtlasUV`); the |
@@ -88,22 +93,21 @@ void main() { |
88 | 93 | // world units). |
89 | 94 | vec2 tileUV = fragWorldPos.xz * mat.uv_xform.xy; |
90 | 95 |
|
91 | | - // Per-texel layer weights (already normalized so active layers sum to 1). |
| 96 | + // Per-texel layer weights: r/g/b/a = overlay palette slot 0/1/2/3. |
92 | 97 | vec4 w = texture(texSampler[4], fragAtlasUV); |
93 | | - int layers = int(mat.misc.x + 0.5); |
94 | 98 |
|
95 | | - vec3 base = texture(texSampler[0], tileUV).rgb; |
96 | | - vec3 col = base * w.r; |
97 | | - if (layers > 1) col += texture(texSampler[1], tileUV).rgb * w.g; |
98 | | - if (layers > 2) col += texture(texSampler[2], tileUV).rgb * w.b; |
99 | | - if (layers > 3) col += texture(texSampler[3], tileUV).rgb * w.a; |
100 | | - |
101 | | - // Guard against an all-zero weight texel (e.g. atlas seam): fall back to |
102 | | - // the base layer so terrain never shows black. |
103 | | - float wsum = w.r + (layers > 1 ? w.g : 0.0) |
104 | | - + (layers > 2 ? w.b : 0.0) |
105 | | - + (layers > 3 ? w.a : 0.0); |
106 | | - if (wsum < 0.001) col = base; |
| 99 | + // Faithful to the original `terra{N}.psh` (reverse-engineered from the |
| 100 | + // shipped shaders): terrain is a LINEAR WEIGHTED SUM of the up-to-four |
| 101 | + // ground textures by the `.alp` blend weights — |
| 102 | + // color = Σ_i tex_i(tileUV) * weight_i |
| 103 | + // Unused slots carry zero weight; for an overlay patch the weights sum to |
| 104 | + // ~1 across its active layers, and an untextured patch is full weight on |
| 105 | + // slot 0 (its base ground). No dominant-base/alpha-over. |
| 106 | + vec3 t0 = texture(texSampler[0], tileUV).rgb; |
| 107 | + vec3 t1 = texture(texSampler[1], tileUV).rgb; |
| 108 | + vec3 t2 = texture(texSampler[2], tileUV).rgb; |
| 109 | + vec3 t3 = texture(texSampler[3], tileUV).rgb; |
| 110 | + vec3 col = t0 * w.r + t1 * w.g + t2 * w.b + t3 * w.a; |
107 | 111 |
|
108 | 112 | // Dynamic Lambert lighting (mirrors actor_lit.frag). |
109 | 113 | vec3 N = normalize(fragNormal); |
|
0 commit comments