struct CameraUniforms {
view: mat4x4<f32>,
projection: mat4x4<f32>,
view_projection: mat4x4<f32>,
camera_position: vec4<f32>,
}
struct GrassUniforms {
time: f32,
wind_strength: f32,
wind_frequency: f32,
sss_intensity: f32,
wind_direction: vec2<f32>,
interaction_strength: f32,
_padding: f32,
sun_direction: vec3<f32>,
sun_intensity: f32,
sun_color: vec3<f32>,
ambient_intensity: f32,
}
struct GrassInstance {
position: vec3<f32>,
rotation: f32,
height: f32,
width: f32,
species_index: u32,
lod: u32,
base_color: vec4<f32>,
tip_color: vec4<f32>,
bend: vec2<f32>,
_padding: vec2<f32>,
}
struct GrassSpeciesData {
base_color: vec4<f32>,
tip_color: vec4<f32>,
sss_color: vec4<f32>,
sss_intensity: f32,
specular_power: f32,
specular_strength: f32,
blade_curvature: f32,
}
struct VertexInput {
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) world_position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) blade_height_factor: f32,
@location(3) base_color: vec4<f32>,
@location(4) tip_color: vec4<f32>,
@location(5) sss_color: vec3<f32>,
@location(6) sss_intensity: f32,
}
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
@group(0) @binding(1) var<uniform> grass_uniforms: GrassUniforms;
@group(0) @binding(2) var<storage, read> instances: array<GrassInstance>;
@group(0) @binding(3) var<storage, read> visible_indices: array<u32>;
@group(0) @binding(4) var<storage, read> species_data: array<GrassSpeciesData>;
const BLADE_VERTICES: u32 = 7u;
const PI: f32 = 3.14159265359;
fn blade_vertex_position(vertex_index: u32, height: f32, width: f32, curvature: f32) -> vec3<f32> {
let segment = vertex_index / 2u;
let side = f32(vertex_index % 2u) * 2.0 - 1.0;
let t = f32(segment) / 3.0;
let curve_offset = curvature * t * t;
let segment_width = width * (1.0 - t * 0.8);
var position: vec3<f32>;
position.x = side * segment_width * 0.5;
position.y = t * height;
position.z = curve_offset;
if vertex_index == 6u {
position.x = 0.0;
position.y = height;
position.z = curvature;
}
return position;
}
fn wind_displacement(world_pos: vec3<f32>, height_factor: f32, time: f32, strength: f32, frequency: f32, direction: vec2<f32>) -> vec3<f32> {
let base_freq = frequency * 0.5;
let gust_freq = frequency * 2.0;
let wave1 = sin(world_pos.x * base_freq + time * 1.5) * 0.5 + 0.5;
let wave2 = sin(world_pos.z * base_freq * 0.7 + time * 1.2) * 0.5 + 0.5;
let wave3 = sin((world_pos.x + world_pos.z) * gust_freq + time * 3.0) * 0.3;
let combined_wave = (wave1 * wave2 + wave3) * strength * height_factor * height_factor;
var displacement: vec3<f32>;
displacement.x = direction.x * combined_wave;
displacement.y = -combined_wave * 0.1;
displacement.z = direction.y * combined_wave;
return displacement;
}
fn rotation_matrix_y(angle: f32) -> mat3x3<f32> {
let c = cos(angle);
let s = sin(angle);
return mat3x3<f32>(
vec3<f32>(c, 0.0, s),
vec3<f32>(0.0, 1.0, 0.0),
vec3<f32>(-s, 0.0, c)
);
}
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let visible_index = visible_indices[input.instance_index];
let instance = instances[visible_index];
let species = species_data[instance.species_index];
let curvature = species.blade_curvature;
var local_pos = blade_vertex_position(input.vertex_index, instance.height, instance.width, curvature);
let height_factor = local_pos.y / instance.height;
let rot = rotation_matrix_y(instance.rotation);
local_pos = rot * local_pos;
let wind_disp = wind_displacement(
instance.position,
height_factor,
grass_uniforms.time,
grass_uniforms.wind_strength,
grass_uniforms.wind_frequency,
grass_uniforms.wind_direction
);
let bend_disp = vec3<f32>(
instance.bend.x * height_factor * height_factor * grass_uniforms.interaction_strength,
0.0,
instance.bend.y * height_factor * height_factor * grass_uniforms.interaction_strength
);
var world_pos = instance.position + local_pos + wind_disp + bend_disp;
let tangent = normalize(vec3<f32>(
rot[0][0],
0.0,
rot[2][0]
));
let bitangent = vec3<f32>(0.0, 1.0, 0.0);
var normal = cross(bitangent, tangent);
let wind_tilt = wind_disp.x * 0.5 + wind_disp.z * 0.5;
normal = normalize(normal + vec3<f32>(wind_tilt * 0.3, 0.0, wind_tilt * 0.3));
output.position = camera.view_projection * vec4<f32>(world_pos, 1.0);
output.world_position = world_pos;
output.normal = normal;
output.blade_height_factor = height_factor;
output.base_color = instance.base_color;
output.tip_color = instance.tip_color;
output.sss_color = species.sss_color.rgb;
output.sss_intensity = species.sss_intensity * grass_uniforms.sss_intensity;
return output;
}
fn kajiya_kay_specular(light_dir: vec3<f32>, view_dir: vec3<f32>, tangent: vec3<f32>, power: f32) -> f32 {
let half_vec = normalize(light_dir + view_dir);
let dot_th = dot(tangent, half_vec);
let sin_th = sqrt(max(0.0, 1.0 - dot_th * dot_th));
return pow(sin_th, power);
}
fn subsurface_scattering(light_dir: vec3<f32>, view_dir: vec3<f32>, normal: vec3<f32>, thickness: f32) -> f32 {
let back_light = dot(-light_dir, view_dir);
let sss_amount = saturate(back_light * 0.5 + 0.5);
let edge_factor = pow(1.0 - saturate(dot(normal, view_dir)), 2.0);
return sss_amount * thickness * (1.0 + edge_factor);
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let base_color = mix(input.base_color.rgb, input.tip_color.rgb, input.blade_height_factor);
let view_dir = normalize(camera.camera_position.xyz - input.world_position);
let light_dir = normalize(grass_uniforms.sun_direction);
let ndotl = dot(input.normal, light_dir);
let wrap_diffuse = (ndotl + 0.5) / 1.5;
let diffuse = max(0.0, wrap_diffuse);
let tangent = normalize(cross(input.normal, vec3<f32>(0.0, 1.0, 0.0)));
let specular = kajiya_kay_specular(light_dir, view_dir, tangent, 64.0) * 0.3;
let sss = subsurface_scattering(light_dir, view_dir, input.normal, input.blade_height_factor) * input.sss_intensity;
let sss_contribution = input.sss_color * sss * grass_uniforms.sun_color * grass_uniforms.sun_intensity;
var final_color = base_color * grass_uniforms.ambient_intensity;
final_color += base_color * diffuse * grass_uniforms.sun_color * grass_uniforms.sun_intensity;
final_color += specular * grass_uniforms.sun_color;
final_color += sss_contribution;
let distance_to_camera = length(camera.camera_position.xyz - input.world_position);
let distance_fade = 1.0 - smoothstep(180.0, 200.0, distance_to_camera);
let tip_fade = smoothstep(0.9, 1.0, input.blade_height_factor);
let alpha = (1.0 - tip_fade * 0.5) * distance_fade;
return vec4<f32>(final_color, alpha);
}