diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 5b2334f..865ab3d 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -182,6 +182,7 @@ async fn main() { color: Vec3::new(1.0, 0.0, 0.0), intensity: 1.0, range: 1.5, + //cutoff: math::Angle::Degrees(45.0), ..Default::default() }, Transform::new( diff --git a/lyra-game/src/render/camera.rs b/lyra-game/src/render/camera.rs index 3b5ab46..3c12dd4 100755 --- a/lyra-game/src/render/camera.rs +++ b/lyra-game/src/render/camera.rs @@ -44,6 +44,7 @@ pub struct CameraUniform { pub inverse_projection: glam::Mat4, /// The view projection matrix pub view_projection: glam::Mat4, + pub projection: glam::Mat4, /// The position of the camera pub position: glam::Vec3, pub tile_debug: u32, @@ -57,6 +58,7 @@ impl Default for CameraUniform { view: glam::Mat4::IDENTITY, inverse_projection: glam::Mat4::IDENTITY, view_projection: glam::Mat4::IDENTITY, + projection: glam::Mat4::IDENTITY, position: Default::default(), tile_debug: 0, //_padding: 0, @@ -65,11 +67,12 @@ impl Default for CameraUniform { } impl CameraUniform { - pub fn new(view: glam::Mat4, inverse_projection: glam::Mat4, view_projection: glam::Mat4, position: glam::Vec3) -> Self { + pub fn new(view: glam::Mat4, inverse_projection: glam::Mat4, view_projection: glam::Mat4, projection: glam::Mat4, position: glam::Vec3) -> Self { Self { view, inverse_projection, view_projection, + projection, position, tile_debug: 0 } @@ -129,6 +132,7 @@ impl RenderCamera { view, inverse_projection: proj.inverse(), view_projection: self.view_proj, + projection: proj, position, tile_debug: camera.debug as u32, } @@ -151,6 +155,7 @@ impl RenderCamera { view, inverse_projection: proj.inverse(), view_projection: self.view_proj, + projection: proj, position, tile_debug: camera.debug as u32, } diff --git a/lyra-game/src/render/shaders/base.wgsl b/lyra-game/src/render/shaders/base.wgsl index f91291e..d059aa0 100755 --- a/lyra-game/src/render/shaders/base.wgsl +++ b/lyra-game/src/render/shaders/base.wgsl @@ -6,6 +6,8 @@ const LIGHT_TY_DIRECTIONAL = 0u; const LIGHT_TY_POINT = 1u; const LIGHT_TY_SPOT = 2u; +const ALPHA_CUTOFF = 0.1; + struct VertexInput { @location(0) position: vec3, @location(1) tex_coords: vec2, @@ -23,6 +25,7 @@ struct CameraUniform { view: mat4x4, inverse_projection: mat4x4, view_projection: mat4x4, + projection: mat4x4, position: vec3, tile_debug: u32, }; @@ -132,13 +135,17 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return debug_grid(in); } - let tile_index = vec2(floor(in.clip_position.xy / 16.0)); - let tile: vec2 = textureLoad(t_light_grid, tile_index).xy; - let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); let specular_color: vec3 = textureSample(t_specular, s_specular, in.tex_coords).xyz; var light_res = vec3(0.0); + if (object_color.a < ALPHA_CUTOFF) { + discard; + } + + let tile_index = vec2(floor(in.clip_position.xy / 16.0)); + let tile: vec2 = textureLoad(t_light_grid, tile_index).xy; + let light_offset = tile.x; let light_count = tile.y; @@ -269,20 +276,19 @@ fn blinn_phong_spot_light(world_pos: vec3, world_norm: vec3, spot_ligh //// end of specular //// //// spot light soft edges //// - let theta = dot(light_dir, normalize(-spot_light.direction)); - let epsilon = spot_light.spot_cutoff - spot_light.spot_outer_cutoff; - let intensity = clamp((theta - spot_light.spot_outer_cutoff) / epsilon, 0.0, 1.0); - //diffuse_color *= intensity; - //specular_color *= intensity; + let min_cos = cos(spot_light.spot_cutoff); + let max_cos = lerp(min_cos, 1.0, 0.5); + let cos_angle = dot(spot_light.direction, -light_dir); + let cone = smoothstep(min_cos, max_cos, cos_angle); //// end of spot light soft edges //// //// spot light attenuation //// let distance = length(light_pos - world_pos); let attenuation = calc_attenuation(spot_light, distance); - ambient_color *= attenuation * intensity; - diffuse_color *= attenuation * intensity; - specular_color *= attenuation * intensity; + ambient_color *= attenuation * spot_light.intensity * cone; + diffuse_color *= attenuation * spot_light.intensity * cone; + specular_color *= attenuation * spot_light.intensity * cone; //// end of spot light attenuation //// @@ -291,4 +297,8 @@ fn blinn_phong_spot_light(world_pos: vec3, world_norm: vec3, spot_ligh fn calc_attenuation(light: Light, distance: f32) -> f32 { return 1.0 - smoothstep(light.range * light.smoothness, light.range, distance); +} + +fn lerp(start: f32, end: f32, alpha: f32) -> f32 { + return (start + (end - start) * alpha); } \ No newline at end of file diff --git a/lyra-game/src/render/shaders/light_cull.comp.wgsl b/lyra-game/src/render/shaders/light_cull.comp.wgsl index dfe5a7a..fd3552d 100644 --- a/lyra-game/src/render/shaders/light_cull.comp.wgsl +++ b/lyra-game/src/render/shaders/light_cull.comp.wgsl @@ -5,20 +5,25 @@ const LIGHT_TY_DIRECTIONAL = 0u; const LIGHT_TY_POINT = 1u; const LIGHT_TY_SPOT = 2u; +type vec2f = vec2; +type vec3f = vec3; +type vec4f = vec4; + struct CameraUniform { view: mat4x4, inverse_projection: mat4x4, - //projection: mat4x4, view_projection: mat4x4, - position: vec3, + projection: mat4x4, + position: vec3f, + tile_debug: u32, }; struct Light { - position: vec3, + position: vec3f, light_ty: u32, - direction: vec3, + direction: vec3f, enabled: u32, - color: vec3, + color: vec3f, range: f32, intensity: f32, @@ -34,16 +39,21 @@ struct Lights { }; struct Cone { - tip: vec3, + tip: vec3f, height: f32, - direction: vec3, + direction: vec3f, radius: f32, } +struct Plane { + normal: vec3f, + origin_distance: f32, +} + var wg_min_depth: atomic; var wg_max_depth: atomic; var wg_light_index_start: atomic; -var wg_frustum_planes: array, 6>; +var wg_frustum_planes: array; // index list of visible light sources for this tile var wg_visible_light_indices: array; @@ -107,41 +117,50 @@ fn cs_main( // Create the frustum planes that will be used for this time if (local_invocation_index == 0u) { - // Compute the 4 corner points on the far clipping plane to use as the frustum vertices. - var screen_space: array, 4>; + // this algorithm is adapted from Google's filament: + // https://github.com/google/filament/blob/3644e7f80827f1cd2caef4a21e410a2243eb6e84/filament/src/Froxelizer.cpp#L402C57-L402C73 + let tile_width_clip_space = f32(2u * BLOCK_SIZE) / f32(u_screen_size.x); + let tile_height_clip_space = f32(2u * BLOCK_SIZE) / f32(u_screen_size.y); - // top left point - var temp: vec2 = workgroup_id.xy * BLOCK_SIZE; - screen_space[0] = vec4(f32(temp.x), f32(temp.y), -1.0, 1.0); + let tr_projection = transpose(u_camera.projection); - // top right point - var temp2 = vec2(f32(workgroup_id.x) + 1.0, f32(workgroup_id.y)) * f32(BLOCK_SIZE); - screen_space[1] = vec4(temp2.x, temp2.y, -1.0, 1.0); - - // bottom left point - temp2 = vec2(f32(workgroup_id.x), f32(workgroup_id.y) + 1.0) * f32(BLOCK_SIZE); - screen_space[2] = vec4(temp2.x, temp2.y, -1.0, 1.0); - - // bottom right point - temp2 = vec2(f32(workgroup_id.x) + 1.0, f32(workgroup_id.y) + 1.0) * f32(BLOCK_SIZE); - screen_space[3] = vec4(temp2.x, temp2.y, -1.0, 1.0); + var planes: array; - // convert screenspace to view space - var view_space: array, 4>; - for (var i = 0u; i < 4u; i++) { - view_space[i] = screen_to_view(screen_space[i]).xyz; + // left plane + { + let x = (f32(workgroup_id.x) * tile_width_clip_space) - 1.0; + let p = tr_projection * vec4f(-1.0, 0.0, 0.0, x); + planes[0] = -vec4f(normalize(p.xyz), 0.0); } - // View space eye is always at the origin - let eye_pos = vec3(0.0, 0.0, 0.0); + // right plane + { + let x = (f32(workgroup_id.x + 1u) * tile_width_clip_space) - 1.0; + let p = tr_projection * vec4f(-1.0, 0.0, 0.0, x); + planes[1] = vec4f(normalize(p.xyz), 0.0); + } - wg_frustum_planes[0] = compute_plane(eye_pos, view_space[2], view_space[0]); // left plane - wg_frustum_planes[1] = compute_plane(eye_pos, view_space[1], view_space[3]); // right plane - wg_frustum_planes[2] = compute_plane(eye_pos, view_space[0], view_space[1]); // top plane - wg_frustum_planes[3] = compute_plane(eye_pos, view_space[3], view_space[2]); // bottom plane + // top plane + { + let y = (f32(workgroup_id.y) * tile_height_clip_space) - 1.0; + let p = tr_projection * vec4f(0.0, 1.0, 0.0, y); + planes[2] = -vec4f(normalize(p.xyz), 0.0); + } - wg_frustum_planes[4] = vec4(0.0, 0.0, -1.0, -min_depth); - wg_frustum_planes[5] = vec4(0.0, 0.0, 1.0, -max_depth); + // bottom plane + { + let y = (f32(workgroup_id.y + 1u) * tile_height_clip_space) - 1.0; + let p = tr_projection * vec4f(0.0, 1.0, 0.0, y); + planes[3] = vec4f(normalize(p.xyz), 0.0); + } + + wg_frustum_planes[0] = Plane(planes[0].xyz, planes[0].w); + wg_frustum_planes[1] = Plane(planes[1].xyz, planes[1].w); + wg_frustum_planes[2] = Plane(planes[2].xyz, planes[2].w); + wg_frustum_planes[3] = Plane(planes[3].xyz, planes[3].w); + + wg_frustum_planes[4] = Plane(vec3f(0.0, 0.0, -1.0), -min_depth); + wg_frustum_planes[5] = Plane(vec3f(0.0, 0.0, 1.0), -max_depth); } workgroupBarrier(); @@ -157,29 +176,27 @@ fn cs_main( let light = u_lights.data[light_index]; if (light.enabled == 1u) { - let position_vec4 = u_camera.view * vec4(light.position, 1.0); - let position = position_vec4.xyz; - let radius = light.range; + let position_vs = (u_camera.view * vec4f(light.position, 1.0)).xyz; if (light.light_ty == LIGHT_TY_DIRECTIONAL) { add_light(light_index); } else if (light.light_ty == LIGHT_TY_POINT - && sphere_inside_frustrum(wg_frustum_planes, position, radius)) { + && sphere_inside_frustrum(wg_frustum_planes, position_vs, light.range)) { // TODO: add the light to the transparent geometry list - if (!sphere_inside_plane(position, radius, wg_frustum_planes[4])) { + if (!sphere_inside_plane(position_vs, light.range, wg_frustum_planes[4])) { add_light(light_index); } } else if (light.light_ty == LIGHT_TY_SPOT) { - let dir_vs = u_camera.view * vec4(light.direction, 1.0); + let dir_vs = (u_camera.view * vec4f(light.direction, 1.0)).xyz; let cone_radius = tan(light.spot_cutoff) * light.range; - let cone = Cone(position, radius, dir_vs.xyz, cone_radius); + let cone = Cone(position_vs, light.range, dir_vs, cone_radius); if (cone_inside_frustum(cone, wg_frustum_planes)) { // TODO: add the light to the transparent geometry list + add_light(light_index); if (!cone_inside_plane(cone, wg_frustum_planes[4])) { - add_light(light_index); } } } @@ -220,7 +237,7 @@ fn add_light(light_index: u32) -> bool { return false; } -fn sphere_inside_frustrum(frustum: array, 6>, sphere_origin: vec3, radius: f32) -> bool { +fn sphere_inside_frustrum(frustum: array, sphere_origin: vec3f, radius: f32) -> bool { // to be able to index this array with a non-const value, // it must be defined as a var var frustum_v = frustum; @@ -239,11 +256,11 @@ fn sphere_inside_frustrum(frustum: array, 6>, sphere_origin: vec3 /// /// Source: Real-time collision detection, Christer Ericson (2005) /// (https://www.3dgep.com/forward-plus/#light-culling-compute-shader) -fn sphere_inside_plane(sphere_origin: vec3, radius: f32, plane: vec4) -> bool { - return dot(plane.xyz, sphere_origin) - plane.w < -radius; +fn sphere_inside_plane(sphere_origin: vec3f, radius: f32, plane: Plane) -> bool { + return dot(plane.normal, sphere_origin) - plane.origin_distance < -radius; } -fn clip_to_view(clip: vec4) -> vec4 { +fn clip_to_view(clip: vec4f) -> vec4f { // view space position var view = u_camera.inverse_projection * clip; @@ -251,12 +268,12 @@ fn clip_to_view(clip: vec4) -> vec4 { return view / view.w; } -fn screen_to_view(screen: vec4) -> vec4 { +fn screen_to_view(screen: vec4f) -> vec4f { // convert to normalized texture coordinates let tex_coord = screen.xy / vec2(u_screen_size); // convert to clip space - let clip = vec4( vec2(tex_coord.x, 1.0 - tex_coord.y) * 2.0 - 1.0, screen.z, screen.w); + let clip = vec4f( vec2(tex_coord.x, 1.0 - tex_coord.y) * 2.0 - 1.0, screen.z, screen.w); return clip_to_view(clip); } @@ -264,52 +281,46 @@ fn screen_to_view(screen: vec4) -> vec4 { /// Compute a plane from 3 noncollinear points that form a triangle. /// This equation assumes a right-handed (counter-clockwise winding order) /// coordinate system to determine the direction of the plane normal. -fn compute_plane(p0: vec3, p1: vec3, p2: vec3) -> vec4 { +fn compute_plane(p0: vec3f, p1: vec3f, p2: vec3f) -> Plane { let v0 = p1 - p0; let v2 = p2 - p0; - var plane = vec4(normalize(cross(v0, v2)), 0.0); + let normal = vec4f(normalize(cross(v0, v2)), 0.0); // find the distance to the origin - plane.w = dot(plane.xyz, p0); + let distance = dot(normal.xyz, p0); - return plane; + return Plane(normal.xyz, distance); } -fn point_inside_plane(point: vec3, plane: vec4) -> bool { - return dot(plane.xyz, point) - plane.w < 0.0; +fn point_inside_plane(point: vec3f, plane: Plane) -> bool { + return dot(plane.normal, point) + plane.origin_distance < 0.0; +} + +fn point_intersect_plane(point: vec3f, plane: Plane) -> f32 { + return dot(plane.normal, point) + plane.origin_distance; } /// Check to see if a cone if fully behind (inside the negative halfspace of) a plane. /// /// Source: Real-time collision detection, Christer Ericson (2005) /// (https://www.3dgep.com/forward-plus/#light-culling-compute-shader) -fn cone_inside_plane(cone: Cone, plane: vec4) -> bool { - // Compute the farthest point on the end of the cone to the positive space of the plane. - let m = cross(cross(plane.xyz, cone.direction), cone.direction); - let farthest = cone.tip + cone.direction * cone.height - m * cone.radius; +fn cone_inside_plane(cone: Cone, plane: Plane) -> bool { + let dir = cone.direction; + let furthest_direction = cross(cross(plane.normal, dir), dir); + let furthest = cone.tip + dir * cone.height - furthest_direction * cone.radius; // The cone is in the negative halfspace of the plane if the tip of the cone, // and the farthest point on the end of the cone are inside the negative halfspace // of the plane. - return point_inside_plane(cone.tip, plane) && point_inside_plane(farthest, plane); + return point_inside_plane(cone.tip, plane) && point_inside_plane(furthest, plane); } -fn cone_inside_frustum(cone: Cone, frustum: array, 6>) -> bool { - //let near_plane = frustum[4]; - //let far_plane = frustum[5]; - - // check near and far clipping planes first - //if (cone_inside_plane(cone, near_plane) || cone_inside_plane(cone, far_plane)) { - // return false; - //} - - // to be able to index this array with a non-const value, - // it must be defined as a var - var frustum_v = frustum; - +fn cone_inside_frustum(cone: Cone, frustum: array) -> bool { + var frustum = frustum; for (var i = 0u; i < 4u; i++) { - if (cone_inside_plane(cone, frustum_v[i])) { + // TODO: better cone checking + if (sphere_inside_plane(cone.tip, cone.radius, frustum[i])) { return false; } }