render: fix spot light culling
This commit is contained in:
parent
e2844a11a6
commit
0f11fe2e6d
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
|
@ -23,6 +25,7 @@ struct CameraUniform {
|
|||
view: mat4x4<f32>,
|
||||
inverse_projection: mat4x4<f32>,
|
||||
view_projection: mat4x4<f32>,
|
||||
projection: mat4x4<f32>,
|
||||
position: vec3<f32>,
|
||||
tile_debug: u32,
|
||||
};
|
||||
|
@ -132,13 +135,17 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
return debug_grid(in);
|
||||
}
|
||||
|
||||
let tile_index = vec2<u32>(floor(in.clip_position.xy / 16.0));
|
||||
let tile: vec2<u32> = textureLoad(t_light_grid, tile_index).xy;
|
||||
|
||||
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
let specular_color: vec3<f32> = textureSample(t_specular, s_specular, in.tex_coords).xyz;
|
||||
var light_res = vec3<f32>(0.0);
|
||||
|
||||
if (object_color.a < ALPHA_CUTOFF) {
|
||||
discard;
|
||||
}
|
||||
|
||||
let tile_index = vec2<u32>(floor(in.clip_position.xy / 16.0));
|
||||
let tile: vec2<u32> = 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<f32>, world_norm: vec3<f32>, 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<f32>, world_norm: vec3<f32>, 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);
|
||||
}
|
|
@ -5,20 +5,25 @@ const LIGHT_TY_DIRECTIONAL = 0u;
|
|||
const LIGHT_TY_POINT = 1u;
|
||||
const LIGHT_TY_SPOT = 2u;
|
||||
|
||||
type vec2f = vec2<f32>;
|
||||
type vec3f = vec3<f32>;
|
||||
type vec4f = vec4<f32>;
|
||||
|
||||
struct CameraUniform {
|
||||
view: mat4x4<f32>,
|
||||
inverse_projection: mat4x4<f32>,
|
||||
//projection: mat4x4<f32>,
|
||||
view_projection: mat4x4<f32>,
|
||||
position: vec3<f32>,
|
||||
projection: mat4x4<f32>,
|
||||
position: vec3f,
|
||||
tile_debug: u32,
|
||||
};
|
||||
|
||||
struct Light {
|
||||
position: vec3<f32>,
|
||||
position: vec3f,
|
||||
light_ty: u32,
|
||||
direction: vec3<f32>,
|
||||
direction: vec3f,
|
||||
enabled: u32,
|
||||
color: vec3<f32>,
|
||||
color: vec3f,
|
||||
|
||||
range: f32,
|
||||
intensity: f32,
|
||||
|
@ -34,16 +39,21 @@ struct Lights {
|
|||
};
|
||||
|
||||
struct Cone {
|
||||
tip: vec3<f32>,
|
||||
tip: vec3f,
|
||||
height: f32,
|
||||
direction: vec3<f32>,
|
||||
direction: vec3f,
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
struct Plane {
|
||||
normal: vec3f,
|
||||
origin_distance: f32,
|
||||
}
|
||||
|
||||
var<workgroup> wg_min_depth: atomic<u32>;
|
||||
var<workgroup> wg_max_depth: atomic<u32>;
|
||||
var<workgroup> wg_light_index_start: atomic<u32>;
|
||||
var<workgroup> wg_frustum_planes: array<vec4<f32>, 6>;
|
||||
var<workgroup> wg_frustum_planes: array<Plane, 6>;
|
||||
|
||||
// index list of visible light sources for this tile
|
||||
var<workgroup> wg_visible_light_indices: array<u32, MAX_TILE_VISIBLE_LIGHTS>;
|
||||
|
@ -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<vec4<f32>, 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<u32> = workgroup_id.xy * BLOCK_SIZE;
|
||||
screen_space[0] = vec4<f32>(f32(temp.x), f32(temp.y), -1.0, 1.0);
|
||||
let tr_projection = transpose(u_camera.projection);
|
||||
|
||||
// top right point
|
||||
var temp2 = vec2<f32>(f32(workgroup_id.x) + 1.0, f32(workgroup_id.y)) * f32(BLOCK_SIZE);
|
||||
screen_space[1] = vec4<f32>(temp2.x, temp2.y, -1.0, 1.0);
|
||||
|
||||
// bottom left point
|
||||
temp2 = vec2<f32>(f32(workgroup_id.x), f32(workgroup_id.y) + 1.0) * f32(BLOCK_SIZE);
|
||||
screen_space[2] = vec4<f32>(temp2.x, temp2.y, -1.0, 1.0);
|
||||
|
||||
// bottom right point
|
||||
temp2 = vec2<f32>(f32(workgroup_id.x) + 1.0, f32(workgroup_id.y) + 1.0) * f32(BLOCK_SIZE);
|
||||
screen_space[3] = vec4<f32>(temp2.x, temp2.y, -1.0, 1.0);
|
||||
var planes: array<vec4f, 4>;
|
||||
|
||||
// convert screenspace to view space
|
||||
var view_space: array<vec3<f32>, 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<f32>(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<f32>(0.0, 0.0, -1.0, -min_depth);
|
||||
wg_frustum_planes[5] = vec4<f32>(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<f32>(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<f32>(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<vec4<f32>, 6>, sphere_origin: vec3<f32>, radius: f32) -> bool {
|
||||
fn sphere_inside_frustrum(frustum: array<Plane, 6>, 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<vec4<f32>, 6>, sphere_origin: vec3<f32>
|
|||
///
|
||||
/// 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<f32>, radius: f32, plane: vec4<f32>) -> 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<f32>) -> vec4<f32> {
|
||||
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<f32>) -> vec4<f32> {
|
|||
return view / view.w;
|
||||
}
|
||||
|
||||
fn screen_to_view(screen: vec4<f32>) -> vec4<f32> {
|
||||
fn screen_to_view(screen: vec4f) -> vec4f {
|
||||
// convert to normalized texture coordinates
|
||||
let tex_coord = screen.xy / vec2<f32>(u_screen_size);
|
||||
|
||||
// convert to clip space
|
||||
let clip = vec4<f32>( vec2<f32>(tex_coord.x, 1.0 - tex_coord.y) * 2.0 - 1.0, screen.z, screen.w);
|
||||
let clip = vec4f( vec2<f32>(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<f32>) -> vec4<f32> {
|
|||
/// 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<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec4<f32> {
|
||||
fn compute_plane(p0: vec3f, p1: vec3f, p2: vec3f) -> Plane {
|
||||
let v0 = p1 - p0;
|
||||
let v2 = p2 - p0;
|
||||
|
||||
var plane = vec4<f32>(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<f32>, plane: vec4<f32>) -> 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<f32>) -> 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<vec4<f32>, 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<Plane, 6>) -> 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue