diff --git a/src/render/lighting/lighting.wgsl b/src/render/lighting/lighting.wgsl index 826a2a6..8d11f8a 100644 --- a/src/render/lighting/lighting.wgsl +++ b/src/render/lighting/lighting.wgsl @@ -18,8 +18,28 @@ struct AmbientLight2d { color: vec4 } -fn world_to_ndc(world_position: vec2, view_projection: mat4x4) -> vec2 { - return (view_projection * vec4(world_position, 0.0, 1.0)).xy; +fn world_to_ndc(world_position: vec2) -> vec2 { + return (view.clip_from_world * vec4(world_position, 0.0, 1.0)).xy; +} + +fn ndc_to_world(ndc_position: vec2) -> vec2 { + return (view.world_from_clip * vec4(ndc_position, 0.0, 1.0)).xy; +} + +fn ndc_to_uv(ndc: vec2) -> vec2 { + return ndc * vec2(0.5, -0.5) + vec2(0.5); +} + +fn frag_coord_to_uv(frag_coord: vec2) -> vec2 { + return (frag_coord - view.viewport.xy) / view.viewport.zw; +} + +fn frag_coord_to_ndc(frag_coord: vec2) -> vec2 { + return uv_to_ndc(frag_coord_to_uv(frag_coord.xy)); +} + +fn uv_to_ndc(uv: vec2) -> vec2 { + return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0); } fn ndc_to_screen(ndc: vec2, screen_size: vec2) -> vec2 { @@ -29,10 +49,9 @@ fn ndc_to_screen(ndc: vec2, screen_size: vec2) -> vec2 { fn world_to_screen( world_position: vec2, - screen_size: vec2, - view_projection: mat4x4 + screen_size: vec2 ) -> vec2 { - return ndc_to_screen(world_to_ndc(world_position, view_projection), screen_size); + return ndc_to_screen(world_to_ndc(world_position), screen_size); } fn scale_factor(view: View) -> f32 { @@ -63,8 +82,18 @@ var ambient_light: AmbientLight2d; var point_lights: array; #endif +@group(0) @binding(5) +var sdf_texture: texture_2d; + @fragment fn fragment(vo: FullscreenVertexOutput) -> @location(0) vec4 { + let current_position = ndc_to_world(frag_coord_to_ndc(vo.position.xy)); + + // Use the ambient texture if we're inside an occluder. + if (signed_distance(current_position) <= 0.0) { + return ambient_texture(vo); + } + // Setup aggregate color from light sources to multiply the main texture by. var light_color = vec3(1.0); @@ -84,8 +113,7 @@ fn fragment(vo: FullscreenVertexOutput) -> @location(0) vec4 { // Our point light position is still in world space. We need to convert // it to screen space in order to do things like compute distances (let // alone render it in the correct place). - let point_light_screen_center = - world_to_screen(point_light.center, view.viewport.zw, view.clip_from_world); + let point_light_screen_center = world_to_screen(point_light.center, view.viewport.zw); // Compute the distance between the current position and the light's center. // We multiply by the scale factor as otherwise our distance will always be @@ -97,22 +125,29 @@ fn fragment(vo: FullscreenVertexOutput) -> @location(0) vec4 { // of illumination. if distance < point_light.radius { - // Compute light color falloff (a value between 0.0 and 1.0). - let attenuation = attenuation( - distance, - point_light.radius, - point_light.intensity, - point_light.falloff - ); - - // Add in the color from the light, taking into account its attenuation. - light_color += point_light.color.rgb * attenuation; + // Check if the point light is occluded from the current position. + if (raymarch(current_position, point_light.center) > 0.0) { + + // Compute light color falloff (a value between 0.0 and 1.0). + let attenuation = attenuation( + distance, + point_light.radius, + point_light.intensity, + point_light.falloff + ); + + // Add in the color from the light, taking into account its attenuation. + light_color += point_light.color.rgb * attenuation; + } } } + return ambient_texture(vo) * vec4(light_color, 1.0); +} + +fn ambient_texture(vo: FullscreenVertexOutput) -> vec4 { return textureSample(screen_texture, texture_sampler, vo.uv) - * vec4(ambient_light.color.rgb, 1.0) - * vec4(light_color, 1.0); + * vec4(ambient_light.color.rgb, 1.0); } fn square(x: f32) -> f32 { @@ -129,3 +164,41 @@ fn attenuation(distance: f32, radius: f32, intensity: f32, falloff: f32) -> f32 let s2 = square(s); return intensity * square(1 - s2) / (1 + falloff * s2); } + +fn signed_distance(pos: vec2) -> f32 { + let uv = ndc_to_uv(world_to_ndc(pos)); + let dist = textureSample(sdf_texture, texture_sampler, uv).r; + return dist; +} + +fn distance_squared(a: vec2, b: vec2) -> f32 { + let c = a - b; + return dot(c, c); +} + +fn raymarch(ray_origin: vec2, ray_target: vec2) -> f32 { + let ray_direction = normalize(ray_target - ray_origin); + let stop_at = distance_squared(ray_origin, ray_target); + + var ray_progress: f32 = 0.0; + var pos = vec2(0.0); + + for (var i = 0; i < 32; i++) { + pos = ray_origin + ray_progress * ray_direction; + + if (ray_progress * ray_progress >= stop_at) { + // ray found target + return 1.0; + } + + let dist = signed_distance(pos); + + if dist <= 0.0 { + break; + } + + ray_progress += dist; + } + + return 0.0; +} diff --git a/src/render/lighting/node.rs b/src/render/lighting/node.rs index 76cd85c..c5d982f 100644 --- a/src/render/lighting/node.rs +++ b/src/render/lighting/node.rs @@ -124,6 +124,7 @@ impl ViewNode for LightingNode { view_uniform_binding, ambient_light_uniform, point_light_binding, + &aux_textures.sdf.default_view, )), ); diff --git a/src/render/lighting/pipeline.rs b/src/render/lighting/pipeline.rs index ad405b0..f7d9a72 100644 --- a/src/render/lighting/pipeline.rs +++ b/src/render/lighting/pipeline.rs @@ -122,6 +122,7 @@ impl FromWorld for LightingPipeline { uniform_buffer::(true), uniform_buffer::(true), GpuArrayBuffer::::binding_layout(render_device), + texture_2d(TextureSampleType::Float { filterable: true }), ), ), );