From f3f6f34e4280bf8d7b49d9527d6495c64d6fb02b Mon Sep 17 00:00:00 2001 From: James Gayfer <10660608+jgayfer@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:44:26 -0700 Subject: [PATCH] Use light map Now we're using our light map, blended with our main texture. --- src/render/lighting/light_map.wgsl | 2 + src/render/lighting/lighting.wgsl | 197 +---------------------------- src/render/lighting/node.rs | 24 +--- src/render/lighting/pipeline.rs | 5 +- 4 files changed, 10 insertions(+), 218 deletions(-) diff --git a/src/render/lighting/light_map.wgsl b/src/render/lighting/light_map.wgsl index 1d49eb8..81fd5a0 100644 --- a/src/render/lighting/light_map.wgsl +++ b/src/render/lighting/light_map.wgsl @@ -102,6 +102,8 @@ fn square(x: f32) -> f32 { return x * x; } +// Compute light attenutation. +// See https://lisyarus.github.io/blog/posts/point-light-attenuation.html fn attenuation(light: PointLight2d, dist: f32) -> f32 { let s = dist / light.radius; if s > 1.0 { diff --git a/src/render/lighting/lighting.wgsl b/src/render/lighting/lighting.wgsl index 8d11f8a..8a0edc2 100644 --- a/src/render/lighting/lighting.wgsl +++ b/src/render/lighting/lighting.wgsl @@ -1,204 +1,17 @@ #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput -#import bevy_render::view::View - -// We're currently only using a single uniform binding for point lights in -// WebGL2, which is limited to 4kb in BatchedUniformBuffer, so we need to -// ensure our point lights can fit in 4kb. -const MAX_POINT_LIGHTS: u32 = 82u; - -struct PointLight2d { - center: vec2f, - radius: f32, - color: vec4, - intensity: f32, - falloff: f32 -} - -struct AmbientLight2d { - color: vec4 -} - -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 { - let screen_position: vec2 = (ndc + 1.0) * 0.5 * screen_size; - return vec2(screen_position.x, (screen_size.y - screen_position.y)); -} - -fn world_to_screen( - world_position: vec2, - screen_size: vec2 -) -> vec2 { - return ndc_to_screen(world_to_ndc(world_position), screen_size); -} - -fn scale_factor(view: View) -> f32 { - let screen_size = - 2.0 * vec2f(view.view_from_clip[0][0], view.view_from_clip[1][1]); - return screen_size.y / view.viewport.w; -} @group(0) @binding(0) var screen_texture: texture_2d; @group(0) @binding(1) -var texture_sampler: sampler; +var light_map_texture: texture_2d; @group(0) @binding(2) -var view: View; - -@group(0) @binding(3) -var ambient_light: AmbientLight2d; - -// WebGL2 does not support storage buffers, so we fall back to a fixed length -// array in a uniform buffer. -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6 - @group(0) @binding(4) - var point_lights: array; -#else - @group(0) @binding(4) - var point_lights: array; -#endif - -@group(0) @binding(5) -var sdf_texture: texture_2d; +var texture_sampler: sampler; @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); - - // WebGL2 does not support storage buffers (or runtime sized arrays), so we - // need to use a fixed number of point lights. -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6 - let point_light_count = arrayLength(&point_lights); -#else - let point_light_count = MAX_POINT_LIGHTS; -#endif - - // For each light, determine its illumination if we're within range of it. - for (var i = 0u; i < point_light_count; i++) { - - let point_light = point_lights[i]; - - // 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); - - // 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 - // represented in actual pixels. - let distance = - distance(point_light_screen_center, vo.position.xy) * scale_factor(view); - - // If we're within the light's radius, it should provide some level - // of illumination. - if distance < point_light.radius { - - // 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); -} - -fn square(x: f32) -> f32 { - return x * x; -} - -// Compute light attenutation. -// See https://lisyarus.github.io/blog/posts/point-light-attenuation.html -fn attenuation(distance: f32, radius: f32, intensity: f32, falloff: f32) -> f32 { - let s = distance / radius; - if (s > 1.0) { - return 0.0; - } - 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; + let light_frag = textureSample(light_map_texture, texture_sampler, vo.uv); + let scene_frag = textureSample(screen_texture, texture_sampler, vo.uv); + return scene_frag * light_frag; } diff --git a/src/render/lighting/node.rs b/src/render/lighting/node.rs index 6e20f98..df90728 100644 --- a/src/render/lighting/node.rs +++ b/src/render/lighting/node.rs @@ -165,7 +165,6 @@ impl ViewNode for LightingNode { drop(light_map_pass); - // Main pass (should be replaced by lighting, blur and post process) let post_process = view_target.post_process_write(); let bind_group = render_context.render_device().create_bind_group( @@ -173,11 +172,8 @@ impl ViewNode for LightingNode { &pipeline.layout, &BindGroupEntries::sequential(( post_process.source, + &aux_textures.light_map.default_view, &pipeline.sampler, - view_uniform_binding, - ambient_light_uniform, - point_light_binding, - &aux_textures.sdf.default_view, )), ); @@ -193,24 +189,8 @@ impl ViewNode for LightingNode { occlusion_query_set: None, }); - let mut dynamic_offsets: SmallVec<[u32; 3]> = - smallvec![view_offset.offset, ambient_index.index()]; - - // Storage buffers aren't available in WebGL2. We fall back to a - // dynamic uniform buffer, and therefore need to provide the offset. - // We're providing a value of 0 here as we're limiting the number of - // point lights to only those that can reasonably fit in a single binding. - if world - .resource::() - .limits() - .max_storage_buffers_per_shader_stage - == 0 - { - dynamic_offsets.push(0); - } - render_pass.set_render_pipeline(lighting_pipeline); - render_pass.set_bind_group(0, &bind_group, &dynamic_offsets); + render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); Ok(()) diff --git a/src/render/lighting/pipeline.rs b/src/render/lighting/pipeline.rs index 7387b6b..10c3040 100644 --- a/src/render/lighting/pipeline.rs +++ b/src/render/lighting/pipeline.rs @@ -147,11 +147,8 @@ impl FromWorld for LightingPipeline { ShaderStages::FRAGMENT, ( texture_2d(TextureSampleType::Float { filterable: true }), - sampler(SamplerBindingType::Filtering), - uniform_buffer::(true), - uniform_buffer::(true), - GpuArrayBuffer::::binding_layout(render_device), texture_2d(TextureSampleType::Float { filterable: true }), + sampler(SamplerBindingType::Filtering), ), ), );