feat(rendering): Vendor Bevy rendering crates (Phase 1 complete)
Closes #6, #7, #8, #9, #10 Refs #2, #122 Vendored bevy_render, bevy_core_pipeline, and bevy_pbr from Bevy v0.17.2 (commit 566358363126dd69f6e457e47f306c68f8041d2a) into libmarathon. - ~51K LOC vendored to crates/libmarathon/src/render/ - Merged bevy_render_macros into crates/macros/ - Fixed 773→0 compilation errors - Updated dependencies (encase 0.10→0.11, added 4 new deps) - Removed bevy_render/pbr/core_pipeline from app Cargo features All builds passing, macOS smoke test successful. Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
#import bevy_pbr::{
|
||||
prepass_utils,
|
||||
pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT,
|
||||
pbr_functions,
|
||||
pbr_deferred_functions::pbr_input_from_deferred_gbuffer,
|
||||
pbr_deferred_types::unpack_unorm3x4_plus_unorm_20_,
|
||||
lighting,
|
||||
mesh_view_bindings::deferred_prepass_texture,
|
||||
}
|
||||
|
||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||
#import bevy_pbr::mesh_view_bindings::screen_space_ambient_occlusion_texture
|
||||
#import bevy_pbr::ssao_utils::ssao_multibounce
|
||||
#endif
|
||||
|
||||
struct FullscreenVertexOutput {
|
||||
@builtin(position)
|
||||
position: vec4<f32>,
|
||||
@location(0)
|
||||
uv: vec2<f32>,
|
||||
};
|
||||
|
||||
struct PbrDeferredLightingDepthId {
|
||||
depth_id: u32, // limited to u8
|
||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
|
||||
// WebGL2 structs must be 16 byte aligned.
|
||||
_webgl2_padding_0: f32,
|
||||
_webgl2_padding_1: f32,
|
||||
_webgl2_padding_2: f32,
|
||||
#endif
|
||||
}
|
||||
@group(2) @binding(0)
|
||||
var<uniform> depth_id: PbrDeferredLightingDepthId;
|
||||
|
||||
@vertex
|
||||
fn vertex(@builtin(vertex_index) vertex_index: u32) -> FullscreenVertexOutput {
|
||||
// See the full screen vertex shader for explanation above for how this works.
|
||||
let uv = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
|
||||
// Depth is stored as unorm, so we are dividing the u8 depth_id by 255.0 here.
|
||||
let clip_position = vec4<f32>(uv * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), f32(depth_id.depth_id) / 255.0, 1.0);
|
||||
|
||||
return FullscreenVertexOutput(clip_position, uv);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
|
||||
var frag_coord = vec4(in.position.xy, 0.0, 0.0);
|
||||
|
||||
let deferred_data = textureLoad(deferred_prepass_texture, vec2<i32>(frag_coord.xy), 0);
|
||||
|
||||
#ifdef WEBGL2
|
||||
frag_coord.z = unpack_unorm3x4_plus_unorm_20_(deferred_data.b).w;
|
||||
#else
|
||||
#ifdef DEPTH_PREPASS
|
||||
frag_coord.z = prepass_utils::prepass_depth(in.position, 0u);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
var pbr_input = pbr_input_from_deferred_gbuffer(frag_coord, deferred_data);
|
||||
var output_color = vec4(0.0);
|
||||
|
||||
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||
if ((pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||
|
||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
|
||||
let ssao_multibounce = ssao_multibounce(ssao, pbr_input.material.base_color.rgb);
|
||||
pbr_input.diffuse_occlusion = min(pbr_input.diffuse_occlusion, ssao_multibounce);
|
||||
|
||||
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
||||
let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001);
|
||||
var perceptual_roughness: f32 = pbr_input.material.perceptual_roughness;
|
||||
let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
// Use SSAO to estimate the specular occlusion.
|
||||
// Lagarde and Rousiers 2014, "Moving Frostbite to Physically Based Rendering"
|
||||
pbr_input.specular_occlusion = saturate(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao);
|
||||
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||
|
||||
output_color = pbr_functions::apply_pbr_lighting(pbr_input);
|
||||
} else {
|
||||
output_color = pbr_input.material.base_color;
|
||||
}
|
||||
|
||||
output_color = pbr_functions::main_pass_post_lighting_processing(pbr_input, output_color);
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
570
crates/libmarathon/src/render/pbr/deferred/mod.rs
Normal file
570
crates/libmarathon/src/render/pbr/deferred/mod.rs
Normal file
@@ -0,0 +1,570 @@
|
||||
use crate::render::pbr::{
|
||||
graph::NodePbr, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes,
|
||||
ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset,
|
||||
ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
|
||||
TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
|
||||
};
|
||||
use crate::render::pbr::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use crate::render::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
deferred::{
|
||||
copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
|
||||
},
|
||||
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
tonemapping::{DebandDither, Tonemapping},
|
||||
};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_light::{EnvironmentMapLight, IrradianceVolume, ShadowFilteringMethod};
|
||||
use crate::render::RenderStartup;
|
||||
use crate::render::{
|
||||
diagnostic::RecordDiagnostics,
|
||||
extract_component::{
|
||||
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
},
|
||||
render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},
|
||||
render_resource::{binding_types::uniform_buffer, *},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
view::{ExtractedView, ViewTarget, ViewUniformOffset},
|
||||
Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_shader::{Shader, ShaderDefVal};
|
||||
use bevy_utils::default;
|
||||
|
||||
pub struct DeferredPbrLightingPlugin;
|
||||
|
||||
pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1;
|
||||
|
||||
/// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass.
|
||||
///
|
||||
/// Will be automatically added to entities with the [`DeferredPrepass`] component that don't already have a [`PbrDeferredLightingDepthId`].
|
||||
#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
|
||||
pub struct PbrDeferredLightingDepthId {
|
||||
depth_id: u32,
|
||||
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_0: f32,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_1: f32,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_2: f32,
|
||||
}
|
||||
|
||||
impl PbrDeferredLightingDepthId {
|
||||
pub fn new(value: u8) -> PbrDeferredLightingDepthId {
|
||||
PbrDeferredLightingDepthId {
|
||||
depth_id: value as u32,
|
||||
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_0: 0.0,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_1: 0.0,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_2: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: u8) {
|
||||
self.depth_id = value as u32;
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u8 {
|
||||
self.depth_id as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PbrDeferredLightingDepthId {
|
||||
fn default() -> Self {
|
||||
PbrDeferredLightingDepthId {
|
||||
depth_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID as u32,
|
||||
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_0: 0.0,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_1: 0.0,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding_2: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for DeferredPbrLightingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<PbrDeferredLightingDepthId>::default(),
|
||||
UniformComponentPlugin::<PbrDeferredLightingDepthId>::default(),
|
||||
))
|
||||
.add_systems(PostUpdate, insert_deferred_lighting_pass_id_component);
|
||||
|
||||
embedded_asset!(app, "deferred_lighting.wgsl");
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<DeferredLightingLayout>>()
|
||||
.add_systems(RenderStartup, init_deferred_lighting_layout)
|
||||
.add_systems(
|
||||
Render,
|
||||
(prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<DeferredOpaquePass3dPbrLightingNode>>(
|
||||
Core3d,
|
||||
NodePbr::DeferredLightingPass,
|
||||
)
|
||||
.add_render_graph_edges(
|
||||
Core3d,
|
||||
(
|
||||
Node3d::StartMainPass,
|
||||
NodePbr::DeferredLightingPass,
|
||||
Node3d::MainOpaquePass,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DeferredOpaquePass3dPbrLightingNode;
|
||||
|
||||
impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
|
||||
type ViewQuery = (
|
||||
&'static ViewUniformOffset,
|
||||
&'static ViewLightsUniformOffset,
|
||||
&'static ViewFogUniformOffset,
|
||||
&'static ViewLightProbesUniformOffset,
|
||||
&'static ViewScreenSpaceReflectionsUniformOffset,
|
||||
&'static ViewEnvironmentMapUniformOffset,
|
||||
&'static MeshViewBindGroup,
|
||||
&'static ViewTarget,
|
||||
&'static DeferredLightingIdDepthTexture,
|
||||
&'static DeferredLightingPipeline,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph_context: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
(
|
||||
view_uniform_offset,
|
||||
view_lights_offset,
|
||||
view_fog_offset,
|
||||
view_light_probes_offset,
|
||||
view_ssr_offset,
|
||||
view_environment_map_offset,
|
||||
mesh_view_bind_group,
|
||||
target,
|
||||
deferred_lighting_id_depth_texture,
|
||||
deferred_lighting_pipeline,
|
||||
): QueryItem<Self::ViewQuery>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let deferred_lighting_layout = world.resource::<DeferredLightingLayout>();
|
||||
|
||||
let Some(pipeline) =
|
||||
pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let deferred_lighting_pass_id =
|
||||
world.resource::<ComponentUniforms<PbrDeferredLightingDepthId>>();
|
||||
let Some(deferred_lighting_pass_id_binding) =
|
||||
deferred_lighting_pass_id.uniforms().binding()
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let diagnostics = render_context.diagnostic_recorder();
|
||||
|
||||
let bind_group_2 = render_context.render_device().create_bind_group(
|
||||
"deferred_lighting_layout_group_2",
|
||||
&deferred_lighting_layout.bind_group_layout_2,
|
||||
&BindGroupEntries::single(deferred_lighting_pass_id_binding),
|
||||
);
|
||||
|
||||
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("deferred_lighting"),
|
||||
color_attachments: &[Some(target.get_color_attachment())],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
view: &deferred_lighting_id_depth_texture.texture.default_view,
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
let pass_span = diagnostics.pass_span(&mut render_pass, "deferred_lighting");
|
||||
|
||||
render_pass.set_render_pipeline(pipeline);
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&mesh_view_bind_group.main,
|
||||
&[
|
||||
view_uniform_offset.offset,
|
||||
view_lights_offset.offset,
|
||||
view_fog_offset.offset,
|
||||
**view_light_probes_offset,
|
||||
**view_ssr_offset,
|
||||
**view_environment_map_offset,
|
||||
],
|
||||
);
|
||||
render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]);
|
||||
render_pass.set_bind_group(2, &bind_group_2, &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
|
||||
pass_span.end(&mut render_pass);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct DeferredLightingLayout {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
bind_group_layout_2: BindGroupLayout,
|
||||
deferred_lighting_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct DeferredLightingPipeline {
|
||||
pub pipeline_id: CachedRenderPipelineId,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for DeferredLightingLayout {
|
||||
type Key = MeshPipelineKey;
|
||||
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
let mut shader_defs = Vec::new();
|
||||
|
||||
// Let the shader code know that it's running in a deferred pipeline.
|
||||
shader_defs.push("DEFERRED_LIGHTING_PIPELINE".into());
|
||||
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
shader_defs.push("WEBGL2".into());
|
||||
|
||||
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
|
||||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
|
||||
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
|
||||
));
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
|
||||
TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
|
||||
));
|
||||
|
||||
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||
|
||||
if method == MeshPipelineKey::TONEMAP_METHOD_NONE {
|
||||
shader_defs.push("TONEMAP_METHOD_NONE".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD {
|
||||
shader_defs.push("TONEMAP_METHOD_REINHARD".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {
|
||||
shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED {
|
||||
shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_AGX {
|
||||
shader_defs.push("TONEMAP_METHOD_AGX".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM {
|
||||
shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {
|
||||
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
|
||||
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
||||
}
|
||||
|
||||
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
||||
if key.contains(MeshPipelineKey::DEBAND_DITHER) {
|
||||
shader_defs.push("DEBAND_DITHER".into());
|
||||
}
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
|
||||
shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
|
||||
shader_defs.push("ENVIRONMENT_MAP".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) {
|
||||
shader_defs.push("IRRADIANCE_VOLUME".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::NORMAL_PREPASS) {
|
||||
shader_defs.push("NORMAL_PREPASS".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::DEPTH_PREPASS) {
|
||||
shader_defs.push("DEPTH_PREPASS".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
|
||||
shader_defs.push("MOTION_VECTOR_PREPASS".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::SCREEN_SPACE_REFLECTIONS) {
|
||||
shader_defs.push("SCREEN_SPACE_REFLECTIONS".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
|
||||
shader_defs.push("HAS_PREVIOUS_SKIN".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
|
||||
shader_defs.push("HAS_PREVIOUS_MORPH".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::DISTANCE_FOG) {
|
||||
shader_defs.push("DISTANCE_FOG".into());
|
||||
}
|
||||
|
||||
// Always true, since we're in the deferred lighting pipeline
|
||||
shader_defs.push("DEFERRED_PREPASS".into());
|
||||
|
||||
let shadow_filter_method =
|
||||
key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
|
||||
if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
|
||||
shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
|
||||
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
|
||||
shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
|
||||
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
|
||||
shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
|
||||
}
|
||||
if self.mesh_pipeline.binding_arrays_are_usable {
|
||||
shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
|
||||
shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
|
||||
|
||||
let layout = self.mesh_pipeline.get_view_layout(key.into());
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("deferred_lighting_pipeline".into()),
|
||||
layout: vec![
|
||||
layout.main_layout.clone(),
|
||||
layout.binding_array_layout.clone(),
|
||||
self.bind_group_layout_2.clone(),
|
||||
],
|
||||
vertex: VertexState {
|
||||
shader: self.deferred_lighting_shader.clone(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
..default()
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.deferred_lighting_shader.clone(),
|
||||
shader_defs,
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: if key.contains(MeshPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
},
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
..default()
|
||||
}),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: CompareFunction::Equal,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_deferred_lighting_layout(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let layout = render_device.create_bind_group_layout(
|
||||
"deferred_lighting_layout",
|
||||
&BindGroupLayoutEntries::single(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
uniform_buffer::<PbrDeferredLightingDepthId>(false),
|
||||
),
|
||||
);
|
||||
commands.insert_resource(DeferredLightingLayout {
|
||||
mesh_pipeline: mesh_pipeline.clone(),
|
||||
bind_group_layout_2: layout,
|
||||
deferred_lighting_shader: load_embedded_asset!(
|
||||
asset_server.as_ref(),
|
||||
"deferred_lighting.wgsl"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_deferred_lighting_pass_id_component(
|
||||
mut commands: Commands,
|
||||
views: Query<Entity, (With<DeferredPrepass>, Without<PbrDeferredLightingDepthId>)>,
|
||||
) {
|
||||
for entity in views.iter() {
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(PbrDeferredLightingDepthId::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_deferred_lighting_pipelines(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<DeferredLightingLayout>>,
|
||||
deferred_lighting_layout: Res<DeferredLightingLayout>,
|
||||
views: Query<(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
Option<&Tonemapping>,
|
||||
Option<&DebandDither>,
|
||||
Option<&ShadowFilteringMethod>,
|
||||
(
|
||||
Has<ScreenSpaceAmbientOcclusion>,
|
||||
Has<ScreenSpaceReflectionsUniform>,
|
||||
Has<DistanceFog>,
|
||||
),
|
||||
(
|
||||
Has<NormalPrepass>,
|
||||
Has<DepthPrepass>,
|
||||
Has<MotionVectorPrepass>,
|
||||
Has<DeferredPrepass>,
|
||||
),
|
||||
Has<RenderViewLightProbes<EnvironmentMapLight>>,
|
||||
Has<RenderViewLightProbes<IrradianceVolume>>,
|
||||
Has<SkipDeferredLighting>,
|
||||
)>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
view,
|
||||
tonemapping,
|
||||
dither,
|
||||
shadow_filter_method,
|
||||
(ssao, ssr, distance_fog),
|
||||
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
|
||||
has_environment_maps,
|
||||
has_irradiance_volumes,
|
||||
skip_deferred_lighting,
|
||||
) in &views
|
||||
{
|
||||
// If there is no deferred prepass or we want to skip the deferred lighting pass,
|
||||
// remove the old pipeline if there was one. This handles the case in which a
|
||||
// view using deferred stops using it.
|
||||
if !deferred_prepass || skip_deferred_lighting {
|
||||
commands.entity(entity).remove::<DeferredLightingPipeline>();
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
|
||||
|
||||
if normal_prepass {
|
||||
view_key |= MeshPipelineKey::NORMAL_PREPASS;
|
||||
}
|
||||
|
||||
if depth_prepass {
|
||||
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
||||
}
|
||||
|
||||
if motion_vector_prepass {
|
||||
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
||||
}
|
||||
|
||||
// Always true, since we're in the deferred lighting pipeline
|
||||
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
||||
|
||||
if !view.hdr {
|
||||
if let Some(tonemapping) = tonemapping {
|
||||
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
||||
view_key |= match tonemapping {
|
||||
Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
|
||||
Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
|
||||
Tonemapping::ReinhardLuminance => {
|
||||
MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
|
||||
}
|
||||
Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
|
||||
Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
|
||||
Tonemapping::SomewhatBoringDisplayTransform => {
|
||||
MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
|
||||
}
|
||||
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
};
|
||||
}
|
||||
if let Some(DebandDither::Enabled) = dither {
|
||||
view_key |= MeshPipelineKey::DEBAND_DITHER;
|
||||
}
|
||||
}
|
||||
|
||||
if ssao {
|
||||
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
|
||||
}
|
||||
if ssr {
|
||||
view_key |= MeshPipelineKey::SCREEN_SPACE_REFLECTIONS;
|
||||
}
|
||||
if distance_fog {
|
||||
view_key |= MeshPipelineKey::DISTANCE_FOG;
|
||||
}
|
||||
|
||||
// We don't need to check to see whether the environment map is loaded
|
||||
// because [`gather_light_probes`] already checked that for us before
|
||||
// adding the [`RenderViewEnvironmentMaps`] component.
|
||||
if has_environment_maps {
|
||||
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
||||
}
|
||||
|
||||
if has_irradiance_volumes {
|
||||
view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
|
||||
}
|
||||
|
||||
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
|
||||
ShadowFilteringMethod::Hardware2x2 => {
|
||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
|
||||
}
|
||||
ShadowFilteringMethod::Gaussian => {
|
||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
|
||||
}
|
||||
ShadowFilteringMethod::Temporal => {
|
||||
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline_id =
|
||||
pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key);
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(DeferredLightingPipeline { pipeline_id });
|
||||
}
|
||||
}
|
||||
|
||||
/// Component to skip running the deferred lighting pass in [`DeferredOpaquePass3dPbrLightingNode`] for a specific view.
|
||||
///
|
||||
/// This works like [`crate::PbrPlugin::add_default_deferred_lighting_plugin`], but is per-view instead of global.
|
||||
///
|
||||
/// Useful for cases where you want to generate a gbuffer, but skip the built-in deferred lighting pass
|
||||
/// to run your own custom lighting pass instead.
|
||||
///
|
||||
/// Insert this component in the render world only.
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct SkipDeferredLighting;
|
||||
@@ -0,0 +1,153 @@
|
||||
#define_import_path bevy_pbr::pbr_deferred_functions
|
||||
|
||||
#import bevy_pbr::{
|
||||
pbr_types::{PbrInput, pbr_input_new, STANDARD_MATERIAL_FLAGS_UNLIT_BIT},
|
||||
pbr_deferred_types as deferred_types,
|
||||
pbr_functions,
|
||||
rgb9e5,
|
||||
mesh_view_bindings::view,
|
||||
utils::{octahedral_encode, octahedral_decode},
|
||||
prepass_io::FragmentOutput,
|
||||
view_transformations::{position_ndc_to_world, frag_coord_to_ndc},
|
||||
}
|
||||
|
||||
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||
#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
|
||||
#else
|
||||
#import bevy_pbr::prepass_io::VertexOutput
|
||||
#endif
|
||||
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
#import bevy_pbr::pbr_prepass_functions::calculate_motion_vector
|
||||
#endif
|
||||
|
||||
// Creates the deferred gbuffer from a PbrInput.
|
||||
fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
|
||||
// Only monochrome occlusion supported. May not be worth including at all.
|
||||
// Some models have baked occlusion, GLTF only supports monochrome.
|
||||
// Real time occlusion is applied in the deferred lighting pass.
|
||||
// Deriving luminance via Rec. 709. coefficients
|
||||
// https://en.wikipedia.org/wiki/Rec._709
|
||||
let rec_709_coeffs = vec3<f32>(0.2126, 0.7152, 0.0722);
|
||||
let diffuse_occlusion = dot(in.diffuse_occlusion, rec_709_coeffs);
|
||||
// Only monochrome specular supported.
|
||||
let reflectance = dot(in.material.reflectance, rec_709_coeffs);
|
||||
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
||||
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
|
||||
reflectance,
|
||||
in.material.metallic,
|
||||
diffuse_occlusion,
|
||||
in.frag_coord.z));
|
||||
#else
|
||||
var props = deferred_types::pack_unorm4x8_(vec4(
|
||||
reflectance, // could be fewer bits
|
||||
in.material.metallic, // could be fewer bits
|
||||
diffuse_occlusion, // is this worth including?
|
||||
0.0)); // spare
|
||||
#endif // WEBGL2
|
||||
let flags = deferred_types::deferred_flags_from_mesh_material_flags(in.flags, in.material.flags);
|
||||
let octahedral_normal = octahedral_encode(normalize(in.N));
|
||||
var base_color_srgb = vec3(0.0);
|
||||
var emissive = in.material.emissive.rgb;
|
||||
if ((in.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) {
|
||||
// Material is unlit, use emissive component of gbuffer for color data.
|
||||
// Unlit materials are effectively emissive.
|
||||
emissive = in.material.base_color.rgb;
|
||||
} else {
|
||||
base_color_srgb = pow(in.material.base_color.rgb, vec3(1.0 / 2.2));
|
||||
}
|
||||
|
||||
// Utilize the emissive channel to transmit the lightmap data. To ensure
|
||||
// it matches the output in forward shading, pre-multiply it with the
|
||||
// calculated diffuse color.
|
||||
let base_color = in.material.base_color.rgb;
|
||||
let metallic = in.material.metallic;
|
||||
let specular_transmission = in.material.specular_transmission;
|
||||
let diffuse_transmission = in.material.diffuse_transmission;
|
||||
let diffuse_color = pbr_functions::calculate_diffuse_color(
|
||||
base_color,
|
||||
metallic,
|
||||
specular_transmission,
|
||||
diffuse_transmission
|
||||
);
|
||||
emissive += in.lightmap_light * diffuse_color * view.exposure;
|
||||
|
||||
let deferred = vec4(
|
||||
deferred_types::pack_unorm4x8_(vec4(base_color_srgb, in.material.perceptual_roughness)),
|
||||
rgb9e5::vec3_to_rgb9e5_(emissive),
|
||||
props,
|
||||
deferred_types::pack_24bit_normal_and_flags(octahedral_normal, flags),
|
||||
);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
// Creates a PbrInput from the deferred gbuffer.
|
||||
fn pbr_input_from_deferred_gbuffer(frag_coord: vec4<f32>, gbuffer: vec4<u32>) -> PbrInput {
|
||||
var pbr = pbr_input_new();
|
||||
|
||||
let flags = deferred_types::unpack_flags(gbuffer.a);
|
||||
let deferred_flags = deferred_types::mesh_material_flags_from_deferred_flags(flags);
|
||||
pbr.flags = deferred_flags.x;
|
||||
pbr.material.flags = deferred_flags.y;
|
||||
|
||||
let base_rough = deferred_types::unpack_unorm4x8_(gbuffer.r);
|
||||
pbr.material.perceptual_roughness = base_rough.a;
|
||||
let emissive = rgb9e5::rgb9e5_to_vec3_(gbuffer.g);
|
||||
if ((pbr.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) {
|
||||
pbr.material.base_color = vec4(emissive, 1.0);
|
||||
pbr.material.emissive = vec4(vec3(0.0), 0.0);
|
||||
} else {
|
||||
pbr.material.base_color = vec4(pow(base_rough.rgb, vec3(2.2)), 1.0);
|
||||
pbr.material.emissive = vec4(emissive, 0.0);
|
||||
}
|
||||
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
||||
let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b);
|
||||
// Bias to 0.5 since that's the value for almost all materials.
|
||||
pbr.material.reflectance = vec3(saturate(props.r - 0.03333333333));
|
||||
#else
|
||||
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
|
||||
pbr.material.reflectance = vec3(props.r);
|
||||
#endif // WEBGL2
|
||||
pbr.material.metallic = props.g;
|
||||
pbr.diffuse_occlusion = vec3(props.b);
|
||||
let octahedral_normal = deferred_types::unpack_24bit_normal(gbuffer.a);
|
||||
let N = octahedral_decode(octahedral_normal);
|
||||
|
||||
let world_position = vec4(position_ndc_to_world(frag_coord_to_ndc(frag_coord)), 1.0);
|
||||
let is_orthographic = view.clip_from_view[3].w == 1.0;
|
||||
let V = pbr_functions::calculate_view(world_position, is_orthographic);
|
||||
|
||||
pbr.frag_coord = frag_coord;
|
||||
pbr.world_normal = N;
|
||||
pbr.world_position = world_position;
|
||||
pbr.N = N;
|
||||
pbr.V = V;
|
||||
pbr.is_orthographic = is_orthographic;
|
||||
|
||||
return pbr;
|
||||
}
|
||||
|
||||
#ifdef PREPASS_PIPELINE
|
||||
fn deferred_output(in: VertexOutput, pbr_input: PbrInput) -> FragmentOutput {
|
||||
var out: FragmentOutput;
|
||||
|
||||
// gbuffer
|
||||
out.deferred = deferred_gbuffer_from_pbr_input(pbr_input);
|
||||
// lighting pass id (used to determine which lighting shader to run for the fragment)
|
||||
out.deferred_lighting_pass_id = pbr_input.material.deferred_lighting_pass_id;
|
||||
// normal if required
|
||||
#ifdef NORMAL_PREPASS
|
||||
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
|
||||
#endif
|
||||
// motion vectors if required
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||
out.motion_vector = in.motion_vector;
|
||||
#else
|
||||
out.motion_vector = calculate_motion_vector(in.world_position, in.previous_world_position);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,89 @@
|
||||
#define_import_path bevy_pbr::pbr_deferred_types
|
||||
|
||||
#import bevy_pbr::{
|
||||
mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT,
|
||||
pbr_types::{STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT},
|
||||
}
|
||||
|
||||
// Maximum of 8 bits available
|
||||
const DEFERRED_FLAGS_UNLIT_BIT: u32 = 1u << 0u;
|
||||
const DEFERRED_FLAGS_FOG_ENABLED_BIT: u32 = 1u << 1u;
|
||||
const DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u << 2u;
|
||||
|
||||
fn deferred_flags_from_mesh_material_flags(mesh_flags: u32, mat_flags: u32) -> u32 {
|
||||
var flags = 0u;
|
||||
flags |= u32((mesh_flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) * DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT;
|
||||
flags |= u32((mat_flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) * DEFERRED_FLAGS_FOG_ENABLED_BIT;
|
||||
flags |= u32((mat_flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) * DEFERRED_FLAGS_UNLIT_BIT;
|
||||
return flags;
|
||||
}
|
||||
|
||||
fn mesh_material_flags_from_deferred_flags(deferred_flags: u32) -> vec2<u32> {
|
||||
var mat_flags = 0u;
|
||||
var mesh_flags = 0u;
|
||||
mesh_flags |= u32((deferred_flags & DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) * MESH_FLAGS_SHADOW_RECEIVER_BIT;
|
||||
mat_flags |= u32((deferred_flags & DEFERRED_FLAGS_FOG_ENABLED_BIT) != 0u) * STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT;
|
||||
mat_flags |= u32((deferred_flags & DEFERRED_FLAGS_UNLIT_BIT) != 0u) * STANDARD_MATERIAL_FLAGS_UNLIT_BIT;
|
||||
return vec2(mesh_flags, mat_flags);
|
||||
}
|
||||
|
||||
const U12MAXF = 4095.0;
|
||||
const U16MAXF = 65535.0;
|
||||
const U20MAXF = 1048575.0;
|
||||
|
||||
// Storing normals as oct24.
|
||||
// Flags are stored in the remaining 8 bits.
|
||||
// https://jcgt.org/published/0003/02/01/paper.pdf
|
||||
// Could possibly go down to oct20 if the space is needed.
|
||||
|
||||
fn pack_24bit_normal_and_flags(octahedral_normal: vec2<f32>, flags: u32) -> u32 {
|
||||
let unorm1 = u32(saturate(octahedral_normal.x) * U12MAXF + 0.5);
|
||||
let unorm2 = u32(saturate(octahedral_normal.y) * U12MAXF + 0.5);
|
||||
return (unorm1 & 0xFFFu) | ((unorm2 & 0xFFFu) << 12u) | ((flags & 0xFFu) << 24u);
|
||||
}
|
||||
|
||||
fn unpack_24bit_normal(packed: u32) -> vec2<f32> {
|
||||
let unorm1 = packed & 0xFFFu;
|
||||
let unorm2 = (packed >> 12u) & 0xFFFu;
|
||||
return vec2(f32(unorm1) / U12MAXF, f32(unorm2) / U12MAXF);
|
||||
}
|
||||
|
||||
fn unpack_flags(packed: u32) -> u32 {
|
||||
return (packed >> 24u) & 0xFFu;
|
||||
}
|
||||
|
||||
// The builtin one didn't work in webgl.
|
||||
// "'unpackUnorm4x8' : no matching overloaded function found"
|
||||
// https://github.com/gfx-rs/naga/issues/2006
|
||||
fn unpack_unorm4x8_(v: u32) -> vec4<f32> {
|
||||
return vec4(
|
||||
f32(v & 0xFFu),
|
||||
f32((v >> 8u) & 0xFFu),
|
||||
f32((v >> 16u) & 0xFFu),
|
||||
f32((v >> 24u) & 0xFFu)
|
||||
) / 255.0;
|
||||
}
|
||||
|
||||
// 'packUnorm4x8' : no matching overloaded function found
|
||||
// https://github.com/gfx-rs/naga/issues/2006
|
||||
fn pack_unorm4x8_(values: vec4<f32>) -> u32 {
|
||||
let v = vec4<u32>(saturate(values) * 255.0 + 0.5);
|
||||
return (v.w << 24u) | (v.z << 16u) | (v.y << 8u) | v.x;
|
||||
}
|
||||
|
||||
// Pack 3x 4bit unorm + 1x 20bit
|
||||
fn pack_unorm3x4_plus_unorm_20_(v: vec4<f32>) -> u32 {
|
||||
let sm = vec3<u32>(saturate(v.xyz) * 15.0 + 0.5);
|
||||
let bg = u32(saturate(v.w) * U20MAXF + 0.5);
|
||||
return (bg << 12u) | (sm.z << 8u) | (sm.y << 4u) | sm.x;
|
||||
}
|
||||
|
||||
// Unpack 3x 4bit unorm + 1x 20bit
|
||||
fn unpack_unorm3x4_plus_unorm_20_(v: u32) -> vec4<f32> {
|
||||
return vec4(
|
||||
f32(v & 0xfu) / 15.0,
|
||||
f32((v >> 4u) & 0xFu) / 15.0,
|
||||
f32((v >> 8u) & 0xFu) / 15.0,
|
||||
f32((v >> 12u) & 0xFFFFFFu) / U20MAXF,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user