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:
2025-12-23 23:50:49 +00:00
parent d73e50bb9e
commit b03b71c1d9
265 changed files with 83142 additions and 643 deletions

View File

@@ -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;
}

View 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;

View File

@@ -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

View File

@@ -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,
);
}