feat(render3d): mesh-shader grass
The chunked grass path draws each chunk as one mesh-shader dispatch when the setting asks and the card has VK_EXT_mesh_shader: work group y is a tile, x a batch of 16 of its blades, and a blade the placement, density, frustum, water or slope tests reject emits nothing - the instanced path still runs its eight vertices to a degenerate position. grass.mesh generates the same blades as grass.vert (grass_blade_mesh(4)'s rows, the same hashes, sway and lighting normal), capped at the instanced path's 65535 a tile. Vulkan: VK_EXT_mesh_shader with meshShader, and maintenance4 (glslang's mesh stages declare LocalSizeId); vkCmdDrawMeshTasksEXT looked up per device, as the Streamline interposer exports none; a *.mesh program's pipeline takes the mesh stage and no vertex input, its bindings the mesh stage bit. gpu_has_mesh, gpu_draw_mesh_tasks; r3d_mesh_grass and R3D_MESH_GRASS / R3D_NO_MESH. bin/ludic-dev rebuilt: the committed binary predated the shader tool's mesh support and compiled grass.mesh as a vertex stage. PC (RTX 3070 Ti): the camp matches the chunked path; validation only the no-window present-id message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6525c11b3c
commit
750d13779d
10 changed files with 334 additions and 13 deletions
167
packages/ludic.render3d/shaders/grass.mesh
Normal file
167
packages/ludic.render3d/shaders/grass.mesh
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Procedural ground-cover blades as a MESH shader (Vulkan, VK_EXT_mesh_shader): the same blades
|
||||
// grass.vert draws - the same places, density, thinning, culling, size, sway and lighting normal -
|
||||
// but a blade the tests reject emits nothing, where the instanced path still runs all eight of its
|
||||
// vertices to a degenerate position.
|
||||
//
|
||||
// One dispatch covers a chunk of tiles (grass.ludic): work group y is the tile's place in u_tiles,
|
||||
// work group x a batch of BLADES blade indices within that tile. Everything past the tile's own
|
||||
// count is skipped.
|
||||
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
|
||||
const int BLADES = 16; // blades an invocation may emit
|
||||
const int ROWS = 4; // grass_blade_mesh(4): two vertices a row, three quads
|
||||
layout(triangles, max_vertices = 128, max_primitives = 96) out;
|
||||
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_vp;
|
||||
uniform vec3 u_cam_pos;
|
||||
uniform float u_time;
|
||||
uniform sampler2D u_ts_height;
|
||||
uniform vec2 u_ts_origin;
|
||||
uniform float u_ts_half;
|
||||
uniform sampler2D u_ortho;
|
||||
uniform float u_ortho_on;
|
||||
uniform float u_lake_level;
|
||||
uniform float u_sea_level;
|
||||
uniform vec4 u_lake;
|
||||
uniform float u_snow_line;
|
||||
uniform float u_wind;
|
||||
uniform int u_tile_cells; // 16 m cells per tile side
|
||||
uniform vec4 u_tiles[256]; // per tile of the dispatch: corner x, corner z, indices per cell
|
||||
uniform float u_s0;
|
||||
uniform float u_d0;
|
||||
uniform float u_radius;
|
||||
uniform int u_dbg;
|
||||
out vec3 v_wpos[];
|
||||
out vec3 v_nrm[];
|
||||
out vec2 v_uv[];
|
||||
out float v_seed[];
|
||||
out vec2 v_rot[];
|
||||
out float v_hull[];
|
||||
|
||||
const float CELL = 16.0;
|
||||
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) {
|
||||
uint h = pcg(uint(cell.x + 32768) * 73856093u ^ uint(cell.y + 32768) * 19349663u ^ uint(j) * 83492791u ^ uint(k) * 2654435761u);
|
||||
return float(h) * (1.0 / 4294967295.0);
|
||||
}
|
||||
// no implicit level of detail outside a fragment shader: every lookup names level 0
|
||||
float heightSmooth(sampler2D tex, vec2 uv) {
|
||||
vec2 res = vec2(textureSize(tex, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (textureLod(tex, vec2(o0.x, o0.y), 0.0).r * s0.x + textureLod(tex, vec2(o1.x, o0.y), 0.0).r * s1.x) * s0.y
|
||||
+ (textureLod(tex, vec2(o0.x, o1.y), 0.0).r * s0.x + textureLod(tex, vec2(o1.x, o1.y), 0.0).r * s1.x) * s1.y;
|
||||
}
|
||||
|
||||
void main() {
|
||||
int ti = int(gl_WorkGroupID.y);
|
||||
vec2 tile = u_tiles[ti].xy;
|
||||
int per_cell = int(u_tiles[ti].z + 0.5);
|
||||
int total = min(per_cell * u_tile_cells * u_tile_cells, 65535); // the instanced path's cap on a tile
|
||||
int first = int(gl_WorkGroupID.x) * BLADES;
|
||||
int nv = 0;
|
||||
int np = 0;
|
||||
for (int b = 0; b < BLADES; b++) {
|
||||
int i = first + b;
|
||||
if (per_cell <= 0 || i >= total) break;
|
||||
int c = i / per_cell;
|
||||
int j = i - c * per_cell;
|
||||
vec2 cell = tile + vec2(float(c % u_tile_cells), float(c / u_tile_cells)) * CELL;
|
||||
vec2 cid = floor(cell / CELL + 0.5);
|
||||
ivec2 ci = ivec2(cid);
|
||||
float fj = float(j);
|
||||
vec2 hv = vec2(bladeHash(ci, j, 0), bladeHash(ci, j, 1));
|
||||
vec2 xz = cell + hv * CELL;
|
||||
float dist = length(xz - u_cam_pos.xz);
|
||||
if (dist >= u_radius) continue;
|
||||
float spacing = u_s0 * (1.0 + dist / u_d0);
|
||||
float count = CELL * CELL / (spacing * spacing) * (1.0 - smoothstep(u_radius * 0.7, u_radius, dist));
|
||||
if (fj >= count) continue;
|
||||
float life = 1.0 - smoothstep(0.8, 1.0, fj / max(count, 1.0));
|
||||
vec2 huv = (xz - u_ts_origin) / (2.0 * u_ts_half) + 0.5;
|
||||
if (huv.x < 0.0 || huv.x > 1.0 || huv.y < 0.0 || huv.y > 1.0) continue;
|
||||
vec4 ht = textureLod(u_ts_height, huv, 0.0);
|
||||
vec4 croot = u_vp * vec4(xz.x, ht.r, xz.y, 1.0);
|
||||
if (croot.w < -1.0 || abs(croot.x) > croot.w * 1.25 + 1.5 || abs(croot.y) > croot.w * 1.4 + 1.5) continue;
|
||||
vec3 gn = normalize(ht.gba);
|
||||
float h3 = bladeHash(ci, j, 2), h4 = bladeHash(ci, j, 3);
|
||||
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);
|
||||
if (u_ortho_on > 0.5) {
|
||||
vec3 oc = textureLod(u_ortho, huv, 1.5).rgb;
|
||||
ok *= 0.25 + 0.75 * smoothstep(0.0, 0.02, oc.g - max(oc.r, oc.b));
|
||||
}
|
||||
if (h4 > ok) continue;
|
||||
bool far = dist > 300.0;
|
||||
float h = far ? ht.r : heightSmooth(u_ts_height, huv);
|
||||
if (far) h += 0.03;
|
||||
if ((u_dbg & 1) != 0) h += 0.3;
|
||||
float seed = hv.x * 0.7 + hv.y * 0.3;
|
||||
float ang = hv.y * 6.2831853;
|
||||
float s = sin(ang), c_ = cos(ang);
|
||||
float grow = spacing / u_s0;
|
||||
float tall = mix(0.18, 0.42, h3) * mix(0.8, 1.2, hash1(cid * 0.1)) * (1.0 + 0.35 * smoothstep(1.0, 12.0, grow)) * life;
|
||||
float bw = 0.028 * mix(1.0, 0.45 * grow, smoothstep(1.0, 4.0, grow));
|
||||
if (far) { bw = max(bw, spacing * 0.35); tall = min(tall, spacing * 0.3); }
|
||||
float gust = sin(xz.x * 0.09 + u_time * 1.1) * 0.5 + sin(xz.y * 0.13 - u_time * 0.8 + xz.x * 0.05) * 0.5;
|
||||
float ph = u_time * 1.7 + seed * 6.2831 + xz.x * 0.05 + xz.y * 0.07;
|
||||
float sway = (sin(ph) * 0.6 + sin(ph * 2.3 + 1.0) * 0.4 + gust) * u_wind;
|
||||
// the ground's frame, shared by the blade's vertices
|
||||
vec3 up = vec3(0.0, 1.0, 0.0);
|
||||
vec3 k = cross(up, gn);
|
||||
float sk = length(k), ck = gn.y;
|
||||
bool tilt = sk > 1e-4;
|
||||
if (tilt) k /= sk;
|
||||
vec3 n = vec3(0.0, 0.3, 1.0);
|
||||
n = normalize(vec3(c_ * n.x + s * n.z, n.y, -s * n.x + c_ * n.z));
|
||||
if (tilt) n = normalize(n * ck + cross(k, n) * sk + k * dot(k, n) * (1.0 - ck));
|
||||
n = normalize(mix(n, gn, smoothstep(2.0, 12.0, dist)));
|
||||
float hull = (dist > 2.0 || far || (u_dbg & 2) != 0) ? -1.0 : 1.0;
|
||||
// the blade: grass_blade_mesh(4)'s vertices, placed as grass.vert places them
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
float t = float(r) / float(ROWS - 1);
|
||||
float taper = max(1.0 - t * t * sqrt(t), 0.12);
|
||||
float bend = t * t * 0.28;
|
||||
for (int sd = 0; sd < 2; sd++) {
|
||||
vec3 a_pos = vec3((float(sd) - 0.5) * taper, t, bend);
|
||||
vec2 a_uv = vec2(float(sd), t);
|
||||
vec3 p = vec3(a_pos.x * bw, a_pos.y * tall, a_pos.z * tall * (0.6 + 0.8 * h4));
|
||||
float hgt = max(p.y, 0.0);
|
||||
p.x += sway * hgt * hgt * 0.35;
|
||||
p.z += sway * hgt * hgt * 0.15 * cos(ph * 0.7);
|
||||
p = vec3(c_ * p.x + s * p.z, p.y, -s * p.x + c_ * p.z);
|
||||
if (tilt) p = p * ck + cross(k, p) * sk + k * dot(k, p) * (1.0 - ck);
|
||||
vec3 w = vec3(xz.x, h - 0.02, xz.y) + p;
|
||||
vec4 clip = u_proj * u_view * vec4(w, 1.0);
|
||||
clip.z = (clip.z + clip.w) * 0.5; // OpenGL's depth range to Vulkan's, as the vertex wrapper does
|
||||
int o = nv + r * 2 + sd;
|
||||
gl_MeshVerticesEXT[o].gl_Position = clip;
|
||||
v_wpos[o] = w;
|
||||
v_nrm[o] = n;
|
||||
v_uv[o] = far ? vec2(a_uv.x, 0.45 + 0.2 * a_uv.y) : a_uv;
|
||||
v_seed[o] = seed;
|
||||
v_rot[o] = vec2(s, c_);
|
||||
v_hull[o] = hull;
|
||||
}
|
||||
}
|
||||
for (int q = 0; q < ROWS - 1; q++) {
|
||||
uint bb = uint(nv + q * 2);
|
||||
gl_PrimitiveTriangleIndicesEXT[np] = uvec3(bb, bb + 1u, bb + 2u);
|
||||
gl_PrimitiveTriangleIndicesEXT[np + 1] = uvec3(bb + 1u, bb + 3u, bb + 2u);
|
||||
np += 2;
|
||||
}
|
||||
nv += ROWS * 2;
|
||||
}
|
||||
SetMeshOutputsEXT(uint(nv), uint(np));
|
||||
}
|
||||
BIN
packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv
Normal file
Binary file not shown.
|
|
@ -628,6 +628,73 @@ T a4683767 u_prefilter 8
|
|||
T a4683767 u_shadow 9
|
||||
T a4683767 u_tershadow 10
|
||||
T a4683767 u_ts_height 11
|
||||
P d48d6a48 grass.mesh model.frag #define FOLIAGE;#define BLADE;#define MESH;
|
||||
B d48d6a48 vert 0 4384
|
||||
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
|
||||
U d48d6a48 vert u_cam_pos 192 vec3 1 0
|
||||
U d48d6a48 vert u_time 204 float 1 0
|
||||
U d48d6a48 vert u_ts_origin 208 vec2 1 0
|
||||
U d48d6a48 vert u_ts_half 216 float 1 0
|
||||
U d48d6a48 vert u_ortho_on 220 float 1 0
|
||||
U d48d6a48 vert u_lake_level 224 float 1 0
|
||||
U d48d6a48 vert u_sea_level 228 float 1 0
|
||||
U d48d6a48 vert u_lake 240 vec4 1 0
|
||||
U d48d6a48 vert u_snow_line 256 float 1 0
|
||||
U d48d6a48 vert u_wind 260 float 1 0
|
||||
U d48d6a48 vert u_tile_cells 264 int 1 0
|
||||
U d48d6a48 vert u_tiles 272 vec4 256 16
|
||||
U d48d6a48 vert u_s0 4368 float 1 0
|
||||
U d48d6a48 vert u_d0 4372 float 1 0
|
||||
U d48d6a48 vert u_radius 4376 float 1 0
|
||||
U d48d6a48 vert u_dbg 4380 int 1 0
|
||||
B d48d6a48 frag 1 892
|
||||
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
|
||||
U d48d6a48 frag u_cascade_texel 480 float 5 16
|
||||
U d48d6a48 frag u_sun_dir 560 vec3 1 0
|
||||
U d48d6a48 frag u_sun_color 576 vec3 1 0
|
||||
U d48d6a48 frag u_cam_pos 592 vec3 1 0
|
||||
U d48d6a48 frag u_prefilter_levels 604 float 1 0
|
||||
U d48d6a48 frag u_fog_density 608 float 1 0
|
||||
U d48d6a48 frag u_fog_height_falloff 612 float 1 0
|
||||
U d48d6a48 frag u_fog_base 616 float 1 0
|
||||
U d48d6a48 frag u_clip_y 620 float 1 0
|
||||
U d48d6a48 frag u_spec_scale 624 float 1 0
|
||||
U d48d6a48 frag u_sky_rot 632 vec2 1 0
|
||||
U d48d6a48 frag u_ibl_scale 640 vec3 1 0
|
||||
U d48d6a48 frag u_daylight 652 float 1 0
|
||||
U d48d6a48 frag u_fire_pos 656 vec3 1 0
|
||||
U d48d6a48 frag u_fire_color 672 vec3 1 0
|
||||
U d48d6a48 frag u_hand_pos 688 vec3 1 0
|
||||
U d48d6a48 frag u_hand_color 704 vec3 1 0
|
||||
U d48d6a48 frag u_hand_dir 720 vec3 1 0
|
||||
U d48d6a48 frag u_hand_cone 732 float 1 0
|
||||
U d48d6a48 frag u_ts_origin 736 vec2 1 0
|
||||
U d48d6a48 frag u_ts_half 744 float 1 0
|
||||
U d48d6a48 frag u_ts_on 748 float 1 0
|
||||
U d48d6a48 frag u_force_cascade 752 int 1 0
|
||||
U d48d6a48 frag u_cloud_shadow 756 float 1 0
|
||||
U d48d6a48 frag u_time 760 float 1 0
|
||||
U d48d6a48 frag u_model_h 764 float 1 0
|
||||
U d48d6a48 frag u_view 768 mat4 1 0
|
||||
U d48d6a48 frag u_tint 832 vec3 1 0
|
||||
U d48d6a48 frag u_rough_scale 844 float 1 0
|
||||
U d48d6a48 frag u_emissive 848 float 1 0
|
||||
U d48d6a48 frag u_blade_base 864 vec3 1 0
|
||||
U d48d6a48 frag u_blade_tip 880 vec3 1 0
|
||||
T d48d6a48 u_arm 2
|
||||
T d48d6a48 u_brdf 3
|
||||
T d48d6a48 u_diff 4
|
||||
T d48d6a48 u_irradiance 5
|
||||
T d48d6a48 u_nrm 6
|
||||
T d48d6a48 u_ortho 7
|
||||
T d48d6a48 u_prefilter 8
|
||||
T d48d6a48 u_shadow 9
|
||||
T d48d6a48 u_tershadow 10
|
||||
T d48d6a48 u_ts_height 11
|
||||
P 7ed7de52 grass.vert model.frag #define FOLIAGE;#define BLADE;
|
||||
B 7ed7de52 vert 0 296
|
||||
U 7ed7de52 vert u_view 0 mat4 1 0
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ fullscreen.vert|ternormal.frag|
|
|||
fullscreen.vert|tershadow.frag|#define NOISE_ONLY;
|
||||
fullscreen.vert|tonemap.frag|
|
||||
fullscreen.vert|tonemap.frag|#define HDR10;
|
||||
grass.mesh|model.frag|#define FOLIAGE;#define BLADE;#define MESH;
|
||||
grass.vert|model.frag|#define FOLIAGE;#define BLADE;
|
||||
grass.vert|model.frag|#define FOLIAGE;#define BLADE;#define TILES;
|
||||
impostor.vert|impostor.frag|
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue