@group(0) @binding(0) var<uniform> uniforms: GrassUniforms;
@group(0) @binding(1) var<storage, read> types: array<GrassType>;
@group(0) @binding(2) var<storage, read> blades: array<GrassBlade>;
@group(0) @binding(3) var<storage, read> far_tiles: array<GrassTileEntry>;
@group(0) @binding(4) var height_texture: texture_2d_array<f32>;
struct FarVarying {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_xz: vec2<f32>,
};
@vertex
fn vs_far(
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32,
) -> FarVarying {
var corners = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
);
let entry = far_tiles[min(instance_index, GRASS_MAX_FAR_TILES - 1u)].data;
let origin = vec2<f32>(entry.xy) * GRASS_TILE_SIZE;
let cell_index = vertex_index / 6u;
let cell = vec2<f32>(
f32(cell_index % GRASS_FAR_TILE_DIVISIONS),
f32(cell_index / GRASS_FAR_TILE_DIVISIONS),
);
let corner = corners[vertex_index % 6u];
let step = GRASS_FAR_TILE_SPAN / f32(GRASS_FAR_TILE_DIVISIONS);
let world_xz = origin + (cell + corner) * step;
let ground = uniforms.radii.w
+ grass_sample_height(
height_texture,
world_xz,
uniforms.height_map,
uniforms.height_bounds,
distance(world_xz, uniforms.camera_position.xz),
);
let world = vec3<f32>(world_xz.x, ground + 0.02, world_xz.y);
var output: FarVarying;
output.clip_position = uniforms.view_projection * vec4<f32>(world, 1.0);
output.world_xz = world_xz;
return output;
}
@fragment
fn fs_far(input: FarVarying) -> @location(0) vec4<f32> {
let camera_xz = uniforms.camera_position.xz;
let view_distance = distance(input.world_xz, camera_xz);
if view_distance > uniforms.radii.z {
discard;
}
let type_count = uniforms.counts.x;
let dither_cell = vec2<i32>(floor(input.world_xz * 6.0));
let dither = grass_hash01(grass_hash_tile(dither_cell, 53u));
let type_index = grass_select_type(input.world_xz, uniforms.params.x, type_count, dither);
let blade_type = types[type_index];
let clump = grass_voronoi(input.world_xz, uniforms.params.y * 2.0, 23u);
let understory = blade_type.color_base.rgb * 0.55;
let canopy = mix(blade_type.color_base.rgb, blade_type.color_tip.rgb, 0.55);
let canopy_blend = smoothstep(uniforms.radii.y * 0.4, uniforms.radii.y, view_distance);
var albedo = mix(understory, canopy, canopy_blend);
let clump_strength = 1.0 - smoothstep(150.0, 400.0, view_distance);
albedo *= 1.0 + (0.2 * clump.cell_value - 0.1) * clump_strength;
let shimmer = grass_wind_sample(input.world_xz, uniforms.params.w, uniforms.wind);
albedo *= 1.0 + (0.16 * shimmer - 0.08) * canopy_blend;
let diffuse_term = max(uniforms.sun_direction.y, 0.0);
let color = albedo * (uniforms.ambient.rgb + uniforms.sun_color.rgb * diffuse_term / GRASS_PI) * 0.8;
return vec4<f32>(color, 1.0);
}