Skip to content

Commit

Permalink
Add isolated light map pass
Browse files Browse the repository at this point in the history
The resulting texture isn't used yet, but it's being written to.
  • Loading branch information
jgayfer committed Jul 31, 2024
1 parent 3beedcb commit 631d65b
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 49 deletions.
14 changes: 11 additions & 3 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use crate::{
ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d,
},
lighting::{
prepare_lighting_auxiliary_textures, prepare_lighting_pipelines, LightingNode,
LightingPass, LightingPipeline, SdfPipeline, LIGHTING_SHADER, SDF_SHADER, TYPES_SHADER,
prepare_lighting_auxiliary_textures, prepare_lighting_pipelines, LightMapPipeline,
LightingNode, LightingPass, LightingPipeline, SdfPipeline, LIGHTING_SHADER,
LIGHT_MAP_SHADER, SDF_SHADER, TYPES_SHADER,
},
},
};
Expand Down Expand Up @@ -51,6 +52,12 @@ impl Plugin for Light2dPlugin {
"render/lighting/lighting.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
LIGHT_MAP_SHADER,
"render/lighting/light_map.wgsl",
Shader::from_wgsl
);

app.add_plugins((
UniformComponentPlugin::<ExtractedAmbientLight2d>::default(),
Expand Down Expand Up @@ -98,6 +105,7 @@ impl Plugin for Light2dPlugin {

render_app
.init_resource::<LightingPipeline>()
.init_resource::<SdfPipeline>();
.init_resource::<SdfPipeline>()
.init_resource::<LightMapPipeline>();
}
}
152 changes: 152 additions & 0 deletions src/render/lighting/light_map.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
#import bevy_render::view::View

fn world_to_ndc(world_position: vec2<f32>) -> vec2<f32> {
return (view.clip_from_world * vec4(world_position, 0.0, 1.0)).xy;
}

fn ndc_to_world(ndc_position: vec2<f32>) -> vec2<f32> {
return (view.world_from_clip * vec4(ndc_position, 0.0, 1.0)).xy;
}

fn frag_coord_to_uv(frag_coord: vec2<f32>) -> vec2<f32> {
return (frag_coord - view.viewport.xy) / view.viewport.zw;
}

fn frag_coord_to_ndc(frag_coord: vec2<f32>) -> vec2<f32> {
return uv_to_ndc(frag_coord_to_uv(frag_coord.xy));
}

fn uv_to_ndc(uv: vec2<f32>) -> vec2<f32> {
return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
}

fn ndc_to_uv(ndc: vec2<f32>) -> vec2<f32> {
return ndc * vec2(0.5, -0.5) + vec2(0.5);
}

// 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<f32>,
intensity: f32,
falloff: f32
}

struct AmbientLight2d {
color: vec4<f32>
}

@group(0) @binding(0)
var<uniform> view: View;

@group(0) @binding(1)
var<uniform> 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(2)
var<storage> point_lights: array<PointLight2d>;
#else
@group(0) @binding(2)
var<uniform> point_lights: array<PointLight2d, MAX_POINT_LIGHTS>;
#endif

@group(0) @binding(3)
var sdf: texture_2d<f32>;

@group(0) @binding(4)
var sdf_sampler: sampler;

@fragment
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let pos = ndc_to_world(frag_coord_to_ndc(in.position.xy));

var lighting_color = ambient_light.color;

if get_distance(pos) <= 0.0 {
return lighting_color;
}

// 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 (var i = 0u; i < point_light_count; i++) {
let light = point_lights[i];
let dist = distance(light.center, pos);

if dist < light.radius {
let raymarch = raymarch(pos, light.center);

if raymarch > 0.0 {
lighting_color += light.color * attenuation(light, dist);
}
}
}

return lighting_color;
}

fn square(x: f32) -> f32 {
return x * x;
}

fn attenuation(light: PointLight2d, dist: f32) -> f32 {
let s = dist / light.radius;
if s > 1.0 {
return 0.0;
}
let s2 = square(s);
return light.intensity * square(1 - s2) / (1 + light.falloff * s2);
}

fn get_distance(pos: vec2<f32>) -> f32 {
let uv = ndc_to_uv(world_to_ndc(pos));
let dist = textureSample(sdf, sdf_sampler, uv).r;
return dist;
}

fn distance_squared(a: vec2<f32>, b: vec2<f32>) -> f32 {
let c = a - b;
return dot(c, c);
}

fn raymarch(ray_origin: vec2<f32>, ray_target: vec2<f32>) -> 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<f32>(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 = get_distance(pos);

if dist <= 0.0 {
break;
}

ray_progress += dist;
}

// ray found occluder
return 0.0;
}

2 changes: 2 additions & 0 deletions src/render/lighting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub const SDF_SHADER: Handle<Shader> =
Handle::weak_from_u128(231804371047309214783091483091843019281);
pub const LIGHTING_SHADER: Handle<Shader> =
Handle::weak_from_u128(111120241052143214281687226997564407636);
pub const LIGHT_MAP_SHADER: Handle<Shader> =
Handle::weak_from_u128(320609826414128764415270070474935914193);

#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct LightingPass;
Expand Down
55 changes: 54 additions & 1 deletion src/render/lighting/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ use crate::render::extract::{
ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d,
};

use super::{Lighting2dAuxiliaryTextures, LightingPipeline, LightingPipelineId, SdfPipeline};
use super::{
LightMapPipeline, Lighting2dAuxiliaryTextures, LightingPipeline, LightingPipelineId,
SdfPipeline,
};

const SDF_PASS: &str = "sdf_pass";
const SDF_BIND_GROUP: &str = "sdf_bind_group";
const LIGHT_MAP_PASS: &str = "light_map_pass";
const LIGHT_MAP_BIND_GROUP: &str = "light_map_bind_group";
const LIGHTING_PASS: &str = "lighting_pass";
const LIGHTING_BIND_GROUP: &str = "lighting_bind_group";

Expand All @@ -45,19 +50,22 @@ impl ViewNode for LightingNode {
world: &'w World,
) -> Result<(), bevy::render::render_graph::NodeRunError> {
let sdf_pipeline_resource = world.resource::<SdfPipeline>();
let light_map_pipeline_resource = world.resource::<LightMapPipeline>();
let pipeline = world.resource::<LightingPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();

let (
Some(sdf_pipeline),
Some(lighting_pipeline),
Some(light_map_pipeline),
Some(view_uniform_binding),
Some(ambient_light_uniform),
Some(point_light_binding),
Some(light_occluders_binding),
) = (
pipeline_cache.get_render_pipeline(sdf_pipeline_resource.pipeline_id),
pipeline_cache.get_render_pipeline(pipeline_id.0),
pipeline_cache.get_render_pipeline(light_map_pipeline_resource.pipeline_id),
world.resource::<ViewUniforms>().uniforms.binding(),
world
.resource::<ComponentUniforms<ExtractedAmbientLight2d>>()
Expand Down Expand Up @@ -112,6 +120,51 @@ impl ViewNode for LightingNode {

drop(sdf_pass);

// Light map
let light_map_bind_group = render_context.render_device().create_bind_group(
LIGHT_MAP_BIND_GROUP,
&light_map_pipeline_resource.layout,
&BindGroupEntries::sequential((
view_uniform_binding.clone(),
ambient_light_uniform.clone(),
point_light_binding.clone(),
&aux_textures.sdf.default_view,
&light_map_pipeline_resource.sdf_sampler,
)),
);

let mut light_map_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some(LIGHT_MAP_PASS),
color_attachments: &[Some(RenderPassColorAttachment {
view: &aux_textures.light_map.default_view,
resolve_target: None,
ops: Operations::default(),
})],
..default()
});

let mut light_map_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::<RenderDevice>()
.limits()
.max_storage_buffers_per_shader_stage
== 0
{
light_map_offsets.push(0);
}

light_map_pass.set_render_pipeline(light_map_pipeline);
light_map_pass.set_bind_group(0, &light_map_bind_group, &light_map_offsets);
light_map_pass.draw(0..3, 0..1);

drop(light_map_pass);

// Main pass (should be replaced by lighting, blur and post process)
let post_process = view_target.post_process_write();

Expand Down
85 changes: 57 additions & 28 deletions src/render/lighting/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ use crate::render::extract::{
ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d,
};

use super::{LightingPipelineKey, LIGHTING_SHADER, SDF_SHADER};
use super::{LightingPipelineKey, LIGHTING_SHADER, LIGHT_MAP_SHADER, SDF_SHADER};

const SDF_PIPELINE: &str = "sdf_pipeline";
const SDF_BIND_GROUP_LAYOUT: &str = "sdf_bind_group_layout";
const LIGHTING_PIPELINE: &str = "lighting_pipeline";
const LIGHTING_BIND_GROUP_LAYOUT: &str = "lighting_bind_group_layout";
const LIGHT_MAP_BIND_GROUP_LAYOUT: &str = "light_map_group_layout";
const LIGHT_MAP_PIPELINE: &str = "light_map_pipeline";

#[derive(Resource)]
pub struct SdfPipeline {
Expand Down Expand Up @@ -53,8 +55,7 @@ impl FromWorld for SdfPipeline {
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
// No need to use a hdr format here
format: TextureFormat::bevy_default(),
format: TextureFormat::Rgba16Float,
blend: None,
write_mask: ColorWrites::ALL,
})],
Expand All @@ -72,32 +73,60 @@ impl FromWorld for SdfPipeline {
}
}

impl SpecializedRenderPipeline for SdfPipeline {
type Key = LightingPipelineKey;
#[derive(Resource)]
pub struct LightMapPipeline {
pub layout: BindGroupLayout,
pub sdf_sampler: Sampler,
pub pipeline_id: CachedRenderPipelineId,
}

fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some(SDF_PIPELINE.into()),
layout: vec![self.layout.clone()],
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: LIGHTING_SHADER,
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: if key.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
impl FromWorld for LightMapPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();

let layout = render_device.create_bind_group_layout(
LIGHT_MAP_BIND_GROUP_LAYOUT,
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
uniform_buffer::<ViewUniform>(true),
uniform_buffer::<ExtractedAmbientLight2d>(true),
GpuArrayBuffer::<ExtractedPointLight2d>::binding_layout(render_device),
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);

let sdf_sampler = render_device.create_sampler(&SamplerDescriptor::default());

let pipeline_id =
world
.resource_mut::<PipelineCache>()
.queue_render_pipeline(RenderPipelineDescriptor {
label: Some(LIGHT_MAP_PIPELINE.into()),
layout: vec![layout.clone()],
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: LIGHT_MAP_SHADER,
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::Rgba16Float,
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
});

Self {
layout,
sdf_sampler,
pipeline_id,
}
}
}
Expand Down
Loading

0 comments on commit 631d65b

Please sign in to comment.