render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants

GrassKind (grass_kind.ludic) carries what grass.ludic, grass_gpu.ludic, grass.vert, grass_cull.comp and
model.frag's BLADE path held as constants: the cell, s0 / d0 / radius, the row bands and rows, the blade
profile (neck, root, swell, tip, bend), the heights, clumps, widths and arch, where it grows (slope, snow,
the photograph's test), the root / tip / gone-to-seed colours, the far tufts, the root occlusion, the
roughness, the sheen and the wind. Its defaults are those constants exactly, so a game that sets nothing
draws as before. The shaders read them as u_gk_* (the cull as five more Params vec4s, 288 bytes);
grass_reset.comp writes each band's index count so a kind of other rows reaches the draw in order.
grass_quality holds the kind to a settings tier (never denser, never farther); R3D_GRASS_* still win.
grass_profile_w / _bend are the one profile, for the meshes and a game's preview. grass.mesh takes only
the cell: the rest of it is an older copy that already differs from grass.vert, left as it draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 19:03:33 +03:00
parent d9a73d63bd
commit e908672034
25 changed files with 335 additions and 75 deletions

View file

@ -116,6 +116,7 @@ uniform float u_d0;
uniform float u_radius;
uniform float u_px; // one pixel in radians (grass.vert)
uniform int u_dbg;
uniform vec4 u_gk_ortho; // the grass kind's (grass_bind_kind): w its cell (m); the rest is grass.vert's
out vec3 v_wpos[];
out vec3 v_nrm[];
out vec2 v_uv[];
@ -124,7 +125,9 @@ out vec2 v_rot[];
out float v_hull[];
out float v_quake[]; // grass does not quake; written so model.frag can read it
const float CELL = 4.0; // grass.ludic GRASS_CELL: 4 m, so a near tile asks for what its blades need
// The kind's cell, as the CPU's tiles count it. Nothing else of the kind reaches this path yet: its
// shape, sizes and density test are an older copy of grass.vert's, kept as they draw today.
#define CELL u_gk_ortho.w
float hash1(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
uint pcg(uint v) { uint s = v * 747796405u + 2891336453u; uint w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u; return (w >> 22u) ^ w; }
float bladeHash(ivec2 cell, int j, int k) {

View file

@ -119,6 +119,12 @@ uniform float u_d0; // distance at which the spacing has doubled (m)
uniform float u_radius; // no blades past this
uniform float u_px; // one pixel's height in radians: no blade is drawn narrower than a pixel
uniform int u_dbg; // R3D_GRASS_DBG: 1 lift blades 0.3 m, 2 light as ground everywhere, 3 both
// the grass kind (grass_kind.ludic, grass_bind_kind); grass_cull.comp's Params carry the same four
uniform vec4 u_gk_tall; // tall_min, tall_max, cell_lo, cell_hi
uniform vec4 u_gk_grow; // tall_far, width, width_far, clump_freq
uniform vec4 u_gk_clump; // clump_lo, clump_hi, arch, arch_var
uniform vec4 u_gk_ground; // slope_lo, slope_hi, snow_in, snow_out
uniform vec4 u_gk_ortho; // ortho_floor, ortho_gain, ortho_green, cell (m)
out vec3 v_wpos;
out vec3 v_nrm;
out vec2 v_uv;
@ -128,7 +134,7 @@ out float v_hull;
out float v_quake; // only aspens quake; written so model.frag can read it
out vec3 v_tint; // the blade's colour field at its root (model.frag's regionTint and patchiness, once a blade)
const float CELL = 4.0; // grass.ludic GRASS_CELL: 4 m, so a near tile asks for what its blades need
#define CELL u_gk_ortho.w // the kind's cell (4 m by default, so a near tile asks for what its blades need)
// hash1 and fbm come from noise.glsl, which a GBLADE vertex stage is given (programs.ludic)
// An integer hash (PCG) for the per-blade values. The sine hash advanced linearly with
// the blade index, so a cell's blades fell into diagonal rows, and the rows read as
@ -207,7 +213,7 @@ void main() {
// line for both left a lake above the sea with grass on its bed or a valley with none.
float wl = u_sea_level;
if (u_lake.z > 0.0) { vec2 q = (xz - u_lake.xy) / u_lake.zw; if (dot(q, q) < 1.0) wl = max(wl, u_lake_level); }
float ok = (1.0 - smoothstep(0.30, 0.55, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(u_snow_line - 80.0, u_snow_line - 200.0, ht.r);
float ok = (1.0 - smoothstep(u_gk_ground.x, u_gk_ground.y, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(u_snow_line - u_gk_ground.z, u_snow_line - u_gk_ground.w, ht.r);
if (u_ortho_on > 0.5) {
vec3 oc = textureLod(u_ortho, huv, tpOrthoLod(1.5)).rgb;
// Is this ground vegetated, by the photograph? The test used to be green DOMINANCE -
@ -218,7 +224,7 @@ void main() {
// and -0.002 six hundred metres away, flipping between full density and a quarter over
// open meadow; g - b reads +0.076 and +0.014 and separates plant from rock and snow
// just as well, because rock and snow are neutral and vegetation is not.
ok *= 0.40 + 0.60 * smoothstep(0.0, 0.025, oc.g - oc.b);
ok *= u_gk_ortho.x + u_gk_ortho.y * smoothstep(0.0, u_gk_ortho.z, oc.g - oc.b);
}
if (h4 > ok) { cull(); return; }
// the root on the drawn surface: the CDLOD mesh follows the B-spline to within
@ -242,22 +248,22 @@ void main() {
// are what carry the silhouette against the light. An even 0.18-0.42 spread read as a lawn
// that had been cut, which is the one thing an alpine meadow is not.
float hh = h3 * h3;
float tall = mix(0.09, 0.60, hh) * mix(0.8, 1.2, hash1(cid * 0.1)) * (1.0 + 0.35 * smoothstep(1.0, 12.0, grow)) * life;
float tall = mix(u_gk_tall.x, u_gk_tall.y, hh) * mix(u_gk_tall.z, u_gk_tall.w, hash1(cid * 0.1)) * (1.0 + u_gk_grow.x * smoothstep(1.0, 12.0, grow)) * life;
// CLUMPS: grass grows in tussocks, taller and shorter patches a metre or so across, and that
// relief is the texture a meadow has from above - an even height reads as felt
float clump = gnoise(xz * 0.85 + 3.7) * 0.5 + 0.5;
tall *= mix(0.45, 1.40, clump * clump * (3.0 - 2.0 * clump));
float clump = gnoise(xz * u_gk_grow.w + 3.7) * 0.5 + 0.5;
tall *= mix(u_gk_clump.x, u_gk_clump.y, clump * clump * (3.0 - 2.0 * clump));
// 2.8 cm was FIVE TIMES a blade of meadow grass, which is 3-6 mm. At 2 m from the
// camera that is a broad dark scimitar lying along the ground rather than a blade
// standing in a sward, and no amount of profile or colour work fixes a blade that is
// the wrong size. Measured, not guessed: it is the one number behind every "too wide
// and too flat" note in this stage.
float bw = 0.010 * mix(1.0, 0.45 * grow, smoothstep(1.0, 4.0, grow));
float bw = u_gk_grow.y * mix(1.0, u_gk_grow.z * grow, smoothstep(1.0, 4.0, grow));
if (far) { bw = max(bw, spacing * 0.35); tall = min(tall, spacing * 0.3); }
// a blade under a pixel is a vertex bill that shimmers: widen it to one and a bit, and let the
// thinning (spacing) keep the coverage
bw = max(bw, dist * u_px * 1.1);
vec3 p = vec3(a_pos.x * bw, a_pos.y * tall, a_pos.z * tall * (0.6 + 0.8 * h4));
vec3 p = vec3(a_pos.x * bw, a_pos.y * tall, a_pos.z * tall * (u_gk_clump.z + u_gk_clump.w * h4));
vec3 n = vec3(0.0, 0.3, 1.0);
// The shared field (wind.glsl), so the gust that crosses this meadow is the same gust that
// reaches the trees behind it a moment later. The two sines that used to live here were a

View file

@ -21,6 +21,12 @@ layout(set = 0, binding = 0) uniform Params {
uvec4 base; // each band's first record in Out
uvec4 cap; // ... and how many it holds
vec4 tp; // the page pool (u_tp_dims): tiles a side, height texels a tile, photograph texels a tile, height texels a side
// the grass kind (grass_kind.ludic, grass_params_kind): grass.vert's u_gk_* by the same names
vec4 gk_tall; // tall_min, tall_max, cell_lo, cell_hi
vec4 gk_grow; // tall_far, width, width_far, clump_freq
vec4 gk_clump; // clump_lo, clump_hi, arch, arch_var
vec4 gk_ground; // slope_lo, slope_hi, snow_in, snow_out
vec4 gk_ortho; // ortho_floor, ortho_gain, ortho_green, cell (m)
} pr;
layout(set = 0, binding = 1) readonly buffer Tiles { vec4 tiles[]; }; // corner x/z, indices per cell, cells per side
@ -36,7 +42,7 @@ layout(set = 0, binding = 8) uniform sampler2DArray u_tp_h; // (T +
layout(set = 0, binding = 9) uniform sampler2DArray u_tp_nrm;
layout(set = 0, binding = 10) uniform sampler2DArray u_tp_ortho; // (S + 2)^2 a layer
const float CELL = 4.0; // grass.ludic GRASS_CELL
#define CELL pr.gk_ortho.w // the kind's cell (m)
uint pcg(uint v) { uint s = v * 747796405u + 2891336453u; uint w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u; return (w >> 22u) ^ w; }
float bladeHash(ivec2 cell, int j, int k) {
@ -132,10 +138,10 @@ void main() {
float h3 = bladeHash(ci, j, 2), h4 = bladeHash(ci, j, 3);
float wl = pr.lev.y;
if (pr.lake.z > 0.0) { vec2 q = (xz - pr.lake.xy) / pr.lake.zw; if (dot(q, q) < 1.0) wl = max(wl, pr.lev.x); }
float ok = (1.0 - smoothstep(0.30, 0.55, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(pr.dens.w - 80.0, pr.dens.w - 200.0, ht.r);
float ok = (1.0 - smoothstep(pr.gk_ground.x, pr.gk_ground.y, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(pr.dens.w - pr.gk_ground.z, pr.dens.w - pr.gk_ground.w, ht.r);
if (pr.lev.z > 0.5) {
vec3 oc = groundO(huv);
ok *= 0.40 + 0.60 * smoothstep(0.0, 0.025, oc.g - oc.b);
ok *= pr.gk_ortho.x + pr.gk_ortho.y * smoothstep(0.0, pr.gk_ortho.z, oc.g - oc.b);
}
if (h4 > ok) return;
float h = heightSmooth(huv);
@ -143,10 +149,10 @@ void main() {
float ang = bladeHash(ci, j, 6) * 6.2831853;
float grow = spacing / pr.dens.x;
float hh = h3 * h3;
float tall = mix(0.09, 0.60, hh) * mix(0.8, 1.2, hash1(cid * 0.1)) * (1.0 + 0.35 * smoothstep(1.0, 12.0, grow)) * life;
float clump = gnoise(xz * 0.85 + 3.7) * 0.5 + 0.5;
tall *= mix(0.45, 1.40, clump * clump * (3.0 - 2.0 * clump));
float bw = 0.010 * mix(1.0, 0.45 * grow, smoothstep(1.0, 4.0, grow));
float tall = mix(pr.gk_tall.x, pr.gk_tall.y, hh) * mix(pr.gk_tall.z, pr.gk_tall.w, hash1(cid * 0.1)) * (1.0 + pr.gk_grow.x * smoothstep(1.0, 12.0, grow)) * life;
float clump = gnoise(xz * pr.gk_grow.w + 3.7) * 0.5 + 0.5;
tall *= mix(pr.gk_clump.x, pr.gk_clump.y, clump * clump * (3.0 - 2.0 * clump));
float bw = pr.gk_grow.y * mix(1.0, pr.gk_grow.z * grow, smoothstep(1.0, 4.0, grow));
bw = max(bw, dist * pr.dens.z * 1.1);
uint b = dist < pr.bands.x ? 0u : (dist < pr.bands.y ? 1u : 2u);
uint slot = atomicAdd(cmds[b * 5u + 1u], 1u);
@ -154,6 +160,6 @@ void main() {
uint o = (pr.base[b] + slot) * 4u;
outv[o] = vec4(xz.x, h - 0.02, xz.y, seed);
outv[o + 1u] = vec4(sin(ang), cos(ang), tall, bw);
outv[o + 2u] = vec4(bladeField(xz, h), 0.6 + 0.8 * h4);
outv[o + 2u] = vec4(bladeField(xz, h), pr.gk_clump.z + pr.gk_clump.w * h4);
outv[o + 3u] = vec4(gn, dist);
}

View file

@ -1,8 +1,10 @@
// grass_reset.comp - zero each band's instance count before grass_cull.comp counts into it. On the
// GPU, in order, because the frame before may still be drawing from the same records.
// GPU, in order, because the frame before may still be drawing from the same records. Each band's
// index count is written here too, from the blade the CPU draws it with, so a grass kind of other
// rows (grass_kind_set) reaches the draw in the frame's own order.
layout(local_size_x = 1) in;
layout(set = 0, binding = 0) uniform Params { uvec4 n; } pr;
layout(set = 0, binding = 0) uniform Params { uvec4 n; uvec4 idx; } pr;
layout(set = 0, binding = 1) buffer Cmds { uint cmds[]; };
void main() {
for (uint b = 0u; b < pr.n.x; b++) { cmds[b * 5u + 1u] = 0u; }
for (uint b = 0u; b < pr.n.x; b++) { cmds[b * 5u] = pr.idx[b]; cmds[b * 5u + 1u] = 0u; }
}

View file

@ -31,6 +31,10 @@ uniform vec3 u_blade_tip;
uniform sampler2D u_blade_tex; // straightened photographic blades, side by side
uniform float u_blade_cols; // how many are in it
uniform float u_blade_tex_on;
// the grass kind (grass_kind.ludic, grass_bind_look): the scatter's blade layers bind it too
uniform vec4 u_gk_seed; // gone to seed: colour, and how far a seeded blade goes to it
uniform vec4 u_gk_look; // seed_lo, seed_hi (the share gone to seed), tuft_mix, tuft_shade
uniform vec4 u_gk_mat; // ao_root, roughness
#endif
#ifdef CARD
uniform float u_cull; // the layer's cull distance (m); 0 = none
@ -105,8 +109,8 @@ void main() {
vec3 field = v_tint;
#endif
alb = mix(u_blade_base, u_blade_tip, smoothstep(0.0, 0.55, t)) * field;
float dry = smoothstep(0.7, 0.8, fract(v_seed * 3.17));
alb = mix(alb, vec3(0.40, 0.36, 0.13) * (0.45 + 0.55 * t), dry * 0.75);
float dry = smoothstep(u_gk_look.x, u_gk_look.y, fract(v_seed * 3.17));
alb = mix(alb, u_gk_seed.rgb * (0.45 + 0.55 * t), dry * u_gk_seed.w);
// A REAL BLADE, if the game gave us one. Each blade picks a column of the atlas from
// its own seed and runs v from sheath to tip, so a meadow is eight different plants
// rather than one plant ten thousand times. The photograph carries the midrib, the
@ -135,7 +139,7 @@ void main() {
alb *= mix(vec3(1.0), min(g, vec3(1.15)), 0.85);
}
// a far tuft is a patch of the meadow, darker than a lit blade tip and never straw
if (v_hull < 0.0) alb = mix(u_blade_base, u_blade_tip, 0.45) * field * 0.72;
if (v_hull < 0.0) alb = mix(u_blade_base, u_blade_tip, u_gk_look.z) * field * u_gk_look.w;
// a rounded cross-section reads softer than a flat card
vec3 side = normalize(cross(N, vec3(0.0, 1.0, 0.0)) + vec3(1e-4));
n = (v_hull < 0.0) ? N : normalize(N + side * (v_uv.x * 2.0 - 1.0) * 0.6);
@ -149,7 +153,7 @@ void main() {
// an occlusion term; grass albedo is capped near 0.5 and this was dividing it by five.
// A blade is a thin thing standing in open air: shaded at the root by its neighbours,
// not buried.
arm = vec3(mix(0.55, 1.0, t), 0.85, 0.0);
arm = vec3(mix(u_gk_mat.x, 1.0, t), u_gk_mat.y, 0.0);
#elif defined(CARD)
// thin grass is lit from either side: face the card toward the sun before shading
if (dot(N, u_sun_dir) < 0.0) N = -N;

View file

@ -926,7 +926,7 @@ T 74258189 u_tershadow 7
T 74258189 u_tershadow_aux 8
T 74258189 u_ts_height 9
P d48d6a48 grass.mesh model.frag #define FOLIAGE;#define BLADE;#define MESH;
B d48d6a48 vert 0 4420
B d48d6a48 vert 0 4448
U d48d6a48 vert u_view 0 mat4 1 0
U d48d6a48 vert u_proj 64 mat4 1 0
U d48d6a48 vert u_vp 128 mat4 1 0
@ -949,7 +949,8 @@ U d48d6a48 vert u_d0 4404 float 1 0
U d48d6a48 vert u_radius 4408 float 1 0
U d48d6a48 vert u_px 4412 float 1 0
U d48d6a48 vert u_dbg 4416 int 1 0
B d48d6a48 frag 1 1028
U d48d6a48 vert u_gk_ortho 4432 vec4 1 0
B d48d6a48 frag 1 1088
U d48d6a48 frag u_cascade_vp 0 mat4 5 64
U d48d6a48 frag u_cascade_split 320 float 5 16
U d48d6a48 frag u_cascade_range 400 float 5 16
@ -998,6 +999,9 @@ U d48d6a48 frag u_blade_base 992 vec3 1 0
U d48d6a48 frag u_blade_tip 1008 vec3 1 0
U d48d6a48 frag u_blade_cols 1020 float 1 0
U d48d6a48 frag u_blade_tex_on 1024 float 1 0
U d48d6a48 frag u_gk_seed 1040 vec4 1 0
U d48d6a48 frag u_gk_look 1056 vec4 1 0
U d48d6a48 frag u_gk_mat 1072 vec4 1 0
T d48d6a48 u_arm 2
T d48d6a48 u_blade_tex 3
T d48d6a48 u_brdf 4
@ -1015,7 +1019,7 @@ T d48d6a48 u_tp_nrm 15
T d48d6a48 u_tp_page 16
T d48d6a48 u_ts_height 17
P cb13f3c6 grass.vert model.frag #define FOLIAGE;#define BLADE;#define GBLADE;
B cb13f3c6 vert 0 356
B cb13f3c6 vert 0 448
U cb13f3c6 vert u_view 0 mat4 1 0
U cb13f3c6 vert u_proj 64 mat4 1 0
U cb13f3c6 vert u_vp 128 mat4 1 0
@ -1040,9 +1044,14 @@ U cb13f3c6 vert u_d0 340 float 1 0
U cb13f3c6 vert u_radius 344 float 1 0
U cb13f3c6 vert u_px 348 float 1 0
U cb13f3c6 vert u_dbg 352 int 1 0
U cb13f3c6 vert u_gk_tall 368 vec4 1 0
U cb13f3c6 vert u_gk_grow 384 vec4 1 0
U cb13f3c6 vert u_gk_clump 400 vec4 1 0
U cb13f3c6 vert u_gk_ground 416 vec4 1 0
U cb13f3c6 vert u_gk_ortho 432 vec4 1 0
I cb13f3c6 a_pos 0 vec3
I cb13f3c6 a_uv 2 vec2
B cb13f3c6 frag 1 1028
B cb13f3c6 frag 1 1088
U cb13f3c6 frag u_cascade_vp 0 mat4 5 64
U cb13f3c6 frag u_cascade_split 320 float 5 16
U cb13f3c6 frag u_cascade_range 400 float 5 16
@ -1091,6 +1100,9 @@ U cb13f3c6 frag u_blade_base 992 vec3 1 0
U cb13f3c6 frag u_blade_tip 1008 vec3 1 0
U cb13f3c6 frag u_blade_cols 1020 float 1 0
U cb13f3c6 frag u_blade_tex_on 1024 float 1 0
U cb13f3c6 frag u_gk_seed 1040 vec4 1 0
U cb13f3c6 frag u_gk_look 1056 vec4 1 0
U cb13f3c6 frag u_gk_mat 1072 vec4 1 0
T cb13f3c6 u_arm 2
T cb13f3c6 u_blade_tex 3
T cb13f3c6 u_brdf 4
@ -1108,7 +1120,7 @@ T cb13f3c6 u_tp_nrm 15
T cb13f3c6 u_tp_page 16
T cb13f3c6 u_ts_height 17
P b8cff226 grass.vert model.frag #define FOLIAGE;#define BLADE;#define GBLADE;#define TILES;
B b8cff226 vert 0 4452
B b8cff226 vert 0 4544
U b8cff226 vert u_view 0 mat4 1 0
U b8cff226 vert u_proj 64 mat4 1 0
U b8cff226 vert u_vp 128 mat4 1 0
@ -1134,9 +1146,14 @@ U b8cff226 vert u_d0 4436 float 1 0
U b8cff226 vert u_radius 4440 float 1 0
U b8cff226 vert u_px 4444 float 1 0
U b8cff226 vert u_dbg 4448 int 1 0
U b8cff226 vert u_gk_tall 4464 vec4 1 0
U b8cff226 vert u_gk_grow 4480 vec4 1 0
U b8cff226 vert u_gk_clump 4496 vec4 1 0
U b8cff226 vert u_gk_ground 4512 vec4 1 0
U b8cff226 vert u_gk_ortho 4528 vec4 1 0
I b8cff226 a_pos 0 vec3
I b8cff226 a_uv 2 vec2
B b8cff226 frag 1 1028
B b8cff226 frag 1 1088
U b8cff226 frag u_cascade_vp 0 mat4 5 64
U b8cff226 frag u_cascade_split 320 float 5 16
U b8cff226 frag u_cascade_range 400 float 5 16
@ -1185,6 +1202,9 @@ U b8cff226 frag u_blade_base 992 vec3 1 0
U b8cff226 frag u_blade_tip 1008 vec3 1 0
U b8cff226 frag u_blade_cols 1020 float 1 0
U b8cff226 frag u_blade_tex_on 1024 float 1 0
U b8cff226 frag u_gk_seed 1040 vec4 1 0
U b8cff226 frag u_gk_look 1056 vec4 1 0
U b8cff226 frag u_gk_mat 1072 vec4 1 0
T b8cff226 u_arm 2
T b8cff226 u_blade_tex 3
T b8cff226 u_brdf 4
@ -2015,7 +2035,7 @@ I f4a2b586 a_pos 0 vec3
I f4a2b586 i_pos 3 vec4
I f4a2b586 a_nrm 1 vec3
I f4a2b586 a_uv 2 vec2
B f4a2b586 frag 1 1028
B f4a2b586 frag 1 1088
U f4a2b586 frag u_cascade_vp 0 mat4 5 64
U f4a2b586 frag u_cascade_split 320 float 5 16
U f4a2b586 frag u_cascade_range 400 float 5 16
@ -2064,6 +2084,9 @@ U f4a2b586 frag u_blade_base 992 vec3 1 0
U f4a2b586 frag u_blade_tip 1008 vec3 1 0
U f4a2b586 frag u_blade_cols 1020 float 1 0
U f4a2b586 frag u_blade_tex_on 1024 float 1 0
U f4a2b586 frag u_gk_seed 1040 vec4 1 0
U f4a2b586 frag u_gk_look 1056 vec4 1 0
U f4a2b586 frag u_gk_mat 1072 vec4 1 0
T f4a2b586 u_arm 2
T f4a2b586 u_blade_tex 3
T f4a2b586 u_brdf 4
@ -3909,7 +3932,7 @@ I b20fdcb5 i_ground 6 vec4
I b20fdcb5 a_pos 0 vec3
I b20fdcb5 i_tint 5 vec4
I b20fdcb5 a_uv 2 vec2
B b20fdcb5 frag 1 1028
B b20fdcb5 frag 1 1088
U b20fdcb5 frag u_cascade_vp 0 mat4 5 64
U b20fdcb5 frag u_cascade_split 320 float 5 16
U b20fdcb5 frag u_cascade_range 400 float 5 16
@ -3958,6 +3981,9 @@ U b20fdcb5 frag u_blade_base 992 vec3 1 0
U b20fdcb5 frag u_blade_tip 1008 vec3 1 0
U b20fdcb5 frag u_blade_cols 1020 float 1 0
U b20fdcb5 frag u_blade_tex_on 1024 float 1 0
U b20fdcb5 frag u_gk_seed 1040 vec4 1 0
U b20fdcb5 frag u_gk_look 1056 vec4 1 0
U b20fdcb5 frag u_gk_mat 1072 vec4 1 0
T b20fdcb5 u_arm 2
T b20fdcb5 u_blade_tex 3
T b20fdcb5 u_brdf 4