Skip to content

Commit

Permalink
Adds SDF Pass
Browse files Browse the repository at this point in the history
  • Loading branch information
malbernaz committed Jul 29, 2024
1 parent 5cf2870 commit 3d59f6e
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 55 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ exclude = ["assets/*", "static/*"]

[dependencies]
bevy = { version = "0.14", default-features = false, features = [
"bevy_pbr",
"bevy_render",
"bevy_core_pipeline",
"bevy_winit",
"x11"
"x11",
] }
smallvec = "1.13"

[dev-dependencies]
bevy = { version = "0.14", default-features = false, features = [
"bevy_pbr",
"bevy_render",
"bevy_core_pipeline",
"bevy_winit",
"bevy_sprite",
"png",
"x11"
"x11",
] }

[lints.clippy]
Expand Down
40 changes: 32 additions & 8 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bevy::{
gpu_component_array_buffer::GpuComponentArrayBufferPlugin,
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::SpecializedRenderPipelines,
view::{check_visibility, VisibilitySystems},
view::{check_visibility, prepare_view_targets, VisibilitySystems},
Render, RenderApp, RenderSet,
},
};
Expand All @@ -18,12 +18,12 @@ use crate::{
light::{AmbientLight2d, PointLight2d},
render::{
extract::{
extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d,
ExtractedPointLight2d,
extract_ambient_lights, extract_light_occluders, extract_point_lights,
ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d,
},
lighting::{
prepare_lighting_pipelines, LightingNode, LightingPass, LightingPipeline,
LIGHTING_SHADER,
prepare_lighting_auxiliary_textures, prepare_lighting_pipelines, LightingNode,
LightingPass, LightingPipeline, SdfPipeline, LIGHTING_SHADER, SDF_SHADER, TYPES_SHADER,
},
},
};
Expand All @@ -33,6 +33,18 @@ pub struct Light2dPlugin;

impl Plugin for Light2dPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
TYPES_SHADER,
"render/lighting/types.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
SDF_SHADER,
"render/lighting/sdf.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
LIGHTING_SHADER,
Expand All @@ -43,6 +55,7 @@ impl Plugin for Light2dPlugin {
app.add_plugins((
UniformComponentPlugin::<ExtractedAmbientLight2d>::default(),
GpuComponentArrayBufferPlugin::<ExtractedPointLight2d>::default(),
GpuComponentArrayBufferPlugin::<ExtractedLightOccluder2d>::default(),
))
.register_type::<AmbientLight2d>()
.register_type::<PointLight2d>()
Expand All @@ -59,11 +72,20 @@ impl Plugin for Light2dPlugin {
.init_resource::<SpecializedRenderPipelines<LightingPipeline>>()
.add_systems(
ExtractSchedule,
(extract_point_lights, extract_ambient_lights),
(
extract_point_lights,
extract_light_occluders,
extract_ambient_lights,
),
)
.add_systems(
Render,
prepare_lighting_pipelines.in_set(RenderSet::Prepare),
(
prepare_lighting_pipelines.in_set(RenderSet::Prepare),
prepare_lighting_auxiliary_textures
.after(prepare_view_targets)
.in_set(RenderSet::ManageViews),
),
)
.add_render_graph_node::<ViewNodeRunner<LightingNode>>(Core2d, LightingPass)
.add_render_graph_edge(Core2d, Node2d::EndMainPass, LightingPass);
Expand All @@ -74,6 +96,8 @@ impl Plugin for Light2dPlugin {
return;
};

render_app.init_resource::<LightingPipeline>();
render_app
.init_resource::<LightingPipeline>()
.init_resource::<SdfPipeline>();
}
}
32 changes: 31 additions & 1 deletion src/render/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy::{
render::{render_resource::ShaderType, Extract},
};

use crate::light::{AmbientLight2d, PointLight2d};
use crate::light::{AmbientLight2d, LightOccluder2d, PointLight2d};

#[derive(Component, Default, Clone, ShaderType)]
pub struct ExtractedPointLight2d {
Expand All @@ -14,6 +14,12 @@ pub struct ExtractedPointLight2d {
pub falloff: f32,
}

#[derive(Component, Default, Clone, ShaderType)]
pub struct ExtractedLightOccluder2d {
pub half_size: Vec2,
pub center: Vec2,
}

#[derive(Component, Default, Clone, ShaderType)]
pub struct ExtractedAmbientLight2d {
pub color: LinearRgba,
Expand Down Expand Up @@ -47,6 +53,30 @@ pub fn extract_point_lights(
});
}

pub fn extract_light_occluders(
mut commands: Commands,
light_occluders_query: Extract<
Query<(Entity, &LightOccluder2d, &GlobalTransform, &ViewVisibility)>,
>,
) {
for (entity, light_occluder, global_transform, view_visibility) in &light_occluders_query {
if !view_visibility.get() {
continue;
}

commands
.get_or_spawn(entity)
.insert(ExtractedLightOccluder2d {
half_size: light_occluder.half_size,
center: global_transform.translation().xy(),
});
}

// BufferVec won't write to the GPU if there aren't any point lights.
// For now we can spawn an empty point light to get around this.
commands.spawn(ExtractedLightOccluder2d::default());
}

pub fn extract_ambient_lights(
mut commands: Commands,
ambient_light_query: Extract<Query<(Entity, &AmbientLight2d)>>,
Expand Down
8 changes: 6 additions & 2 deletions src/render/lighting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ use bevy::{
};

pub use node::LightingNode;
pub use pipeline::LightingPipeline;
pub use prepare::prepare_lighting_pipelines;
pub use pipeline::*;
pub use prepare::*;

pub const TYPES_SHADER: Handle<Shader> =
Handle::weak_from_u128(134542958402584092759402858489640143033);
pub const SDF_SHADER: Handle<Shader> =
Handle::weak_from_u128(231804371047309214783091483091843019281);
pub const LIGHTING_SHADER: Handle<Shader> =
Handle::weak_from_u128(111120241052143214281687226997564407636);

Expand Down
106 changes: 77 additions & 29 deletions src/render/lighting/node.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bevy::ecs::system::lifetimeless::Read;
use bevy::prelude::*;
use bevy::render::extract_component::{ComponentUniforms, DynamicUniformIndex};
use bevy::render::render_graph::ViewNode;
Expand All @@ -10,10 +11,14 @@ use bevy::render::renderer::RenderDevice;
use bevy::render::view::{ViewTarget, ViewUniformOffset, ViewUniforms};
use smallvec::{smallvec, SmallVec};

use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d};
use crate::render::extract::{
ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d,
};

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

const SDF_PASS: &str = "sdf_pass";
const SDF_BIND_GROUP: &str = "sdf_bind_group";
const LIGHTING_PASS: &str = "lighting_pass";
const LIGHTING_BIND_GROUP: &str = "lighting_bind_group";

Expand All @@ -22,57 +27,100 @@ pub struct LightingNode;

impl ViewNode for LightingNode {
type ViewQuery = (
&'static ViewTarget,
&'static DynamicUniformIndex<ExtractedAmbientLight2d>,
&'static ViewUniformOffset,
&'static LightingPipelineId,
Read<ViewTarget>,
Read<DynamicUniformIndex<ExtractedAmbientLight2d>>,
Read<ViewUniformOffset>,
Read<LightingPipelineId>,
Read<Lighting2dAuxiliaryTextures>,
);

fn run<'w>(
&self,
_graph: &mut bevy::render::render_graph::RenderGraphContext,
render_context: &mut bevy::render::renderer::RenderContext<'w>,
(view_target, ambient_index, view_offset, pipeline_id): bevy::ecs::query::QueryItem<
(view_target, ambient_index, view_offset, pipeline_id, aux_textures): bevy::ecs::query::QueryItem<
'w,
Self::ViewQuery,
>,
world: &'w World,
) -> Result<(), bevy::render::render_graph::NodeRunError> {
let lighting_pipeline = world.resource::<LightingPipeline>();

let sdf_pipeline_resource = world.resource::<SdfPipeline>();
let pipeline = world.resource::<LightingPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();

let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
let (
Some(sdf_pipeline),
Some(lighting_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),
world.resource::<ViewUniforms>().uniforms.binding(),
world
.resource::<ComponentUniforms<ExtractedAmbientLight2d>>()
.uniforms()
.binding(),
world
.resource::<GpuArrayBuffer<ExtractedPointLight2d>>()
.binding(),
world
.resource::<GpuArrayBuffer<ExtractedLightOccluder2d>>()
.binding(),
)
else {
return Ok(());
};

let Some(view_uniform_binding) = world.resource::<ViewUniforms>().uniforms.binding() else {
return Ok(());
};
// SDF
let bind_group = render_context.render_device().create_bind_group(
SDF_BIND_GROUP,
&sdf_pipeline_resource.layout,
&BindGroupEntries::sequential((view_uniform_binding.clone(), light_occluders_binding)),
);

let Some(ambient_light_uniform) = world
.resource::<ComponentUniforms<ExtractedAmbientLight2d>>()
.uniforms()
.binding()
else {
return Ok(());
};
let mut sdf_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some(SDF_PASS),
color_attachments: &[Some(RenderPassColorAttachment {
view: &aux_textures.sdf.default_view,
resolve_target: None,
ops: Operations::default(),
})],
..default()
});

let Some(point_light_binding) = world
.resource::<GpuArrayBuffer<ExtractedPointLight2d>>()
.binding()
else {
return Ok(());
};
let mut dynamic_offsets: SmallVec<[u32; 3]> = smallvec![view_offset.offset];

// 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
{
dynamic_offsets.push(0);
}

sdf_pass.set_render_pipeline(sdf_pipeline);
sdf_pass.set_bind_group(0, &bind_group, &dynamic_offsets);
sdf_pass.draw(0..3, 0..1);

drop(sdf_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(
LIGHTING_BIND_GROUP,
&lighting_pipeline.layout,
&pipeline.layout,
&BindGroupEntries::sequential((
post_process.source,
&lighting_pipeline.sampler,
&pipeline.sampler,
view_uniform_binding,
ambient_light_uniform,
point_light_binding,
Expand Down Expand Up @@ -107,7 +155,7 @@ impl ViewNode for LightingNode {
dynamic_offsets.push(0);
}

render_pass.set_render_pipeline(pipeline);
render_pass.set_render_pipeline(lighting_pipeline);
render_pass.set_bind_group(0, &bind_group, &dynamic_offsets);
render_pass.draw(0..3, 0..1);

Expand Down
Loading

0 comments on commit 3d59f6e

Please sign in to comment.