struct WaterMaterial {
base_color: vec4<f32>,
water_color: vec4<f32>,
wave_height: f32,
choppy: f32,
speed: f32,
freq: f32,
specular_strength: f32,
fresnel_power: f32,
volume_shape: u32,
volume_flow_type: u32,
volume_size: vec3<f32>,
is_volumetric: u32,
flow_direction: vec2<f32>,
flow_strength: f32,
_flow_padding: f32,
};
struct WaterInstance {
model_0: vec4<f32>,
model_1: vec4<f32>,
model_2: vec4<f32>,
model_3: vec4<f32>,
material_index: u32,
_pad0: u32,
_pad1: u32,
_pad2: u32,
};
struct MeshBounds {
center: vec3<f32>,
radius: f32,
};
struct DrawIndexedIndirect {
index_count: u32,
instance_count: atomic<u32>,
first_index: u32,
base_vertex: i32,
first_instance: u32,
};
struct CullingUniforms {
frustum_planes: array<vec4<f32>, 6>,
object_count: u32,
_pad0: u32,
_pad1: u32,
_pad2: u32,
};
struct WaterObjectData {
instance_index: u32,
mesh_id: u32,
batch_id: u32,
_padding: u32,
};
@group(0) @binding(0)
var<storage, read> instances: array<WaterInstance>;
@group(0) @binding(1)
var<uniform> culling: CullingUniforms;
@group(0) @binding(2)
var<storage, read> mesh_bounds: array<MeshBounds>;
@group(0) @binding(3)
var<storage, read> object_data: array<WaterObjectData>;
@group(0) @binding(4)
var<storage, read_write> indirect_commands: array<DrawIndexedIndirect>;
@group(0) @binding(5)
var<storage, read_write> visible_indices: array<u32>;
@group(0) @binding(6)
var<storage, read> materials: array<WaterMaterial>;
fn sphere_in_frustum(center: vec3<f32>, radius: f32) -> bool {
for (var index = 0u; index < 6u; index++) {
let plane = culling.frustum_planes[index];
let distance = dot(plane.xyz, center) + plane.w;
if distance < -radius {
return false;
}
}
return true;
}
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let object_index = global_id.x;
if object_index >= culling.object_count {
return;
}
let object = object_data[object_index];
let instance = instances[object.instance_index];
let bounds = mesh_bounds[object.mesh_id];
let material = materials[instance.material_index];
let model = mat4x4<f32>(
instance.model_0,
instance.model_1,
instance.model_2,
instance.model_3
);
let scale_x = length(vec3<f32>(model[0][0], model[0][1], model[0][2]));
let scale_y = length(vec3<f32>(model[1][0], model[1][1], model[1][2]));
let scale_z = length(vec3<f32>(model[2][0], model[2][1], model[2][2]));
let max_scale = max(max(scale_x, scale_y), scale_z);
var visible = true;
if material.is_volumetric == 0u {
let local_center = vec4<f32>(bounds.center, 1.0);
let world_center = model * local_center;
let world_pos = world_center.xyz;
let bounding_radius = max_scale * bounds.radius;
visible = sphere_in_frustum(world_pos, bounding_radius);
} else {
let half_size = material.volume_size * 0.5;
let volume_radius = length(half_size);
let world_pos = vec3<f32>(model[3][0], model[3][1], model[3][2]);
let bounding_radius = max_scale * volume_radius;
visible = sphere_in_frustum(world_pos, bounding_radius);
}
if visible {
let batch_id = object.batch_id;
let write_index = atomicAdd(&indirect_commands[batch_id].instance_count, 1u);
let first_instance = indirect_commands[batch_id].first_instance;
visible_indices[first_instance + write_index] = object.instance_index;
}
}