Deferred Renderer (#9258)

# Objective

- Add a [Deferred
Renderer](https://en.wikipedia.org/wiki/Deferred_shading) to Bevy.
- This allows subsequent passes to access per pixel material information
before/during shading.
- Accessing this per pixel material information is needed for some
features, like GI. It also makes other features (ex. Decals) simpler to
implement and/or improves their capability. There are multiple
approaches to accomplishing this. The deferred shading approach works
well given the limitations of WebGPU and WebGL2.

Motivation: [I'm working on a GI solution for
Bevy](https://youtu.be/eH1AkL-mwhI)

# Solution
- The deferred renderer is implemented with a prepass and a deferred
lighting pass.
- The prepass renders opaque objects into the Gbuffer attachment
(`Rgba32Uint`). The PBR shader generates a `PbrInput` in mostly the same
way as the forward implementation and then [packs it into the
Gbuffer](ec1465559f/crates/bevy_pbr/src/render/pbr.wgsl (L168)).
- The deferred lighting pass unpacks the `PbrInput` and [feeds it into
the pbr()
function](ec1465559f/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl (L65)),
then outputs the shaded color data.

- There is now a resource
[DefaultOpaqueRendererMethod](ec1465559f/crates/bevy_pbr/src/material.rs (L599))
that can be used to set the default render method for opaque materials.
If materials return `None` from
[opaque_render_method()](ec1465559f/crates/bevy_pbr/src/material.rs (L131))
the `DefaultOpaqueRendererMethod` will be used. Otherwise, custom
materials can also explicitly choose to only support Deferred or Forward
by returning the respective
[OpaqueRendererMethod](ec1465559f/crates/bevy_pbr/src/material.rs (L603))

- Deferred materials can be used seamlessly along with both opaque and
transparent forward rendered materials in the same scene. The [deferred
rendering
example](https://github.com/DGriffin91/bevy/blob/deferred/examples/3d/deferred_rendering.rs)
does this.

- The deferred renderer does not support MSAA. If any deferred materials
are used, MSAA must be disabled. Both TAA and FXAA are supported.

- Deferred rendering supports WebGL2/WebGPU. 

## Custom deferred materials
- Custom materials can support both deferred and forward at the same
time. The
[StandardMaterial](ec1465559f/crates/bevy_pbr/src/render/pbr.wgsl (L166))
does this. So does [this
example](https://github.com/DGriffin91/bevy_glowy_orb_tutorial/blob/deferred/assets/shaders/glowy.wgsl#L56).
- Custom deferred materials that require PBR lighting can create a
`PbrInput`, write it to the deferred GBuffer and let it be rendered by
the `PBRDeferredLightingPlugin`.
- Custom deferred materials that require custom lighting have two
options:
1. Use the base_color channel of the `PbrInput` combined with the
`STANDARD_MATERIAL_FLAGS_UNLIT_BIT` flag.
[Example.](https://github.com/DGriffin91/bevy_glowy_orb_tutorial/blob/deferred/assets/shaders/glowy.wgsl#L56)
(If the unlit bit is set, the base_color is stored as RGB9E5 for extra
precision)
2. A Custom Deferred Lighting pass can be created, either overriding the
default, or running in addition. The a depth buffer is used to limit
rendering to only the required fragments for each deferred lighting
pass. Materials can set their respective depth id via the
[deferred_lighting_pass_id](b79182d2a3/crates/bevy_pbr/src/prepass/prepass_io.wgsl (L95))
attachment. The custom deferred lighting pass plugin can then set [its
corresponding
depth](ec1465559f/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl (L37)).
Then with the lighting pass using
[CompareFunction::Equal](ec1465559f/crates/bevy_pbr/src/deferred/mod.rs (L335)),
only the fragments with a depth that equal the corresponding depth
written in the material will be rendered.

Custom deferred lighting plugins can also be created to render the
StandardMaterial. The default deferred lighting plugin can be bypassed
with `DefaultPlugins.set(PBRDeferredLightingPlugin { bypass: true })`

---------

Co-authored-by: nickrart <nickolas.g.russell@gmail.com>
This commit is contained in:
Griffin 2023-10-12 15:10:38 -07:00 committed by GitHub
parent c8fd390ced
commit a15d152635
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 2960 additions and 486 deletions

View file

@ -611,6 +611,16 @@ description = "Illustrates bloom configuration using HDR and emissive materials"
category = "3D Rendering"
wasm = true
[[example]]
name = "deferred_rendering"
path = "examples/3d/deferred_rendering.rs"
[package.metadata.example.deferred_rendering]
name = "Deferred Rendering"
description = "Renders meshes with both forward and deferred pipelines"
category = "3D Rendering"
wasm = true
[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"

View file

@ -1,6 +1,6 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
#import bevy_pbr::mesh_view_bindings view
#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT
#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT, PbrInput, pbr_input_new
#import bevy_core_pipeline::tonemapping tone_mapping
#import bevy_pbr::pbr_functions as fns
@ -16,7 +16,7 @@ fn fragment(
// Prepare a 'processed' StandardMaterial by sampling all textures to resolve
// the material members
var pbr_input: fns::PbrInput = fns::pbr_input_new();
var pbr_input: PbrInput = pbr_input_new();
pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, mesh.uv, layer);
#ifdef VERTEX_COLORS

View file

@ -1,7 +1,7 @@
use crate::{
clear_color::{ClearColor, ClearColorConfig},
core_3d::{Camera3d, Opaque3d},
prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass},
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
skybox::{SkyboxBindGroup, SkyboxPipelineId},
};
use bevy_ecs::{prelude::*, query::QueryItem};
@ -34,6 +34,7 @@ impl ViewNode for MainOpaquePass3dNode {
Option<&'static DepthPrepass>,
Option<&'static NormalPrepass>,
Option<&'static MotionVectorPrepass>,
Option<&'static DeferredPrepass>,
Option<&'static SkyboxPipelineId>,
Option<&'static SkyboxBindGroup>,
&'static ViewUniformOffset,
@ -53,12 +54,24 @@ impl ViewNode for MainOpaquePass3dNode {
depth_prepass,
normal_prepass,
motion_vector_prepass,
deferred_prepass,
skybox_pipeline,
skybox_bind_group,
view_uniform_offset,
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let load = if deferred_prepass.is_none() {
match camera_3d.clear_color {
ClearColorConfig::Default => LoadOp::Clear(world.resource::<ClearColor>().0.into()),
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
ClearColorConfig::None => LoadOp::Load,
}
} else {
// If the deferred lighting pass has run, don't clear again in this pass.
LoadOp::Load
};
// Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")]
@ -69,16 +82,9 @@ impl ViewNode for MainOpaquePass3dNode {
label: Some("main_opaque_pass_3d"),
// NOTE: The opaque pass loads the color
// buffer as well as writing to it.
color_attachments: &[Some(target.get_color_attachment(Operations {
load: match camera_3d.clear_color {
ClearColorConfig::Default => {
LoadOp::Clear(world.resource::<ClearColor>().0.into())
}
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
ClearColorConfig::None => LoadOp::Load,
},
store: true,
}))],
color_attachments: &[Some(
target.get_color_attachment(Operations { load, store: true }),
)],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The opaque main pass loads the depth buffer and possibly overwrites it
@ -86,6 +92,7 @@ impl ViewNode for MainOpaquePass3dNode {
load: if depth_prepass.is_some()
|| normal_prepass.is_some()
|| motion_vector_prepass.is_some()
|| deferred_prepass.is_some()
{
// if any prepass runs, it will generate a depth buffer so we should use it,
// even if only the normal_prepass is used.

View file

@ -10,6 +10,9 @@ pub mod graph {
pub mod node {
pub const MSAA_WRITEBACK: &str = "msaa_writeback";
pub const PREPASS: &str = "prepass";
pub const DEFERRED_PREPASS: &str = "deferred_prepass";
pub const COPY_DEFERRED_LIGHTING_ID: &str = "copy_deferred_lighting_id";
pub const END_PREPASSES: &str = "end_prepasses";
pub const START_MAIN_PASS: &str = "start_main_pass";
pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass";
pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass";
@ -24,13 +27,16 @@ pub mod graph {
}
pub const CORE_3D: &str = graph::NAME;
// PERF: vulkan docs recommend using 24 bit depth for better performance
pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
use std::{cmp::Reverse, ops::Range};
pub use camera_3d::*;
pub use main_opaque_pass_3d_node::*;
pub use main_transparent_pass_3d_node::*;
use bevy_app::{App, Plugin};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::prelude::*;
use bevy_render::{
camera::{Camera, ExtractedCamera},
@ -50,12 +56,17 @@ use bevy_render::{
view::ViewDepthTexture,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{nonmax::NonMaxU32, FloatOrd, HashMap};
use bevy_utils::{nonmax::NonMaxU32, tracing::warn, FloatOrd, HashMap};
use crate::{
deferred::{
copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode,
AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT,
DEFERRED_PREPASS_FORMAT,
},
prepass::{
node::PrepassNode, AlphaMask3dPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
Opaque3dPrepass, ViewPrepassTextures, DEPTH_PREPASS_FORMAT, MOTION_VECTOR_PREPASS_FORMAT,
node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass,
NormalPrepass, Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT,
NORMAL_PREPASS_FORMAT,
},
skybox::SkyboxPlugin,
@ -69,7 +80,8 @@ impl Plugin for Core3dPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Camera3d>()
.register_type::<Camera3dDepthLoadOp>()
.add_plugins((SkyboxPlugin, ExtractComponentPlugin::<Camera3d>::default()));
.add_plugins((SkyboxPlugin, ExtractComponentPlugin::<Camera3d>::default()))
.add_systems(PostUpdate, check_msaa);
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
@ -82,6 +94,8 @@ impl Plugin for Core3dPlugin {
.init_resource::<DrawFunctions<Transparent3d>>()
.init_resource::<DrawFunctions<Opaque3dPrepass>>()
.init_resource::<DrawFunctions<AlphaMask3dPrepass>>()
.init_resource::<DrawFunctions<Opaque3dDeferred>>()
.init_resource::<DrawFunctions<AlphaMask3dDeferred>>()
.add_systems(ExtractSchedule, extract_core_3d_camera_phases)
.add_systems(ExtractSchedule, extract_camera_prepass_phase)
.add_systems(
@ -92,6 +106,8 @@ impl Plugin for Core3dPlugin {
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Opaque3dDeferred>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dDeferred>.in_set(RenderSet::PhaseSort),
prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
prepare_prepass_textures.in_set(RenderSet::PrepareResources),
),
@ -101,6 +117,15 @@ impl Plugin for Core3dPlugin {
render_app
.add_render_sub_graph(CORE_3D)
.add_render_graph_node::<ViewNodeRunner<PrepassNode>>(CORE_3D, PREPASS)
.add_render_graph_node::<ViewNodeRunner<DeferredGBufferPrepassNode>>(
CORE_3D,
DEFERRED_PREPASS,
)
.add_render_graph_node::<ViewNodeRunner<CopyDeferredLightingIdNode>>(
CORE_3D,
COPY_DEFERRED_LIGHTING_ID,
)
.add_render_graph_node::<EmptyNode>(CORE_3D, END_PREPASSES)
.add_render_graph_node::<EmptyNode>(CORE_3D, START_MAIN_PASS)
.add_render_graph_node::<ViewNodeRunner<MainOpaquePass3dNode>>(
CORE_3D,
@ -118,6 +143,9 @@ impl Plugin for Core3dPlugin {
CORE_3D,
&[
PREPASS,
DEFERRED_PREPASS,
COPY_DEFERRED_LIGHTING_ID,
END_PREPASSES,
START_MAIN_PASS,
MAIN_OPAQUE_PASS,
MAIN_TRANSPARENT_PASS,
@ -341,12 +369,14 @@ pub fn extract_camera_prepass_phase(
Option<&DepthPrepass>,
Option<&NormalPrepass>,
Option<&MotionVectorPrepass>,
Option<&DeferredPrepass>,
),
With<Camera3d>,
>,
>,
) {
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass) in cameras_3d.iter()
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
cameras_3d.iter()
{
if camera.is_active {
let mut entity = commands.get_or_spawn(entity);
@ -361,6 +391,13 @@ pub fn extract_camera_prepass_phase(
));
}
if deferred_prepass.is_some() {
entity.insert((
RenderPhase::<Opaque3dDeferred>::default(),
RenderPhase::<AlphaMask3dDeferred>::default(),
));
}
if depth_prepass.is_some() {
entity.insert(DepthPrepass);
}
@ -370,6 +407,9 @@ pub fn extract_camera_prepass_phase(
if motion_vector_prepass.is_some() {
entity.insert(MotionVectorPrepass);
}
if deferred_prepass.is_some() {
entity.insert(DeferredPrepass);
}
}
}
}
@ -428,8 +468,7 @@ pub fn prepare_core_3d_depth_textures(
mip_level_count: 1,
sample_count: msaa.samples(),
dimension: TextureDimension::D2,
// PERF: vulkan docs recommend using 24 bit depth for better performance
format: TextureFormat::Depth32Float,
format: CORE_3D_DEPTH_FORMAT,
usage,
view_formats: &[],
};
@ -445,6 +484,22 @@ pub fn prepare_core_3d_depth_textures(
}
}
// Disable MSAA and warn if using deferred rendering
pub fn check_msaa(
mut msaa: ResMut<Msaa>,
deferred_views: Query<Entity, (With<Camera>, With<DeferredPrepass>)>,
) {
if !deferred_views.is_empty() {
match *msaa {
Msaa::Off => (),
_ => {
warn!("MSAA is incompatible with deferred rendering and has been disabled.");
*msaa = Msaa::Off;
}
};
}
}
// Prepares the textures used by the prepass
pub fn prepare_prepass_textures(
mut commands: Commands,
@ -458,6 +513,7 @@ pub fn prepare_prepass_textures(
Option<&DepthPrepass>,
Option<&NormalPrepass>,
Option<&MotionVectorPrepass>,
Option<&DeferredPrepass>,
),
(
With<RenderPhase<Opaque3dPrepass>>,
@ -467,8 +523,12 @@ pub fn prepare_prepass_textures(
) {
let mut depth_textures = HashMap::default();
let mut normal_textures = HashMap::default();
let mut deferred_textures = HashMap::default();
let mut deferred_lighting_id_textures = HashMap::default();
let mut motion_vectors_textures = HashMap::default();
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass) in &views_3d {
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
&views_3d
{
let Some(physical_target_size) = camera.physical_target_size else {
continue;
};
@ -489,7 +549,7 @@ pub fn prepare_prepass_textures(
mip_level_count: 1,
sample_count: msaa.samples(),
dimension: TextureDimension::D2,
format: DEPTH_PREPASS_FORMAT,
format: CORE_3D_DEPTH_FORMAT,
usage: TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING,
@ -544,10 +604,56 @@ pub fn prepare_prepass_textures(
.clone()
});
let cached_deferred_texture = deferred_prepass.is_some().then(|| {
deferred_textures
.entry(camera.target.clone())
.or_insert_with(|| {
texture_cache.get(
&render_device,
TextureDescriptor {
label: Some("prepass_deferred_texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: DEFERRED_PREPASS_FORMAT,
usage: TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
)
})
.clone()
});
let deferred_lighting_pass_id_texture = deferred_prepass.is_some().then(|| {
deferred_lighting_id_textures
.entry(camera.target.clone())
.or_insert_with(|| {
texture_cache.get(
&render_device,
TextureDescriptor {
label: Some("deferred_lighting_pass_id_texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: DEFERRED_LIGHTING_PASS_ID_FORMAT,
usage: TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
)
})
.clone()
});
commands.entity(entity).insert(ViewPrepassTextures {
depth: cached_depth_texture,
normal: cached_normals_texture,
motion_vectors: cached_motion_vectors_texture,
deferred: cached_deferred_texture,
deferred_lighting_pass_id: deferred_lighting_pass_id_texture,
size,
});
}

View file

@ -0,0 +1,19 @@
#import bevy_pbr::utils
#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput
@group(0) @binding(0)
var material_id_texture: texture_2d<u32>;
struct FragmentOutput {
@builtin(frag_depth) frag_depth: f32,
}
@fragment
fn fragment(in: FullscreenVertexOutput) -> FragmentOutput {
var out: FragmentOutput;
// Depth is stored as unorm, so we are dividing the u8 by 255.0 here.
out.frag_depth = f32(textureLoad(material_id_texture, vec2<i32>(in.position.xy), 0).x) / 255.0;
return out;
}

View file

@ -0,0 +1,224 @@
use crate::{
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
prepass::{DeferredPrepass, ViewPrepassTextures},
};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::prelude::*;
use bevy_math::UVec2;
use bevy_render::{
camera::ExtractedCamera,
render_resource::*,
renderer::RenderDevice,
texture::{CachedTexture, TextureCache},
view::ViewTarget,
Render, RenderApp, RenderSet,
};
use bevy_ecs::query::QueryItem;
use bevy_render::{
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{
BindGroupDescriptor, BindGroupEntry, BindingResource, Operations, PipelineCache,
RenderPassDescriptor,
},
renderer::RenderContext,
};
use super::DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT;
pub const COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(5230948520734987);
pub struct CopyDeferredLightingIdPlugin;
impl Plugin for CopyDeferredLightingIdPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE,
"copy_deferred_lighting_id.wgsl",
Shader::from_wgsl
);
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.add_systems(
Render,
(prepare_deferred_lighting_id_textures.in_set(RenderSet::PrepareResources),),
);
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<CopyDeferredLightingIdPipeline>();
}
}
#[derive(Default)]
pub struct CopyDeferredLightingIdNode;
impl CopyDeferredLightingIdNode {
pub const NAME: &str = "copy_deferred_lighting_id";
}
impl ViewNode for CopyDeferredLightingIdNode {
type ViewQuery = (
&'static ViewTarget,
&'static ViewPrepassTextures,
&'static DeferredLightingIdDepthTexture,
);
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
(_view_target, view_prepass_textures, deferred_lighting_id_depth_texture): QueryItem<
Self::ViewQuery,
>,
world: &World,
) -> Result<(), NodeRunError> {
let copy_deferred_lighting_id_pipeline = world.resource::<CopyDeferredLightingIdPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let Some(pipeline) =
pipeline_cache.get_render_pipeline(copy_deferred_lighting_id_pipeline.pipeline_id)
else {
return Ok(());
};
let Some(deferred_lighting_pass_id_texture) =
&view_prepass_textures.deferred_lighting_pass_id
else {
return Ok(());
};
let bind_group = render_context
.render_device()
.create_bind_group(&BindGroupDescriptor {
label: Some("copy_deferred_lighting_id_bind_group"),
layout: &copy_deferred_lighting_id_pipeline.layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(
&deferred_lighting_pass_id_texture.default_view,
),
}],
});
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("copy_deferred_lighting_id_pass"),
color_attachments: &[],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &deferred_lighting_id_depth_texture.texture.default_view,
depth_ops: Some(Operations {
load: LoadOp::Clear(0.0),
store: true,
}),
stencil_ops: None,
}),
});
render_pass.set_render_pipeline(pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}
#[derive(Resource)]
struct CopyDeferredLightingIdPipeline {
layout: BindGroupLayout,
pipeline_id: CachedRenderPipelineId,
}
impl FromWorld for CopyDeferredLightingIdPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("copy_deferred_lighting_id_bind_group_layout"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Uint,
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
}],
});
let pipeline_id =
world
.resource_mut::<PipelineCache>()
.queue_render_pipeline(RenderPipelineDescriptor {
label: Some("copy_deferred_lighting_id_pipeline".into()),
layout: vec![layout.clone()],
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Always,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
});
Self {
layout,
pipeline_id,
}
}
}
#[derive(Component)]
pub struct DeferredLightingIdDepthTexture {
pub texture: CachedTexture,
}
fn prepare_deferred_lighting_id_textures(
mut commands: Commands,
mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>,
views: Query<(Entity, &ExtractedCamera), With<DeferredPrepass>>,
) {
for (entity, camera) in &views {
if let Some(UVec2 {
x: width,
y: height,
}) = camera.physical_target_size
{
let texture_descriptor = TextureDescriptor {
label: Some("deferred_lighting_id_depth_texture_a"),
size: Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
view_formats: &[],
};
let texture = texture_cache.get(&render_device, texture_descriptor);
commands
.entity(entity)
.insert(DeferredLightingIdDepthTexture { texture });
}
}
}

View file

@ -0,0 +1,149 @@
pub mod copy_lighting_id;
pub mod node;
use std::{cmp::Reverse, ops::Range};
use bevy_ecs::prelude::*;
use bevy_render::{
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem},
render_resource::{CachedRenderPipelineId, TextureFormat},
};
use bevy_utils::{nonmax::NonMaxU32, FloatOrd};
pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint;
pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint;
pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
/// Opaque phase of the 3D Deferred pass.
///
/// Sorted front-to-back by the z-distance in front of the camera.
///
/// Used to render all 3D meshes with materials that have no transparency.
pub struct Opaque3dDeferred {
pub distance: f32,
pub entity: Entity,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>,
}
impl PhaseItem for Opaque3dDeferred {
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
type SortKey = Reverse<FloatOrd>;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
// Key negated to match reversed SortKey ordering
radsort::sort_by_key(items, |item| -item.distance);
}
#[inline]
fn batch_range(&self) -> &Range<u32> {
&self.batch_range
}
#[inline]
fn batch_range_mut(&mut self) -> &mut Range<u32> {
&mut self.batch_range
}
#[inline]
fn dynamic_offset(&self) -> Option<NonMaxU32> {
self.dynamic_offset
}
#[inline]
fn dynamic_offset_mut(&mut self) -> &mut Option<NonMaxU32> {
&mut self.dynamic_offset
}
}
impl CachedRenderPipelinePhaseItem for Opaque3dDeferred {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id
}
}
/// Alpha mask phase of the 3D Deferred pass.
///
/// Sorted front-to-back by the z-distance in front of the camera.
///
/// Used to render all meshes with a material with an alpha mask.
pub struct AlphaMask3dDeferred {
pub distance: f32,
pub entity: Entity,
pub pipeline_id: CachedRenderPipelineId,
pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>,
pub dynamic_offset: Option<NonMaxU32>,
}
impl PhaseItem for AlphaMask3dDeferred {
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
type SortKey = Reverse<FloatOrd>;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
// Key negated to match reversed SortKey ordering
radsort::sort_by_key(items, |item| -item.distance);
}
#[inline]
fn batch_range(&self) -> &Range<u32> {
&self.batch_range
}
#[inline]
fn batch_range_mut(&mut self) -> &mut Range<u32> {
&mut self.batch_range
}
#[inline]
fn dynamic_offset(&self) -> Option<NonMaxU32> {
self.dynamic_offset
}
#[inline]
fn dynamic_offset_mut(&mut self) -> &mut Option<NonMaxU32> {
&mut self.dynamic_offset
}
}
impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline_id
}
}

View file

@ -0,0 +1,205 @@
use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryItem;
use bevy_render::render_graph::ViewNode;
use bevy_render::{
camera::ExtractedCamera,
prelude::Color,
render_graph::{NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
render_resource::{
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPassDescriptor,
},
renderer::RenderContext,
view::ViewDepthTexture,
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
use crate::core_3d::{Camera3d, Camera3dDepthLoadOp};
use crate::prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass, ViewPrepassTextures};
use super::{AlphaMask3dDeferred, Opaque3dDeferred};
/// Render node used by the prepass.
///
/// By default, inserted before the main pass in the render graph.
#[derive(Default)]
pub struct DeferredGBufferPrepassNode;
impl ViewNode for DeferredGBufferPrepassNode {
type ViewQuery = (
&'static ExtractedCamera,
&'static RenderPhase<Opaque3dDeferred>,
&'static RenderPhase<AlphaMask3dDeferred>,
&'static ViewDepthTexture,
&'static ViewPrepassTextures,
&'static Camera3d,
Option<&'static DepthPrepass>,
Option<&'static NormalPrepass>,
Option<&'static MotionVectorPrepass>,
);
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
(
camera,
opaque_deferred_phase,
alpha_mask_deferred_phase,
view_depth_texture,
view_prepass_textures,
camera_3d,
depth_prepass,
normal_prepass,
motion_vector_prepass,
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let mut color_attachments = vec![];
color_attachments.push(
view_prepass_textures
.normal
.as_ref()
.map(|view_normals_texture| RenderPassColorAttachment {
view: &view_normals_texture.default_view,
resolve_target: None,
ops: Operations {
load: if normal_prepass.is_some() {
// Load if the normal_prepass has already run.
// The prepass will have already cleared this for the current frame.
LoadOp::Load
} else {
LoadOp::Clear(Color::BLACK.into())
},
store: true,
},
}),
);
color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map(
|view_motion_vectors_texture| RenderPassColorAttachment {
view: &view_motion_vectors_texture.default_view,
resolve_target: None,
ops: Operations {
load: if motion_vector_prepass.is_some() {
// Load if the motion_vector_prepass has already run.
// The prepass will have already cleared this for the current frame.
LoadOp::Load
} else {
LoadOp::Clear(Color::BLACK.into())
},
store: true,
},
},
));
// If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors:
// Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format.
// Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT.
// Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9
// For webgl2 we fallback to manually clearing
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
if let Some(deferred_texture) = &view_prepass_textures.deferred {
render_context.command_encoder().clear_texture(
&deferred_texture.texture,
&bevy_render::render_resource::ImageSubresourceRange::default(),
);
}
color_attachments.push(
view_prepass_textures
.deferred
.as_ref()
.map(|deferred_texture| RenderPassColorAttachment {
view: &deferred_texture.default_view,
resolve_target: None,
ops: Operations {
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
load: LoadOp::Load,
#[cfg(not(all(feature = "webgl", target_arch = "wasm32")))]
load: LoadOp::Clear(Default::default()),
store: true,
},
}),
);
color_attachments.push(
view_prepass_textures
.deferred_lighting_pass_id
.as_ref()
.map(|deferred_lighting_pass_id| RenderPassColorAttachment {
view: &deferred_lighting_pass_id.default_view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Default::default()),
store: true,
},
}),
);
if color_attachments.iter().all(Option::is_none) {
// All attachments are none: clear the attachment list so that no fragment shader is required.
color_attachments.clear();
}
{
// Set up the pass descriptor with the depth attachment and optional color attachments.
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("deferred"),
color_attachments: &color_attachments,
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &view_depth_texture.view,
depth_ops: Some(Operations {
load: if depth_prepass.is_some()
|| normal_prepass.is_some()
|| motion_vector_prepass.is_some()
{
// If any prepass runs, it will generate a depth buffer so we should use it.
Camera3dDepthLoadOp::Load
} else {
// NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections.
camera_3d.depth_load_op.clone()
}
.into(),
store: true,
}),
stencil_ops: None,
}),
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
}
// Always run deferred pass to ensure the deferred gbuffer and deferred_lighting_pass_id are cleared.
{
// Run the prepass, sorted front-to-back.
#[cfg(feature = "trace")]
let _opaque_prepass_span = info_span!("opaque_deferred").entered();
opaque_deferred_phase.render(&mut render_pass, world, view_entity);
}
if !alpha_mask_deferred_phase.items.is_empty() {
// Run the deferred, sorted front-to-back.
#[cfg(feature = "trace")]
let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered();
alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity);
}
}
if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
// Copy depth buffer to texture.
render_context.command_encoder().copy_texture_to_texture(
view_depth_texture.texture.as_image_copy(),
prepass_depth_texture.texture.as_image_copy(),
view_prepass_textures.size,
);
}
Ok(())
}
}

View file

@ -6,6 +6,7 @@ pub mod clear_color;
pub mod contrast_adaptive_sharpening;
pub mod core_2d;
pub mod core_3d;
pub mod deferred;
pub mod fullscreen_vertex_shader;
pub mod fxaa;
pub mod msaa_writeback;
@ -40,6 +41,7 @@ use crate::{
contrast_adaptive_sharpening::CASPlugin,
core_2d::Core2dPlugin,
core_3d::Core3dPlugin,
deferred::copy_lighting_id::CopyDeferredLightingIdPlugin,
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
fxaa::FxaaPlugin,
msaa_writeback::MsaaWritebackPlugin,
@ -72,6 +74,7 @@ impl Plugin for CorePipelinePlugin {
ExtractResourcePlugin::<ClearColor>::default(),
Core2dPlugin,
Core3dPlugin,
CopyDeferredLightingIdPlugin,
BlitPlugin,
MsaaWritebackPlugin,
TonemappingPlugin,

View file

@ -38,7 +38,6 @@ use bevy_render::{
};
use bevy_utils::{nonmax::NonMaxU32, FloatOrd};
pub const DEPTH_PREPASS_FORMAT: TextureFormat = TextureFormat::Depth32Float;
pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm;
pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float;
@ -55,6 +54,10 @@ pub struct NormalPrepass;
#[derive(Component, Default, Reflect)]
pub struct MotionVectorPrepass;
/// If added to a [`crate::prelude::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes.
#[derive(Component, Default, Reflect)]
pub struct DeferredPrepass;
/// Textures that are written to by the prepass.
///
/// This component will only be present if any of the relevant prepass components are also present.
@ -69,6 +72,12 @@ pub struct ViewPrepassTextures {
/// The motion vectors texture generated by the prepass.
/// Exists only if [`MotionVectorPrepass`] is added to the `ViewTarget`
pub motion_vectors: Option<CachedTexture>,
/// The deferred gbuffer generated by the deferred pass.
/// Exists only if [`DeferredPrepass`] is added to the `ViewTarget`
pub deferred: Option<CachedTexture>,
/// A texture that specifies the deferred lighting pass id for a material.
/// Exists only if [`DeferredPrepass`] is added to the `ViewTarget`
pub deferred_lighting_pass_id: Option<CachedTexture>,
/// The size of the textures.
pub size: Extent3d,
}

View file

@ -16,7 +16,7 @@ use bevy_render::{
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
use super::{AlphaMask3dPrepass, Opaque3dPrepass, ViewPrepassTextures};
use super::{AlphaMask3dPrepass, DeferredPrepass, Opaque3dPrepass, ViewPrepassTextures};
/// Render node used by the prepass.
///
@ -31,6 +31,7 @@ impl ViewNode for PrepassNode {
&'static RenderPhase<AlphaMask3dPrepass>,
&'static ViewDepthTexture,
&'static ViewPrepassTextures,
Option<&'static DeferredPrepass>,
);
fn run(
@ -43,13 +44,13 @@ impl ViewNode for PrepassNode {
alpha_mask_prepass_phase,
view_depth_texture,
view_prepass_textures,
deferred_prepass,
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let mut color_attachments = vec![];
color_attachments.push(
let mut color_attachments = vec![
view_prepass_textures
.normal
.as_ref()
@ -61,20 +62,25 @@ impl ViewNode for PrepassNode {
store: true,
},
}),
);
color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map(
|view_motion_vectors_texture| RenderPassColorAttachment {
view: &view_motion_vectors_texture.default_view,
resolve_target: None,
ops: Operations {
// Red and Green channels are X and Y components of the motion vectors
// Blue channel doesn't matter, but set to 0.0 for possible faster clear
// https://gpuopen.com/performance/#clears
load: LoadOp::Clear(Color::rgb_linear(0.0, 0.0, 0.0).into()),
store: true,
},
},
));
view_prepass_textures
.motion_vectors
.as_ref()
.map(|view_motion_vectors_texture| RenderPassColorAttachment {
view: &view_motion_vectors_texture.default_view,
resolve_target: None,
ops: Operations {
// Red and Green channels are X and Y components of the motion vectors
// Blue channel doesn't matter, but set to 0.0 for possible faster clear
// https://gpuopen.com/performance/#clears
load: LoadOp::Clear(Color::BLACK.into()),
store: true,
},
}),
// Use None in place of Deferred attachments
None,
None,
];
if color_attachments.iter().all(Option::is_none) {
// all attachments are none: clear the attachment list so that no fragment shader is required
color_attachments.clear();
@ -113,16 +119,17 @@ impl ViewNode for PrepassNode {
alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity);
}
}
if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
// Copy depth buffer to texture
render_context.command_encoder().copy_texture_to_texture(
view_depth_texture.texture.as_image_copy(),
prepass_depth_texture.texture.as_image_copy(),
view_prepass_textures.size,
);
if deferred_prepass.is_none() {
// Copy if deferred isn't going to
if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
// Copy depth buffer to texture
render_context.command_encoder().copy_texture_to_texture(
view_depth_texture.texture.as_image_copy(),
prepass_depth_texture.texture.as_image_copy(),
view_prepass_textures.size,
);
}
}
Ok(())
}
}

View file

@ -11,7 +11,7 @@ use bevy_render::{
render_asset::RenderAssets,
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType,
CachedRenderPipelineId, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
DepthStencilState, FragmentState, MultisampleState, PipelineCache, PrimitiveState,
RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages, ShaderType,
@ -24,6 +24,8 @@ use bevy_render::{
Render, RenderApp, RenderSet,
};
use crate::core_3d::CORE_3D_DEPTH_FORMAT;
const SKYBOX_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(55594763423201);
pub struct SkyboxPlugin;
@ -121,6 +123,7 @@ impl SkyboxPipeline {
struct SkyboxPipelineKey {
hdr: bool,
samples: u32,
depth_format: TextureFormat,
}
impl SpecializedRenderPipeline for SkyboxPipeline {
@ -139,7 +142,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline {
},
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
format: key.depth_format,
depth_write_enabled: false,
depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState {
@ -169,7 +172,8 @@ impl SpecializedRenderPipeline for SkyboxPipeline {
} else {
TextureFormat::bevy_default()
},
blend: Some(BlendState::REPLACE),
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases.
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
@ -195,6 +199,7 @@ fn prepare_skybox_pipelines(
SkyboxPipelineKey {
hdr: view.hdr,
samples: msaa.samples(),
depth_format: CORE_3D_DEPTH_FORMAT,
},
);

View file

@ -4,7 +4,7 @@ use crate::{
};
use bevy_app::{App, Plugin};
use bevy_asset::Handle;
use bevy_core_pipeline::core_3d::Transparent3d;
use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT};
use bevy_ecs::{
prelude::Entity,
@ -121,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),

View file

@ -0,0 +1,95 @@
#import bevy_pbr::prepass_utils
#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT
#import bevy_pbr::pbr_functions as pbr_functions
#import bevy_pbr::pbr_deferred_types as deferred_types
#import bevy_pbr::pbr_deferred_functions pbr_input_from_deferred_gbuffer, unpack_unorm3x4_plus_unorm_20_
#import bevy_pbr::mesh_view_types FOG_MODE_OFF
#import bevy_pbr::mesh_view_bindings deferred_prepass_texture, fog, view, screen_space_ambient_occlusion_texture
#import bevy_core_pipeline::tonemapping screen_space_dither, powsafe, tone_mapping
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
#import bevy_pbr::gtao_utils gtao_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(1) @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 = deferred_types::unpack_unorm3x4_plus_unorm_20_(deferred_data.b).w;
#else
frag_coord.z = bevy_pbr::prepass_utils::prepass_depth(in.position, 0u);
#endif
var pbr_input = pbr_input_from_deferred_gbuffer(frag_coord, deferred_data);
var output_color = vec4(0.0);
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb);
pbr_input.occlusion = min(pbr_input.occlusion, ssao_multibounce);
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
// 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) {
output_color = pbr_functions::pbr(pbr_input);
} else {
output_color = pbr_input.material.base_color;
}
// fog
if (fog.mode != FOG_MODE_OFF && (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
output_color = pbr_functions::apply_fog(fog, output_color, pbr_input.world_position.xyz, view.world_position.xyz);
}
#ifdef TONEMAP_IN_SHADER
output_color = tone_mapping(output_color, view.color_grading);
#ifdef DEBAND_DITHER
var output_rgb = output_color.rgb;
output_rgb = powsafe(output_rgb, 1.0 / 2.2);
output_rgb = output_rgb + screen_space_dither(frag_coord.xy);
// This conversion back to linear space is required because our output texture format is
// SRGB; the GPU will assume our output is linear and will apply an SRGB conversion.
output_rgb = powsafe(output_rgb, 2.2);
output_color = vec4(output_rgb, output_color.a);
#endif
#endif
#ifdef PREMULTIPLY_ALPHA
output_color = pbr_functions::premultiply_alpha(material.flags, output_color);
#endif
return output_color;
}

View file

@ -0,0 +1,473 @@
use crate::{MeshPipeline, MeshViewBindGroup, ScreenSpaceAmbientOcclusionSettings};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::{
clear_color::ClearColorConfig,
core_3d,
deferred::{
copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
},
prelude::{Camera3d, ClearColor},
prepass::DeferredPrepass,
tonemapping::{DebandDither, Tonemapping},
};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{
extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
},
render_asset::RenderAssets,
render_graph::{NodeRunError, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::{self, Operations, PipelineCache, RenderPassDescriptor},
renderer::{RenderContext, RenderDevice},
texture::Image,
view::{ViewTarget, ViewUniformOffset},
Render, RenderSet,
};
use bevy_render::{
render_graph::RenderGraphApp, render_resource::*, texture::BevyDefault, view::ExtractedView,
RenderApp,
};
use crate::{
EnvironmentMapLight, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
ViewLightsUniformOffset,
};
pub struct DeferredPbrLightingPlugin;
pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(2708011359337029741);
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"))]
_webgl2_padding_0: f32,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_webgl2_padding_1: f32,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_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"))]
_webgl2_padding_0: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_webgl2_padding_1: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_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"))]
_webgl2_padding_0: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_webgl2_padding_1: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
_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);
load_internal_asset!(
app,
DEFERRED_LIGHTING_SHADER_HANDLE,
"deferred_lighting.wgsl",
Shader::from_wgsl
);
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<SpecializedRenderPipelines<DeferredLightingLayout>>()
.add_systems(
Render,
(prepare_deferred_lighting_pipelines.in_set(RenderSet::Prepare),),
)
.add_render_graph_node::<ViewNodeRunner<DeferredOpaquePass3dPbrLightingNode>>(
core_3d::graph::NAME,
DEFERRED_LIGHTING_PASS,
)
.add_render_graph_edges(
core_3d::graph::NAME,
&[
core_3d::graph::node::START_MAIN_PASS,
DEFERRED_LIGHTING_PASS,
core_3d::graph::node::MAIN_OPAQUE_PASS,
],
);
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<DeferredLightingLayout>();
}
}
pub const DEFERRED_LIGHTING_PASS: &str = "deferred_opaque_pbr_lighting_pass_3d";
#[derive(Default)]
pub struct DeferredOpaquePass3dPbrLightingNode;
impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
type ViewQuery = (
&'static ViewUniformOffset,
&'static ViewLightsUniformOffset,
&'static ViewFogUniformOffset,
&'static MeshViewBindGroup,
&'static ViewTarget,
&'static DeferredLightingIdDepthTexture,
&'static Camera3d,
&'static DeferredLightingPipeline,
);
fn run(
&self,
_graph_context: &mut RenderGraphContext,
render_context: &mut RenderContext,
(
view_uniform_offset,
view_lights_offset,
view_fog_offset,
mesh_view_bind_group,
target,
deferred_lighting_id_depth_texture,
camera_3d,
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 bind_group_1 = render_context
.render_device()
.create_bind_group(&BindGroupDescriptor {
label: Some("deferred_lighting_layout_group_1"),
layout: &deferred_lighting_layout.bind_group_layout_1,
entries: &[BindGroupEntry {
binding: 0,
resource: deferred_lighting_pass_id_binding.clone(),
}],
});
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("deferred_lighting_pass"),
color_attachments: &[Some(target.get_color_attachment(Operations {
load: match camera_3d.clear_color {
ClearColorConfig::Default => {
LoadOp::Clear(world.resource::<ClearColor>().0.into())
}
ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
ClearColorConfig::None => LoadOp::Load,
},
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &deferred_lighting_id_depth_texture.texture.default_view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: false,
}),
stencil_ops: None,
}),
});
render_pass.set_render_pipeline(pipeline);
render_pass.set_bind_group(
0,
&mesh_view_bind_group.value,
&[
view_uniform_offset.offset,
view_lights_offset.offset,
view_fog_offset.offset,
],
);
render_pass.set_bind_group(1, &bind_group_1, &[]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}
#[derive(Resource)]
pub struct DeferredLightingLayout {
bind_group_layout_0: BindGroupLayout,
bind_group_layout_1: BindGroupLayout,
}
#[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();
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
shader_defs.push("WEBGL2".into());
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
shader_defs.push("TONEMAP_IN_SHADER".into());
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());
}
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_CASTANO_13 {
shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 {
shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into());
}
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
RenderPipelineDescriptor {
label: Some("deferred_lighting_pipeline".into()),
layout: vec![
self.bind_group_layout_0.clone(),
self.bind_group_layout_1.clone(),
],
vertex: VertexState {
shader: DEFERRED_LIGHTING_SHADER_HANDLE,
shader_defs: shader_defs.clone(),
entry_point: "vertex".into(),
buffers: Vec::new(),
},
fragment: Some(FragmentState {
shader: DEFERRED_LIGHTING_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: if key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::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,
},
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
}
}
}
impl FromWorld for DeferredLightingLayout {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("deferred_lighting_layout"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX_FRAGMENT,
ty: BindingType::Buffer {
ty: render_resource::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: Some(PbrDeferredLightingDepthId::min_size()),
},
count: None,
}],
});
Self {
bind_group_layout_0: world.resource::<MeshPipeline>().view_layout.clone(),
bind_group_layout_1: layout,
}
}
}
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<&EnvironmentMapLight>,
Option<&ShadowFilteringMethod>,
Option<&ScreenSpaceAmbientOcclusionSettings>,
),
With<DeferredPrepass>,
>,
images: Res<RenderAssets<Image>>,
) {
for (entity, view, tonemapping, dither, environment_map, shadow_filter_method, ssao) in &views {
let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
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.is_some() {
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
}
let environment_map_loaded = match environment_map {
Some(environment_map) => environment_map.is_loaded(&images),
None => false,
};
if environment_map_loaded {
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
}
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
ShadowFilteringMethod::Hardware2x2 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
}
ShadowFilteringMethod::Castano13 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13;
}
ShadowFilteringMethod::Jimenez14 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14;
}
}
let pipeline_id =
pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key);
commands
.entity(entity)
.insert(DeferredLightingPipeline { pipeline_id });
}
}

View file

@ -0,0 +1,129 @@
#define_import_path bevy_pbr::pbr_deferred_functions
#import bevy_pbr::pbr_types PbrInput, standard_material_new, STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT
#import bevy_pbr::pbr_deferred_types as deferred_types
#import bevy_pbr::pbr_functions as pbr_functions
#import bevy_pbr::rgb9e5 as rgb9e5
#import bevy_pbr::mesh_view_bindings as view_bindings
#import bevy_pbr::mesh_view_bindings view
#import bevy_pbr::utils octahedral_encode, octahedral_decode
// ---------------------------
// from https://github.com/DGriffin91/bevy_coordinate_systems/blob/main/src/transformations.wgsl
// ---------------------------
/// Convert a ndc space position to world space
fn position_ndc_to_world(ndc_pos: vec3<f32>) -> vec3<f32> {
let world_pos = view.inverse_view_proj * vec4(ndc_pos, 1.0);
return world_pos.xyz / world_pos.w;
}
/// Convert ndc space xy coordinate [-1.0 .. 1.0] to uv [0.0 .. 1.0]
fn ndc_to_uv(ndc: vec2<f32>) -> vec2<f32> {
return ndc * vec2(0.5, -0.5) + vec2(0.5);
}
/// Convert uv [0.0 .. 1.0] coordinate to ndc space xy [-1.0 .. 1.0]
fn uv_to_ndc(uv: vec2<f32>) -> vec2<f32> {
return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
}
/// Returns the (0.0, 0.0) .. (1.0, 1.0) position within the viewport for the current render target.
/// [0 .. render target viewport size] eg. [(0.0, 0.0) .. (1280.0, 720.0)] to [(0.0, 0.0) .. (1.0, 1.0)]
fn frag_coord_to_uv(frag_coord: vec2<f32>) -> vec2<f32> {
return (frag_coord - view.viewport.xy) / view.viewport.zw;
}
/// Convert frag coord to ndc.
fn frag_coord_to_ndc(frag_coord: vec4<f32>) -> vec3<f32> {
return vec3(uv_to_ndc(frag_coord_to_uv(frag_coord.xy)), frag_coord.z);
}
// 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 occlusion = dot(in.occlusion, vec3<f32>(0.2126, 0.7152, 0.0722));
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
in.material.reflectance,
in.material.metallic,
occlusion,
in.frag_coord.z));
#else
var props = deferred_types::pack_unorm4x8_(vec4(
in.material.reflectance, // could be fewer bits
in.material.metallic, // could be fewer bits
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));
}
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: PbrInput;
pbr.material = standard_material_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), 1.0);
} else {
pbr.material.base_color = vec4(pow(base_rough.rgb, vec3(2.2)), 1.0);
pbr.material.emissive = vec4(emissive, 1.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 = saturate(props.r - 0.03333333333);
#else
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
pbr.material.reflectance = props.r;
#endif // WEBGL2
pbr.material.metallic = props.g;
pbr.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.projection[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;
}

View file

@ -0,0 +1,86 @@
#define_import_path bevy_pbr::pbr_deferred_types
#import bevy_pbr::mesh_types MESH_FLAGS_SHADOW_RECEIVER_BIT
#import bevy_pbr::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;
const DEFERRED_FLAGS_FOG_ENABLED_BIT: u32 = 2u;
const DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 4u;
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,
);
}

View file

@ -4,6 +4,7 @@ pub mod wireframe;
mod alpha;
mod bundle;
pub mod deferred;
mod environment_map;
mod fog;
mod light;
@ -63,6 +64,8 @@ use bevy_render::{
use bevy_transform::TransformSystem;
use environment_map::EnvironmentMapPlugin;
use crate::deferred::DeferredPbrLightingPlugin;
pub const PBR_TYPES_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(1708015359337029744);
pub const PBR_BINDINGS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(5635987986427308186);
pub const UTILS_HANDLE: Handle<Shader> = Handle::weak_from_u128(1900548483293416725);
@ -76,18 +79,26 @@ pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(16550102
pub const PBR_AMBIENT_HANDLE: Handle<Shader> = Handle::weak_from_u128(2441520459096337034);
pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(17035894873630133905);
pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(73204817249182637);
pub const PBR_DEFERRED_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(3221241127431430599);
pub const PBR_DEFERRED_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(72019026415438599);
pub const RGB9E5_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(2659010996143919192);
/// Sets up the entire PBR infrastructure of bevy.
pub struct PbrPlugin {
/// Controls if the prepass is enabled for the StandardMaterial.
/// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
pub prepass_enabled: bool,
/// Controls if [`DeferredPbrLightingPlugin`] is added.
pub add_default_deferred_lighting_plugin: bool,
}
impl Default for PbrPlugin {
fn default() -> Self {
Self {
prepass_enabled: true,
add_default_deferred_lighting_plugin: true,
}
}
}
@ -125,6 +136,18 @@ impl Plugin for PbrPlugin {
"render/shadows.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_DEFERRED_TYPES_HANDLE,
"deferred/pbr_deferred_types.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_DEFERRED_FUNCTIONS_HANDLE,
"deferred/pbr_deferred_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
SHADOW_SAMPLING_HANDLE,
@ -137,6 +160,12 @@ impl Plugin for PbrPlugin {
"render/pbr_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
RGB9E5_FUNCTIONS_HANDLE,
"render/rgb9e5.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_AMBIENT_HANDLE,
@ -144,6 +173,12 @@ impl Plugin for PbrPlugin {
Shader::from_wgsl
);
load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
PBR_PREPASS_FUNCTIONS_SHADER_HANDLE,
"render/pbr_prepass_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_PREPASS_SHADER_HANDLE,
@ -180,6 +215,8 @@ impl Plugin for PbrPlugin {
.init_resource::<GlobalVisiblePointLights>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.register_type::<DefaultOpaqueRendererMethod>()
.init_resource::<DefaultOpaqueRendererMethod>()
.add_plugins((
MeshRenderPlugin,
MaterialPlugin::<StandardMaterial> {
@ -190,6 +227,7 @@ impl Plugin for PbrPlugin {
EnvironmentMapPlugin,
ExtractResourcePlugin::<AmbientLight>::default(),
FogPlugin,
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
))
.configure_sets(
@ -245,6 +283,10 @@ impl Plugin for PbrPlugin {
),
);
if self.add_default_deferred_lighting_plugin {
app.add_plugins(DeferredPbrLightingPlugin);
}
app.world.resource_mut::<Assets<StandardMaterial>>().insert(
Handle::<StandardMaterial>::default(),
StandardMaterial {

View file

@ -8,7 +8,7 @@ use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Hand
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
experimental::taa::TemporalAntiAliasSettings,
prepass::NormalPrepass,
prepass::{DeferredPrepass, NormalPrepass},
tonemapping::{DebandDither, Tonemapping},
};
use bevy_derive::{Deref, DerefMut};
@ -16,7 +16,9 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
use bevy_reflect::Reflect;
use bevy_render::{
extract_resource::ExtractResource,
mesh::{Mesh, MeshVertexBufferLayout},
prelude::Image,
render_asset::{prepare_assets, RenderAssets},
@ -116,8 +118,16 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
AlphaMode::Opaque
}
/// Returns if this material should be rendered by the deferred or forward renderer.
/// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials.
/// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource.
#[inline]
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
fn opaque_render_method(&self) -> OpaqueRendererMethod {
OpaqueRendererMethod::Forward
}
#[inline]
/// Add a bias to the view depth of the mesh which can be used to force a specific render order.
/// for meshes with similar depth, to avoid z-fighting.
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
fn depth_bias(&self) -> f32 {
@ -137,6 +147,19 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
ShaderRef::Default
}
/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader
/// will be used.
fn deferred_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader
/// will be used.
#[allow(unused_variables)]
fn deferred_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
#[allow(unused_variables)]
@ -422,6 +445,7 @@ pub fn queue_material_meshes<M: Material>(
Option<&ShadowFilteringMethod>,
Option<&ScreenSpaceAmbientOcclusionSettings>,
Option<&NormalPrepass>,
Option<&DeferredPrepass>,
Option<&TemporalAntiAliasSettings>,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
@ -439,6 +463,7 @@ pub fn queue_material_meshes<M: Material>(
shadow_filter_method,
ssao,
normal_prepass,
deferred_prepass,
taa_settings,
mut opaque_phase,
mut alpha_mask_phase,
@ -455,6 +480,11 @@ pub fn queue_material_meshes<M: Material>(
if normal_prepass.is_some() {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if deferred_prepass.is_some() {
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
if taa_settings.is_some() {
view_key |= MeshPipelineKey::TAA;
}
@ -502,6 +532,13 @@ pub fn queue_material_meshes<M: Material>(
let Some(material) = render_materials.get(material_asset_id) else {
continue;
};
let forward = match material.properties.render_method {
OpaqueRendererMethod::Forward => true,
OpaqueRendererMethod::Deferred => false,
OpaqueRendererMethod::Auto => unreachable!(),
};
let mut mesh_key = view_key;
mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
@ -511,6 +548,10 @@ pub fn queue_material_meshes<M: Material>(
}
mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode);
if deferred_prepass.is_some() && !forward {
mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&material_pipeline,
@ -535,24 +576,28 @@ pub fn queue_material_meshes<M: Material>(
+ material.properties.depth_bias;
match material.properties.alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
if forward {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
if forward {
alpha_mask_phase.add(AlphaMask3d {
entity: *visible_entity,
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Blend
| AlphaMode::Premultiplied
@ -572,8 +617,59 @@ pub fn queue_material_meshes<M: Material>(
}
}
/// Default render method used for opaque materials.
#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
impl DefaultOpaqueRendererMethod {
pub fn forward() -> Self {
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
}
pub fn deferred() -> Self {
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
}
pub fn set_to_forward(&mut self) {
self.0 = OpaqueRendererMethod::Forward;
}
pub fn set_to_deferred(&mut self) {
self.0 = OpaqueRendererMethod::Deferred;
}
}
/// Render method used for opaque materials.
///
/// The forward rendering main pass draws each mesh entity and shades it according to its
/// corresponding material and the lights that affect it. Some render features like Screen Space
/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like
/// prepasses over all mesh entities to populate depth and normal textures. This means that when
/// using render features that require running prepasses, multiple passes over all visible geometry
/// are required. This can be slow if there is a lot of geometry that cannot be batched into few
/// draws.
///
/// Deferred rendering runs a prepass to gather not only geometric information like depth and
/// normals, but also all the material properties like base color, emissive color, reflectance,
/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is
/// then a fullscreen pass that reads data from these textures and executes shading. This allows
/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier
/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices.
///
/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used.
#[derive(Default, Clone, Copy, Debug, Reflect)]
pub enum OpaqueRendererMethod {
#[default]
Forward,
Deferred,
Auto,
}
/// Common [`Material`] properties, calculated for a specific material instance.
pub struct MaterialProperties {
/// Is this material should be rendered by the deferred renderer when.
/// AlphaMode::Opaque or AlphaMode::Mask
pub render_method: OpaqueRendererMethod,
/// The [`AlphaMode`] of this material.
pub alpha_mode: AlphaMode,
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
@ -676,6 +772,7 @@ impl<M: Material> Default for PrepareNextFrameMaterials<M> {
/// This system prepares all assets of the corresponding [`Material`] type
/// which where extracted this frame for the GPU.
#[allow(clippy::too_many_arguments)]
pub fn prepare_materials<M: Material>(
mut prepare_next_frame: Local<PrepareNextFrameMaterials<M>>,
mut extracted_assets: ResMut<ExtractedMaterials<M>>,
@ -684,6 +781,7 @@ pub fn prepare_materials<M: Material>(
images: Res<RenderAssets<Image>>,
fallback_image: Res<FallbackImage>,
pipeline: Res<MaterialPipeline<M>>,
default_opaque_render_method: Res<DefaultOpaqueRendererMethod>,
) {
let queued_assets = std::mem::take(&mut prepare_next_frame.assets);
for (id, material) in queued_assets.into_iter() {
@ -693,6 +791,7 @@ pub fn prepare_materials<M: Material>(
&images,
&fallback_image,
&pipeline,
default_opaque_render_method.0,
) {
Ok(prepared_asset) => {
render_materials.insert(id, prepared_asset);
@ -714,6 +813,7 @@ pub fn prepare_materials<M: Material>(
&images,
&fallback_image,
&pipeline,
default_opaque_render_method.0,
) {
Ok(prepared_asset) => {
render_materials.insert(id, prepared_asset);
@ -731,6 +831,7 @@ fn prepare_material<M: Material>(
images: &RenderAssets<Image>,
fallback_image: &FallbackImage,
pipeline: &MaterialPipeline<M>,
default_opaque_render_method: OpaqueRendererMethod,
) -> Result<PreparedMaterial<M>, AsBindGroupError> {
let prepared = material.as_bind_group(
&pipeline.material_layout,
@ -738,6 +839,11 @@ fn prepare_material<M: Material>(
images,
fallback_image,
)?;
let method = match material.opaque_render_method() {
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
OpaqueRendererMethod::Auto => default_opaque_render_method,
};
Ok(PreparedMaterial {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
@ -745,6 +851,7 @@ fn prepare_material<M: Material>(
properties: MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
render_method: method,
},
})
}

View file

@ -1,6 +1,7 @@
use crate::{
AlphaMode, Material, MaterialPipeline, MaterialPipelineKey, ParallaxMappingMethod,
PBR_PREPASS_SHADER_HANDLE, PBR_SHADER_HANDLE,
deferred::DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID, AlphaMode, Material, MaterialPipeline,
MaterialPipelineKey, OpaqueRendererMethod, ParallaxMappingMethod, PBR_PREPASS_SHADER_HANDLE,
PBR_SHADER_HANDLE,
};
use bevy_asset::{Asset, Handle};
use bevy_math::Vec4;
@ -316,6 +317,14 @@ pub struct StandardMaterial {
///
/// Default is `16.0`.
pub max_parallax_layer_count: f32,
/// Render method used for opaque materials. (Where `alpha_mode` is [`AlphaMode::Opaque`] or [`AlphaMode::Mask`])
pub opaque_render_method: OpaqueRendererMethod,
/// Used for selecting the deferred lighting pass for deferred materials.
/// Default is [`DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID`] for default
/// PBR deferred lighting pass. Ignored in the case of forward materials.
pub deferred_lighting_pass_id: u8,
}
impl Default for StandardMaterial {
@ -349,6 +358,8 @@ impl Default for StandardMaterial {
parallax_depth_scale: 0.1,
max_parallax_layer_count: 16.0,
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
opaque_render_method: OpaqueRendererMethod::Auto,
deferred_lighting_pass_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID,
}
}
}
@ -441,6 +452,8 @@ pub struct StandardMaterialUniform {
/// Using [`ParallaxMappingMethod::Relief`], how many additional
/// steps to use at most to find the depth value.
pub max_relief_mapping_search_steps: u32,
/// ID for specifying which deferred lighting pass should be used for rendering this material, if any.
pub deferred_lighting_pass_id: u32,
}
impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
@ -514,6 +527,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
parallax_depth_scale: self.parallax_depth_scale,
max_parallax_layer_count: self.max_parallax_layer_count,
max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(),
deferred_lighting_pass_id: self.deferred_lighting_pass_id as u32,
}
}
}
@ -572,6 +586,10 @@ impl Material for StandardMaterial {
PBR_PREPASS_SHADER_HANDLE.into()
}
fn deferred_fragment_shader() -> ShaderRef {
PBR_SHADER_HANDLE.into()
}
fn fragment_shader() -> ShaderRef {
PBR_SHADER_HANDLE.into()
}
@ -585,4 +603,9 @@ impl Material for StandardMaterial {
fn depth_bias(&self) -> f32 {
self.depth_bias
}
#[inline]
fn opaque_render_method(&self) -> OpaqueRendererMethod {
self.opaque_render_method
}
}

View file

@ -1,11 +1,15 @@
use bevy_app::{Plugin, PreUpdate};
use bevy_asset::{load_internal_asset, AssetServer, Handle};
use bevy_core_pipeline::{
core_3d::CORE_3D_DEPTH_FORMAT,
deferred::{
AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT,
DEFERRED_PREPASS_FORMAT,
},
prelude::Camera3d,
prepass::{
AlphaMask3dPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass,
ViewPrepassTextures, DEPTH_PREPASS_FORMAT, MOTION_VECTOR_PREPASS_FORMAT,
NORMAL_PREPASS_FORMAT,
AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
},
};
use bevy_ecs::{
@ -28,27 +32,28 @@ use bevy_render::{
},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache,
PolygonMode, PrimitiveState, PushConstantRange, RenderPipelineDescriptor, Shader,
ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
TextureViewDimension, VertexState,
BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ColorTargetState,
ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer,
FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
PushConstantRange, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
StencilFaceState, StencilState, TextureAspect, TextureFormat, TextureSampleType,
TextureView, TextureViewDescriptor, TextureViewDimension, VertexState,
},
renderer::{RenderDevice, RenderQueue},
texture::{FallbackImagesDepth, FallbackImagesMsaa},
texture::{BevyDefault, FallbackImageMsaa},
view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::default;
use bevy_utils::tracing::error;
use crate::{
prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material,
MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey,
RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SetMaterialBindGroup,
SetMeshBindGroup,
OpaqueRendererMethod, RenderMaterialInstances, RenderMaterials, RenderMeshInstances,
SetMaterialBindGroup, SetMeshBindGroup,
};
use std::{hash::Hash, marker::PhantomData};
@ -60,6 +65,8 @@ pub const PREPASS_BINDINGS_SHADER_HANDLE: Handle<Shader> =
pub const PREPASS_UTILS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4603948296044544);
pub const PREPASS_IO_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(81212356509530944);
/// Sets up everything required to use the prepass pipeline.
///
/// This does not add the actual prepasses, see [`PrepassPlugin`] for that.
@ -97,6 +104,13 @@ where
Shader::from_wgsl
);
load_internal_asset!(
app,
PREPASS_IO_SHADER_HANDLE,
"prepass_io.wgsl",
Shader::from_wgsl
);
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
@ -172,6 +186,8 @@ where
render_app
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
.add_render_command::<Opaque3dDeferred, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dDeferred, DrawPrepass<M>>()
.add_systems(
Render,
queue_prepass_material_meshes::<M>
@ -225,8 +241,10 @@ pub struct PrepassPipeline<M: Material> {
pub view_layout_no_motion_vectors: BindGroupLayout,
pub mesh_layouts: MeshLayouts,
pub material_layout: BindGroupLayout,
pub material_vertex_shader: Option<Handle<Shader>>,
pub material_fragment_shader: Option<Handle<Shader>>,
pub prepass_material_vertex_shader: Option<Handle<Shader>>,
pub prepass_material_fragment_shader: Option<Handle<Shader>>,
pub deferred_material_vertex_shader: Option<Handle<Shader>>,
pub deferred_material_fragment_shader: Option<Handle<Shader>>,
pub material_pipeline: MaterialPipeline<M>,
_marker: PhantomData<M>,
}
@ -311,12 +329,22 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
view_layout_motion_vectors,
view_layout_no_motion_vectors,
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
material_vertex_shader: match M::prepass_vertex_shader() {
prepass_material_vertex_shader: match M::prepass_vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
material_fragment_shader: match M::prepass_fragment_shader() {
prepass_material_fragment_shader: match M::prepass_fragment_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
deferred_material_vertex_shader: match M::deferred_vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
deferred_material_fragment_shader: match M::deferred_fragment_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
@ -354,6 +382,11 @@ where
// The main limitation right now is that bind group order is hardcoded in shaders.
bind_group_layouts.insert(1, self.material_layout.clone());
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
shader_defs.push("WEBGL2".into());
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
if key.mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) {
shader_defs.push("DEPTH_PREPASS".into());
}
@ -394,15 +427,37 @@ where
}
if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2));
shader_defs.push("NORMAL_PREPASS".into());
}
if key
.mesh_key
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
{
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2));
shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push("VERTEX_TANGENTS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
}
if key
.mesh_key
.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
{
shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into());
}
if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
shader_defs.push("DEFERRED_PREPASS".into());
if layout.contains(Mesh::ATTRIBUTE_COLOR) {
shader_defs.push("VERTEX_COLORS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(6));
}
}
if key
.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
@ -410,10 +465,11 @@ where
shader_defs.push("MOTION_VECTOR_PREPASS".into());
}
if key
.mesh_key
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS)
{
if key.mesh_key.intersects(
MeshPipelineKey::NORMAL_PREPASS
| MeshPipelineKey::MOTION_VECTOR_PREPASS
| MeshPipelineKey::DEFERRED_PREPASS,
) {
shader_defs.push("PREPASS_FRAGMENT".into());
}
@ -430,25 +486,40 @@ where
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
// Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1
let mut targets = vec![];
targets.push(
let mut targets = vec![
key.mesh_key
.contains(MeshPipelineKey::NORMAL_PREPASS)
.then_some(ColorTargetState {
format: NORMAL_PREPASS_FORMAT,
blend: Some(BlendState::REPLACE),
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases.
blend: None,
write_mask: ColorWrites::ALL,
}),
);
targets.push(
key.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
.then_some(ColorTargetState {
format: MOTION_VECTOR_PREPASS_FORMAT,
blend: Some(BlendState::REPLACE),
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases.
blend: None,
write_mask: ColorWrites::ALL,
}),
);
key.mesh_key
.contains(MeshPipelineKey::DEFERRED_PREPASS)
.then_some(ColorTargetState {
format: DEFERRED_PREPASS_FORMAT,
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases.
blend: None,
write_mask: ColorWrites::ALL,
}),
key.mesh_key
.contains(MeshPipelineKey::DEFERRED_PREPASS)
.then_some(ColorTargetState {
format: DEFERRED_LIGHTING_PASS_ID_FORMAT,
blend: None,
write_mask: ColorWrites::ALL,
}),
];
if targets.iter().all(Option::is_none) {
// if no targets are required then clear the list, so that no fragment shader is required
// (though one may still be used for discarding depth buffer writes)
@ -461,13 +532,20 @@ where
let fragment_required = !targets.is_empty()
|| key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO)
|| (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
&& self.material_fragment_shader.is_some());
&& self.prepass_material_fragment_shader.is_some());
let fragment = fragment_required.then(|| {
// Use the fragment shader from the material
let frag_shader_handle = match self.material_fragment_shader.clone() {
Some(frag_shader_handle) => frag_shader_handle,
_ => PREPASS_SHADER_HANDLE,
let frag_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
match self.deferred_material_fragment_shader.clone() {
Some(frag_shader_handle) => frag_shader_handle,
_ => PREPASS_SHADER_HANDLE,
}
} else {
match self.prepass_material_fragment_shader.clone() {
Some(frag_shader_handle) => frag_shader_handle,
_ => PREPASS_SHADER_HANDLE,
}
};
FragmentState {
@ -479,7 +557,13 @@ where
});
// Use the vertex shader from the material if present
let vert_shader_handle = if let Some(handle) = &self.material_vertex_shader {
let vert_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
if let Some(handle) = &self.deferred_material_vertex_shader {
handle.clone()
} else {
PREPASS_SHADER_HANDLE
}
} else if let Some(handle) = &self.prepass_material_vertex_shader {
handle.clone()
} else {
PREPASS_SHADER_HANDLE
@ -512,7 +596,7 @@ where
conservative: false,
},
depth_stencil: Some(DepthStencilState {
format: DEPTH_PREPASS_FORMAT,
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState {
@ -546,9 +630,9 @@ where
}
pub fn get_bind_group_layout_entries(
bindings: [u32; 3],
bindings: [u32; 4],
multisampled: bool,
) -> [BindGroupLayoutEntry; 3] {
) -> [BindGroupLayoutEntry; 4] {
[
// Depth texture
BindGroupLayoutEntry {
@ -583,53 +667,91 @@ pub fn get_bind_group_layout_entries(
},
count: None,
},
// Deferred texture
BindGroupLayoutEntry {
binding: bindings[3],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Uint,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
]
}
pub fn get_bindings<'a>(
prepass_textures: Option<&'a ViewPrepassTextures>,
fallback_images: &'a mut FallbackImagesMsaa,
fallback_depths: &'a mut FallbackImagesDepth,
msaa: &'a Msaa,
bindings: [u32; 3],
) -> [BindGroupEntry<'a>; 3] {
// Needed so the texture views can live long enough.
pub struct PrepassBindingsSet([TextureView; 4]);
impl PrepassBindingsSet {
pub fn get_entries(&self, bindings: [u32; 4]) -> [BindGroupEntry; 4] {
[
BindGroupEntry {
binding: bindings[0],
resource: BindingResource::TextureView(&self.0[0]),
},
BindGroupEntry {
binding: bindings[1],
resource: BindingResource::TextureView(&self.0[1]),
},
BindGroupEntry {
binding: bindings[2],
resource: BindingResource::TextureView(&self.0[2]),
},
BindGroupEntry {
binding: bindings[3],
resource: BindingResource::TextureView(&self.0[3]),
},
]
}
}
pub fn get_bindings(
prepass_textures: Option<&ViewPrepassTextures>,
fallback_images: &mut FallbackImageMsaa,
msaa: &Msaa,
) -> PrepassBindingsSet {
let depth_desc = TextureViewDescriptor {
label: Some("prepass_depth"),
aspect: TextureAspect::DepthOnly,
..default()
};
let depth_view = match prepass_textures.and_then(|x| x.depth.as_ref()) {
Some(texture) => &texture.default_view,
None => {
&fallback_depths
.image_for_samplecount(msaa.samples())
.texture_view
}
Some(texture) => texture.texture.create_view(&depth_desc),
None => fallback_images
.image_for_samplecount(msaa.samples(), CORE_3D_DEPTH_FORMAT)
.texture
.create_view(&depth_desc),
};
let normal_motion_vectors_fallback = &fallback_images
.image_for_samplecount(msaa.samples())
.image_for_samplecount(msaa.samples(), TextureFormat::bevy_default())
.texture_view;
let normal_view = match prepass_textures.and_then(|x| x.normal.as_ref()) {
Some(texture) => &texture.default_view,
None => normal_motion_vectors_fallback,
};
}
.clone();
let motion_vectors_view = match prepass_textures.and_then(|x| x.motion_vectors.as_ref()) {
Some(texture) => &texture.default_view,
None => normal_motion_vectors_fallback,
};
}
.clone();
[
BindGroupEntry {
binding: bindings[0],
resource: BindingResource::TextureView(depth_view),
},
BindGroupEntry {
binding: bindings[1],
resource: BindingResource::TextureView(normal_view),
},
BindGroupEntry {
binding: bindings[2],
resource: BindingResource::TextureView(motion_vectors_view),
},
]
let deferred_fallback = &fallback_images
.image_for_samplecount(1, TextureFormat::Rgba32Uint)
.texture_view;
let deferred_view = match prepass_textures.and_then(|x| x.deferred.as_ref()) {
Some(texture) => &texture.default_view,
None => deferred_fallback,
}
.clone();
PrepassBindingsSet([depth_view, normal_view, motion_vectors_view, deferred_view])
}
// Extract the render phases for the prepass
@ -754,6 +876,8 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
pub fn queue_prepass_material_meshes<M: Material>(
opaque_draw_functions: Res<DrawFunctions<Opaque3dPrepass>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3dPrepass>>,
opaque_deferred_draw_functions: Res<DrawFunctions<Opaque3dDeferred>>,
alpha_mask_deferred_draw_functions: Res<DrawFunctions<AlphaMask3dDeferred>>,
prepass_pipeline: Res<PrepassPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
@ -767,9 +891,12 @@ pub fn queue_prepass_material_meshes<M: Material>(
&VisibleEntities,
&mut RenderPhase<Opaque3dPrepass>,
&mut RenderPhase<AlphaMask3dPrepass>,
Option<&mut RenderPhase<Opaque3dDeferred>>,
Option<&mut RenderPhase<AlphaMask3dDeferred>>,
Option<&DepthPrepass>,
Option<&NormalPrepass>,
Option<&MotionVectorPrepass>,
Option<&DeferredPrepass>,
)>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
@ -782,14 +909,25 @@ pub fn queue_prepass_material_meshes<M: Material>(
.read()
.get_id::<DrawPrepass<M>>()
.unwrap();
let opaque_draw_deferred = opaque_deferred_draw_functions
.read()
.get_id::<DrawPrepass<M>>()
.unwrap();
let alpha_mask_draw_deferred = alpha_mask_deferred_draw_functions
.read()
.get_id::<DrawPrepass<M>>()
.unwrap();
for (
view,
visible_entities,
mut opaque_phase,
mut alpha_mask_phase,
mut opaque_deferred_phase,
mut alpha_mask_deferred_phase,
depth_prepass,
normal_prepass,
motion_vector_prepass,
deferred_prepass,
) in &mut views
{
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
@ -803,6 +941,9 @@ pub fn queue_prepass_material_meshes<M: Material>(
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
let mut opaque_phase_deferred = opaque_deferred_phase.as_mut();
let mut alpha_mask_phase_deferred = alpha_mask_deferred_phase.as_mut();
let rangefinder = view.rangefinder3d();
for visible_entity in &visible_entities.entities {
@ -834,6 +975,18 @@ pub fn queue_prepass_material_meshes<M: Material>(
| AlphaMode::Multiply => continue,
}
let forward = match material.properties.render_method {
OpaqueRendererMethod::Forward => true,
OpaqueRendererMethod::Deferred => false,
OpaqueRendererMethod::Auto => unreachable!(),
};
let deferred = deferred_prepass.is_some() && !forward;
if deferred {
mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&prepass_pipeline,
@ -856,24 +1009,52 @@ pub fn queue_prepass_material_meshes<M: Material>(
+ material.properties.depth_bias;
match alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3dPrepass {
entity: *visible_entity,
draw_function: opaque_draw_prepass,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
if deferred {
opaque_phase_deferred
.as_mut()
.unwrap()
.add(Opaque3dDeferred {
entity: *visible_entity,
draw_function: opaque_draw_deferred,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
} else {
opaque_phase.add(Opaque3dPrepass {
entity: *visible_entity,
draw_function: opaque_draw_prepass,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3dPrepass {
entity: *visible_entity,
draw_function: alpha_mask_draw_prepass,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
if deferred {
alpha_mask_phase_deferred
.as_mut()
.unwrap()
.add(AlphaMask3dDeferred {
entity: *visible_entity,
draw_function: alpha_mask_draw_deferred,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
} else {
alpha_mask_phase.add(AlphaMask3dPrepass {
entity: *visible_entity,
draw_function: alpha_mask_draw_prepass,
pipeline_id,
distance,
batch_range: 0..1,
dynamic_offset: None,
});
}
}
AlphaMode::Blend
| AlphaMode::Premultiplied

View file

@ -1,60 +1,15 @@
#import bevy_pbr::prepass_bindings
#import bevy_pbr::mesh_functions
#import bevy_pbr::prepass_io Vertex, VertexOutput, FragmentInput, FragmentOutput
#import bevy_pbr::skinning
#import bevy_pbr::morph
#import bevy_pbr::mesh_bindings mesh
#import bevy_render::instance_index get_instance_index
#import bevy_pbr::mesh_view_bindings view, previous_view_proj
// Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can
// pass them to custom prepass shaders like pbr_prepass.wgsl.
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
#ifdef VERTEX_UVS
@location(1) uv: vec2<f32>,
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS
@location(2) normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(3) tangent: vec4<f32>,
#endif // VERTEX_TANGENTS
#endif // NORMAL_PREPASS
#ifdef SKINNED
@location(4) joint_indices: vec4<u32>,
@location(5) joint_weights: vec4<f32>,
#endif // SKINNED
#ifdef MORPH_TARGETS
@builtin(vertex_index) index: u32,
#endif // MORPH_TARGETS
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
#ifdef VERTEX_UVS
@location(0) uv: vec2<f32>,
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS
@location(1) world_normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(2) world_tangent: vec4<f32>,
#endif // VERTEX_TANGENTS
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@location(3) world_position: vec4<f32>,
@location(4) previous_world_position: vec4<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@location(5) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
}
#ifdef DEFERRED_PREPASS
#import bevy_pbr::rgb9e5
#endif
#ifdef MORPH_TARGETS
fn morph_vertex(vertex_in: Vertex) -> Vertex {
@ -105,7 +60,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.uv = vertex.uv;
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
#ifdef SKINNED
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
#else // SKINNED
@ -126,10 +81,17 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
get_instance_index(vertex_no_morph.instance_index)
);
#endif // VERTEX_TANGENTS
#endif // NORMAL_PREPASS
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
#ifdef VERTEX_COLORS
out.color = vertex.color;
#endif
#ifdef MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS
out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0));
#endif // MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS
#ifdef MOTION_VECTOR_PREPASS
out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0));
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(
@ -138,43 +100,16 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
);
#endif // MOTION_VECTOR_PREPASS
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
out.instance_index = get_instance_index(vertex_no_morph.instance_index);
#endif
return out;
}
#ifdef PREPASS_FRAGMENT
struct FragmentInput {
#ifdef VERTEX_UVS
@location(0) uv: vec2<f32>,
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS
@location(1) world_normal: vec3<f32>,
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@location(3) world_position: vec4<f32>,
@location(4) previous_world_position: vec4<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@location(5) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
}
struct FragmentOutput {
#ifdef NORMAL_PREPASS
@location(0) normal: vec4<f32>,
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@location(1) motion_vector: vec2<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@builtin(frag_depth) frag_depth: f32,
#endif // DEPTH_CLAMP_ORTHO
}
@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
var out: FragmentOutput;
@ -188,7 +123,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput {
#endif // DEPTH_CLAMP_ORTHO
#ifdef MOTION_VECTOR_PREPASS
let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position;
let clip_position_t = view.unjittered_view_proj * in.world_position;
let clip_position = clip_position_t.xy / clip_position_t.w;
let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position;
let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w;
@ -201,6 +136,15 @@ fn fragment(in: FragmentInput) -> FragmentOutput {
out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5);
#endif // MOTION_VECTOR_PREPASS
#ifdef DEFERRED_PREPASS
// There isn't any material info available for this default prepass shader so we are just writing 
// emissive magenta out to the deferred gbuffer to be rendered by the first deferred lighting pass layer.
// The is here so if the default prepass fragment is used for deferred magenta will be rendered, and also
// as an example to show that a user could write to the deferred gbuffer if they were to start from this shader.
out.deferred = vec4(0u, bevy_pbr::rgb9e5::vec3_to_rgb9e5_(vec3(1.0, 0.0, 1.0)), 0u, 0u);
out.deferred_lighting_pass_id = 1u;
#endif
return out;
}
#endif // PREPASS_FRAGMENT

View file

@ -1,11 +1,6 @@
#define_import_path bevy_pbr::prepass_bindings
#import bevy_render::view View
#import bevy_render::globals Globals
#import bevy_pbr::mesh_types
@group(0) @binding(0) var<uniform> view: View;
@group(0) @binding(1) var<uniform> globals: Globals;
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(2) var<uniform> previous_view_proj: mat4x4<f32>;
#endif // MOTION_VECTOR_PREPASS

View file

@ -0,0 +1,114 @@
#define_import_path bevy_pbr::prepass_io
// Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can
// pass them to custom prepass shaders like pbr_prepass.wgsl.
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
#ifdef VERTEX_UVS
@location(1) uv: vec2<f32>,
#endif
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
@location(2) normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(3) tangent: vec4<f32>,
#endif
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
#ifdef SKINNED
@location(4) joint_indices: vec4<u32>,
@location(5) joint_weights: vec4<f32>,
#endif
#ifdef VERTEX_COLORS
@location(6) color: vec4<f32>,
#endif
#ifdef MORPH_TARGETS
@builtin(vertex_index) index: u32,
#endif // MORPH_TARGETS
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
#ifdef VERTEX_UVS
@location(0) uv: vec2<f32>,
#endif
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
@location(1) world_normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(2) world_tangent: vec4<f32>,
#endif
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
@location(3) world_position: vec4<f32>,
#ifdef MOTION_VECTOR_PREPASS
@location(4) previous_world_position: vec4<f32>,
#endif
#ifdef DEPTH_CLAMP_ORTHO
@location(5) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
@location(6) instance_index: u32,
#endif
#ifdef VERTEX_COLORS
@location(7) color: vec4<f32>,
#endif
}
struct FragmentInput {
@builtin(position) position: vec4<f32>,
#ifdef VERTEX_UVS
@location(0) uv: vec2<f32>,
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
@location(1) world_normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(2) world_tangent: vec4<f32>,
#endif
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
@location(3) world_position: vec4<f32>,
#ifdef MOTION_VECTOR_PREPASS
@location(4) previous_world_position: vec4<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@location(5) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
@location(6) instance_index: u32,
#endif
#ifdef VERTEX_COLORS
@location(7) color: vec4<f32>,
#endif
};
#ifdef PREPASS_FRAGMENT
struct FragmentOutput {
#ifdef NORMAL_PREPASS
@location(0) normal: vec4<f32>,
#endif
#ifdef MOTION_VECTOR_PREPASS
@location(1) motion_vector: vec2<f32>,
#endif
#ifdef DEFERRED_PREPASS
@location(2) deferred: vec4<u32>,
@location(3) deferred_lighting_pass_id: u32,
#endif
#ifdef DEPTH_CLAMP_ORTHO
@builtin(frag_depth) frag_depth: f32,
#endif // DEPTH_CLAMP_ORTHO
}
#endif //PREPASS_FRAGMENT

View file

@ -6,7 +6,7 @@ use crate::{
PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterialInstances, RenderMaterials,
RenderMeshInstances, SpotLight, VisiblePointLights,
};
use bevy_core_pipeline::core_3d::Transparent3d;
use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT};
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::{
@ -218,7 +218,6 @@ pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
pub const MAX_CASCADES_PER_LIGHT: usize = 4;
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
pub const MAX_CASCADES_PER_LIGHT: usize = 1;
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
#[derive(Resource, Clone)]
pub struct ShadowSamplers {
@ -912,7 +911,7 @@ pub fn prepare_lights(
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: SHADOW_FORMAT,
format: CORE_3D_DEPTH_FORMAT,
label: Some("point_light_shadow_map_texture"),
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
view_formats: &[],
@ -933,7 +932,7 @@ pub fn prepare_lights(
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: SHADOW_FORMAT,
format: CORE_3D_DEPTH_FORMAT,
label: Some("directional_light_shadow_map_texture"),
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
view_formats: &[],
@ -1172,7 +1171,7 @@ pub fn prepare_lights(
dimension: Some(TextureViewDimension::CubeArray),
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
dimension: Some(TextureViewDimension::Cube),
aspect: TextureAspect::All,
aspect: TextureAspect::DepthOnly,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
@ -1187,7 +1186,7 @@ pub fn prepare_lights(
dimension: Some(TextureViewDimension::D2Array),
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
aspect: TextureAspect::DepthOnly,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,

View file

@ -8,7 +8,8 @@ use crate::{
use bevy_app::{Plugin, PostUpdate};
use bevy_asset::{load_internal_asset, AssetId, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
core_3d::{AlphaMask3d, Opaque3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
prepass::ViewPrepassTextures,
tonemapping::{
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
@ -37,8 +38,8 @@ use bevy_render::{
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{
BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImagesDepth,
FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImageMsaa, GpuImage, Image,
ImageSampler, TextureFormatPixelInfo,
},
view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
@ -137,6 +138,8 @@ impl Plugin for MeshRenderPlugin {
batch_and_prepare_render_phase::<Transparent3d, MeshPipeline>,
batch_and_prepare_render_phase::<AlphaMask3d, MeshPipeline>,
batch_and_prepare_render_phase::<Shadow, MeshPipeline>,
batch_and_prepare_render_phase::<Opaque3dDeferred, MeshPipeline>,
batch_and_prepare_render_phase::<AlphaMask3dDeferred, MeshPipeline>,
)
.in_set(RenderSet::PrepareResources),
write_batched_instance_buffer::<MeshPipeline>
@ -513,7 +516,7 @@ impl FromWorld for MeshPipeline {
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && !multisampled)
{
entries.extend_from_slice(&prepass::get_bind_group_layout_entries(
[17, 18, 19],
[17, 18, 19, 20],
multisampled,
));
}
@ -637,14 +640,15 @@ bitflags::bitflags! {
const DEBAND_DITHER = (1 << 2);
const DEPTH_PREPASS = (1 << 3);
const NORMAL_PREPASS = (1 << 4);
const MOTION_VECTOR_PREPASS = (1 << 5);
const MAY_DISCARD = (1 << 6); // Guards shader codepaths that may discard, allowing early depth tests in most cases
const DEFERRED_PREPASS = (1 << 5);
const MOTION_VECTOR_PREPASS = (1 << 6);
const MAY_DISCARD = (1 << 7); // Guards shader codepaths that may discard, allowing early depth tests in most cases
// See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test
const ENVIRONMENT_MAP = (1 << 7);
const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 8);
const DEPTH_CLAMP_ORTHO = (1 << 9);
const TAA = (1 << 10);
const MORPH_TARGETS = (1 << 11);
const ENVIRONMENT_MAP = (1 << 8);
const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 9);
const DEPTH_CLAMP_ORTHO = (1 << 10);
const TAA = (1 << 11);
const MORPH_TARGETS = (1 << 12);
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3
const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); //
@ -862,7 +866,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
depth_write_enabled = false;
} else {
label = "opaque_mesh_pipeline".into();
blend = Some(BlendState::REPLACE);
// BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases
blend = None;
// For the opaque and alpha mask passes, fragments that are closer will replace
// the current fragment value in the output and the depth is written to the
// depth buffer
@ -874,6 +879,9 @@ impl SpecializedMeshPipeline for MeshPipeline {
shader_defs.push("LOAD_PREPASS_NORMALS".into());
}
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
shader_defs.push("WEBGL2".into());
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
shader_defs.push("TONEMAP_IN_SHADER".into());
@ -978,7 +986,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled,
depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState {
@ -1090,10 +1098,9 @@ pub fn prepare_mesh_view_bind_groups(
Option<&EnvironmentMapLight>,
&Tonemapping,
)>,
(images, mut fallback_images, mut fallback_depths, fallback_cubemap): (
(images, mut fallback_images, fallback_cubemap): (
Res<RenderAssets<Image>>,
FallbackImagesMsaa,
FallbackImagesDepth,
FallbackImageMsaa,
Res<FallbackImageCubemap>,
),
msaa: Res<Msaa>,
@ -1124,7 +1131,7 @@ pub fn prepare_mesh_view_bind_groups(
) in &views
{
let fallback_ssao = fallback_images
.image_for_samplecount(1)
.image_for_samplecount(1, TextureFormat::bevy_default())
.texture_view
.clone();
@ -1205,28 +1212,31 @@ pub fn prepare_mesh_view_bind_groups(
get_lut_bindings(&images, &tonemapping_luts, tonemapping, [15, 16]);
entries.extend_from_slice(&tonemapping_luts);
let label = Some("mesh_view_bind_group");
// When using WebGL, we can't have a depth texture with multisampling
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && msaa.samples() == 1)
{
entries.extend_from_slice(&prepass::get_bindings(
prepass_textures,
&mut fallback_images,
&mut fallback_depths,
&msaa,
[17, 18, 19],
));
let prepass_bindings =
prepass::get_bindings(prepass_textures, &mut fallback_images, &msaa);
entries.extend_from_slice(&prepass_bindings.get_entries([17, 18, 19, 20]));
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label,
layout,
}),
});
} else {
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label,
layout,
}),
});
}
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label: Some("mesh_view_bind_group"),
layout,
});
commands.entity(entity).insert(MeshViewBindGroup {
value: view_bind_group,
});
}
}
}

View file

@ -49,4 +49,5 @@
@group(0) @binding(17) var depth_prepass_texture: texture_depth_2d;
@group(0) @binding(18) var normal_prepass_texture: texture_2d<f32>;
@group(0) @binding(19) var motion_vector_prepass_texture: texture_2d<f32>;
@group(0) @binding(20) var deferred_prepass_texture: texture_2d<u32>;
#endif

View file

@ -3,9 +3,7 @@
#import bevy_pbr::pbr_functions as pbr_functions
#import bevy_pbr::pbr_bindings as pbr_bindings
#import bevy_pbr::pbr_types as pbr_types
#import bevy_pbr::prepass_utils
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
#import bevy_pbr::mesh_bindings mesh
#import bevy_pbr::mesh_view_bindings view, fog, screen_space_ambient_occlusion_texture
#import bevy_pbr::mesh_view_types FOG_MODE_OFF
@ -18,11 +16,34 @@
#import bevy_pbr::gtao_utils gtao_multibounce
#endif
#ifdef DEFERRED_PREPASS
#import bevy_pbr::pbr_deferred_types as pbr_deferred_types
#import bevy_pbr::pbr_deferred_functions as pbr_deferred_functions
#import bevy_pbr::pbr_prepass_functions as pbr_prepass_functions
#import bevy_pbr::prepass_io as prepass_io
#else // DEFERRED_PREPASS
#import bevy_pbr::mesh_vertex_output as mesh_vertex_output
#endif // DEFERRED_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(2)
var<uniform> previous_view_proj: mat4x4<f32>;
#endif // MOTION_VECTOR_PREPASS
@fragment
#ifdef DEFERRED_PREPASS
fn fragment(
in: MeshVertexOutput,
@builtin(front_facing) is_front: bool,
) -> @location(0) vec4<f32> {
in: prepass_io::FragmentInput,
@builtin(front_facing) is_front: bool,
) -> prepass_io::FragmentOutput {
var out: prepass_io::FragmentOutput;
#else // DEFERRED_PREPASS
fn fragment(
in: mesh_vertex_output::MeshVertexOutput,
@builtin(front_facing) is_front: bool,
) -> @location(0) vec4<f32> {
#endif // DEFERRED_PREPASS
var output_color: vec4<f32> = pbr_bindings::material.base_color;
let is_orthographic = view.projection[3].w == 1.0;
@ -57,13 +78,13 @@ fn fragment(
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias);
}
#endif
#endif // VERTEX_UVS
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
// Prepare a 'processed' StandardMaterial by sampling all textures to resolve
// the material members
var pbr_input: pbr_functions::PbrInput;
var pbr_input: pbr_types::PbrInput;
pbr_input.material.base_color = output_color;
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
@ -99,11 +120,13 @@ fn fragment(
occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, uv, view.mip_bias).r);
}
#endif
#ifndef DEFERRED_PREPASS
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb);
occlusion = min(occlusion, ssao_multibounce);
#endif
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
#endif // DEFERRED_PREPASS
pbr_input.occlusion = occlusion;
pbr_input.frag_coord = in.position;
@ -139,12 +162,40 @@ fn fragment(
pbr_input.occlusion = occlusion;
pbr_input.flags = mesh[in.instance_index].flags;
#ifdef DEFERRED_PREPASS
pbr_functions::alpha_discard(pbr_bindings::material, output_color);
out.deferred = pbr_deferred_functions::deferred_gbuffer_from_pbr_input(pbr_input);
out.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;
#ifdef NORMAL_PREPASS
out.normal = vec4(pbr_input.N * 0.5 + vec3(0.5), 1.0);
#endif // NORMAL_PREPASS
#else // DEFERRED_PREPASS
output_color = pbr_functions::pbr(pbr_input);
} else {
output_color = pbr_functions::alpha_discard(pbr_bindings::material, output_color);
#endif // DEFERRED_PREPASS
} else { // if UNLIT_BIT != 0
pbr_functions::alpha_discard(pbr_bindings::material, output_color);
#ifdef DEFERRED_PREPASS
var pbr_input = pbr_types::pbr_input_new();
pbr_input.flags = mesh[in.instance_index].flags;
pbr_input.material.flags = pbr_bindings::material.flags;
pbr_input.material.base_color = output_color;
pbr_input.world_position = in.world_position;
pbr_input.world_normal = in.world_normal;
pbr_input.frag_coord = in.position;
out.deferred = pbr_deferred_functions::deferred_gbuffer_from_pbr_input(pbr_input);
out.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;
#ifdef NORMAL_PREPASS
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
#endif
#endif // DEFERRED_PREPASS
}
#ifdef DEFERRED_PREPASS
#ifdef MOTION_VECTOR_PREPASS
out.motion_vector = pbr_prepass_functions::calculate_motion_vector(in.world_position, in.previous_world_position);
#endif // MOTION_VECTOR_PREPASS
return out;
#else //DEFERRED_PREPASS
// fog
if (fog.mode != FOG_MODE_OFF && (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
output_color = pbr_functions::apply_fog(fog, output_color, in.world_position.xyz, view.world_position.xyz);
@ -165,5 +216,7 @@ fn fragment(
#ifdef PREMULTIPLY_ALPHA
output_color = pbr_functions::premultiply_alpha(pbr_bindings::material.flags, output_color);
#endif
return output_color;
#endif //DEFERRED_PREPASS
}

View file

@ -17,7 +17,6 @@
#import bevy_pbr::environment_map
#endif
#import bevy_pbr::mesh_bindings mesh
#import bevy_pbr::mesh_types MESH_FLAGS_SHADOW_RECEIVER_BIT
fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
@ -137,47 +136,9 @@ fn calculate_view(
return V;
}
struct PbrInput {
material: pbr_types::StandardMaterial,
occlusion: vec3<f32>,
frag_coord: vec4<f32>,
world_position: vec4<f32>,
// Normalized world normal used for shadow mapping as normal-mapping is not used for shadow
// mapping
world_normal: vec3<f32>,
// Normalized normal-mapped world normal used for lighting
N: vec3<f32>,
// Normalized view vector in world space, pointing from the fragment world position toward the
// view world position
V: vec3<f32>,
is_orthographic: bool,
flags: u32,
};
// Creates a PbrInput with default values
fn pbr_input_new() -> PbrInput {
var pbr_input: PbrInput;
pbr_input.material = pbr_types::standard_material_new();
pbr_input.occlusion = vec3<f32>(1.0);
pbr_input.frag_coord = vec4<f32>(0.0, 0.0, 0.0, 1.0);
pbr_input.world_position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
pbr_input.world_normal = vec3<f32>(0.0, 0.0, 1.0);
pbr_input.is_orthographic = false;
pbr_input.N = vec3<f32>(0.0, 0.0, 1.0);
pbr_input.V = vec3<f32>(1.0, 0.0, 0.0);
pbr_input.flags = 0u;
return pbr_input;
}
#ifndef PREPASS_FRAGMENT
fn pbr(
in: PbrInput,
in: pbr_types::PbrInput,
) -> vec4<f32> {
var output_color: vec4<f32> = in.material.base_color;

View file

@ -1,87 +1,22 @@
#import bevy_pbr::prepass_bindings
#import bevy_pbr::pbr_prepass_functions
#import bevy_pbr::pbr_bindings
#import bevy_pbr::pbr_types
#ifdef NORMAL_PREPASS
#import bevy_pbr::pbr_functions
#endif // NORMAL_PREPASS
struct FragmentInput {
@builtin(front_facing) is_front: bool,
@builtin(position) frag_coord: vec4<f32>,
#ifdef VERTEX_UVS
@location(0) uv: vec2<f32>,
#endif // VERTEX_UVS
#ifdef NORMAL_PREPASS
@location(1) world_normal: vec3<f32>,
#ifdef VERTEX_TANGENTS
@location(2) world_tangent: vec4<f32>,
#endif // VERTEX_TANGENTS
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@location(3) world_position: vec4<f32>,
@location(4) previous_world_position: vec4<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@location(5) clip_position_unclamped: vec4<f32>,
#endif // DEPTH_CLAMP_ORTHO
};
// Cutoff used for the premultiplied alpha modes BLEND and ADD.
const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff
fn prepass_alpha_discard(in: FragmentInput) {
#ifdef MAY_DISCARD
var output_color: vec4<f32> = bevy_pbr::pbr_bindings::material.base_color;
#ifdef VERTEX_UVS
if (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u {
output_color = output_color * textureSampleBias(bevy_pbr::pbr_bindings::base_color_texture, bevy_pbr::pbr_bindings::base_color_sampler, in.uv, bevy_pbr::prepass_bindings::view.mip_bias);
}
#endif // VERTEX_UVS
let alpha_mode = bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
if output_color.a < bevy_pbr::pbr_bindings::material.alpha_cutoff {
discard;
}
} else if (alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) {
if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
discard;
}
} else if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED {
if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
discard;
}
}
#endif // MAY_DISCARD
}
#import bevy_pbr::prepass_io as prepass_io
#import bevy_pbr::mesh_view_bindings view
#ifdef PREPASS_FRAGMENT
struct FragmentOutput {
#ifdef NORMAL_PREPASS
@location(0) normal: vec4<f32>,
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@location(1) motion_vector: vec2<f32>,
#endif // MOTION_VECTOR_PREPASS
#ifdef DEPTH_CLAMP_ORTHO
@builtin(frag_depth) frag_depth: f32,
#endif // DEPTH_CLAMP_ORTHO
}
@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
prepass_alpha_discard(in);
fn fragment(
in: prepass_io::FragmentInput,
@builtin(front_facing) is_front: bool,
) -> prepass_io::FragmentOutput {
bevy_pbr::pbr_prepass_functions::prepass_alpha_discard(in);
var out: FragmentOutput;
var out: prepass_io::FragmentOutput;
#ifdef DEPTH_CLAMP_ORTHO
out.frag_depth = in.clip_position_unclamped.z;
@ -93,7 +28,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput {
let world_normal = bevy_pbr::pbr_functions::prepare_world_normal(
in.world_normal,
(bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u,
in.is_front,
is_front,
);
let normal = bevy_pbr::pbr_functions::apply_normal_mapping(
@ -107,7 +42,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput {
#ifdef VERTEX_UVS
in.uv,
#endif // VERTEX_UVS
bevy_pbr::prepass_bindings::view.mip_bias,
view.mip_bias,
);
out.normal = vec4(normal * 0.5 + vec3(0.5), 1.0);
@ -117,24 +52,14 @@ fn fragment(in: FragmentInput) -> FragmentOutput {
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position;
let clip_position = clip_position_t.xy / clip_position_t.w;
let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position;
let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w;
// These motion vectors are used as offsets to UV positions and are stored
// in the range -1,1 to allow offsetting from the one corner to the
// diagonally-opposite corner in UV coordinates, in either direction.
// A difference between diagonally-opposite corners of clip space is in the
// range -2,2, so this needs to be scaled by 0.5. And the V direction goes
// down where clip space y goes up, so y needs to be flipped.
out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5);
#endif // MOTION_VECTOR_PREPASS
out.motion_vector = bevy_pbr::pbr_prepass_functions::calculate_motion_vector(in.world_position, in.previous_world_position);
#endif
return out;
}
#else
@fragment
fn fragment(in: FragmentInput) {
prepass_alpha_discard(in);
fn fragment(in: prepass_io::FragmentInput) {
bevy_pbr::pbr_prepass_functions::prepass_alpha_discard(in);
}
#endif // PREPASS_FRAGMENT

View file

@ -0,0 +1,57 @@
#define_import_path bevy_pbr::pbr_prepass_functions
#import bevy_pbr::prepass_io as prepass_io
#import bevy_pbr::prepass_bindings previous_view_proj
#import bevy_pbr::mesh_view_bindings view
#import bevy_pbr::pbr_bindings as pbr_bindings
#import bevy_pbr::pbr_types as pbr_types
// Cutoff used for the premultiplied alpha modes BLEND and ADD.
const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff
fn prepass_alpha_discard(in: prepass_io::FragmentInput) {
#ifdef MAY_DISCARD
var output_color: vec4<f32> = pbr_bindings::material.base_color;
#ifdef VERTEX_UVS
if (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u {
output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, in.uv, view.mip_bias);
}
#endif // VERTEX_UVS
let alpha_mode = pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
if output_color.a < pbr_bindings::material.alpha_cutoff {
discard;
}
} else if (alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) {
if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
discard;
}
} else if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED {
if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
discard;
}
}
#endif // MAY_DISCARD
}
#ifdef MOTION_VECTOR_PREPASS
fn calculate_motion_vector(world_position: vec4<f32>, previous_world_position: vec4<f32>) -> vec2<f32> {
let clip_position_t = view.unjittered_view_proj * world_position;
let clip_position = clip_position_t.xy / clip_position_t.w;
let previous_clip_position_t = previous_view_proj * previous_world_position;
let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w;
// These motion vectors are used as offsets to UV positions and are stored
// in the range -1,1 to allow offsetting from the one corner to the
// diagonally-opposite corner in UV coordinates, in either direction.
// A difference between diagonally-opposite corners of clip space is in the
// range -2,2, so this needs to be scaled by 0.5. And the V direction goes
// down where clip space y goes up, so y needs to be flipped.
return (clip_position - previous_clip_position) * vec2(0.5, -0.5);
}
#endif // MOTION_VECTOR_PREPASS

View file

@ -12,8 +12,14 @@ struct StandardMaterial {
parallax_depth_scale: f32,
max_parallax_layer_count: f32,
max_relief_mapping_search_steps: u32,
/// ID for specifying which deferred lighting pass should be used for rendering this material, if any.
deferred_lighting_pass_id: u32,
};
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: if these flags are updated or changed. Be sure to also update
// deferred_flags_from_mesh_material_flags and mesh_material_flags_from_deferred_flags
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u;
const STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u;
const STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u;
@ -34,6 +40,7 @@ const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MULTIPLY: u32 = 2684354560u;
// To calculate/verify the values above, use the following playground:
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7792f8dd6fc6a8d4d0b6b1776898a7f4
// Creates a StandardMaterial with default values
fn standard_material_new() -> StandardMaterial {
var material: StandardMaterial;
@ -49,6 +56,45 @@ fn standard_material_new() -> StandardMaterial {
material.parallax_depth_scale = 0.1;
material.max_parallax_layer_count = 16.0;
material.max_relief_mapping_search_steps = 5u;
material.deferred_lighting_pass_id = 1u;
return material;
}
struct PbrInput {
material: StandardMaterial,
occlusion: vec3<f32>,
frag_coord: vec4<f32>,
world_position: vec4<f32>,
// Normalized world normal used for shadow mapping as normal-mapping is not used for shadow
// mapping
world_normal: vec3<f32>,
// Normalized normal-mapped world normal used for lighting
N: vec3<f32>,
// Normalized view vector in world space, pointing from the fragment world position toward the
// view world position
V: vec3<f32>,
is_orthographic: bool,
flags: u32,
};
// Creates a PbrInput with default values
fn pbr_input_new() -> PbrInput {
var pbr_input: PbrInput;
pbr_input.material = standard_material_new();
pbr_input.occlusion = vec3<f32>(1.0);
pbr_input.frag_coord = vec4<f32>(0.0, 0.0, 0.0, 1.0);
pbr_input.world_position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
pbr_input.world_normal = vec3<f32>(0.0, 0.0, 1.0);
pbr_input.is_orthographic = false;
pbr_input.N = vec3<f32>(0.0, 0.0, 1.0);
pbr_input.V = vec3<f32>(1.0, 0.0, 0.0);
pbr_input.flags = 0u;
return pbr_input;
}

View file

@ -0,0 +1,63 @@
#define_import_path bevy_pbr::rgb9e5
const RGB9E5_EXPONENT_BITS = 5u;
const RGB9E5_MANTISSA_BITS = 9;
const RGB9E5_MANTISSA_BITSU = 9u;
const RGB9E5_EXP_BIAS = 15;
const RGB9E5_MAX_VALID_BIASED_EXP = 31u;
//#define MAX_RGB9E5_EXP (RGB9E5_MAX_VALID_BIASED_EXP - RGB9E5_EXP_BIAS)
//#define RGB9E5_MANTISSA_VALUES (1<<RGB9E5_MANTISSA_BITS)
//#define MAX_RGB9E5_MANTISSA (RGB9E5_MANTISSA_VALUES-1)
//#define MAX_RGB9E5 ((f32(MAX_RGB9E5_MANTISSA))/RGB9E5_MANTISSA_VALUES * (1<<MAX_RGB9E5_EXP))
//#define EPSILON_RGB9E5_ ((1.0/RGB9E5_MANTISSA_VALUES) / (1<<RGB9E5_EXP_BIAS))
const MAX_RGB9E5_EXP = 16u;
const RGB9E5_MANTISSA_VALUES = 512;
const MAX_RGB9E5_MANTISSA = 511;
const MAX_RGB9E5_MANTISSAU = 511u;
const MAX_RGB9E5_ = 65408.0;
const EPSILON_RGB9E5_ = 0.000000059604645;
fn floor_log2_(x: f32) -> i32 {
let f = bitcast<u32>(x);
let biasedexponent = (f & 0x7F800000u) >> 23u;
return i32(biasedexponent) - 127;
}
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
fn vec3_to_rgb9e5_(rgb_in: vec3<f32>) -> u32 {
let rgb = clamp(rgb_in, vec3(0.0), vec3(MAX_RGB9E5_));
let maxrgb = max(rgb.r, max(rgb.g, rgb.b));
var exp_shared = max(-RGB9E5_EXP_BIAS - 1, floor_log2_(maxrgb)) + 1 + RGB9E5_EXP_BIAS;
var denom = exp2(f32(exp_shared - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS));
let maxm = i32(floor(maxrgb / denom + 0.5));
if (maxm == RGB9E5_MANTISSA_VALUES) {
denom *= 2.0;
exp_shared += 1;
}
let n = vec3<u32>(floor(rgb / denom + 0.5));
return (u32(exp_shared) << 27u) | (n.b << 18u) | (n.g << 9u) | (n.r << 0u);
}
// Builtin extractBits() is not working on WEBGL or DX12
// DX12: HLSL: Unimplemented("write_expr_math ExtractBits")
fn extract_bits(value: u32, offset: u32, bits: u32) -> u32 {
let mask = (1u << bits) - 1u;
return (value >> offset) & mask;
}
fn rgb9e5_to_vec3_(v: u32) -> vec3<f32> {
let exponent = i32(extract_bits(v, 27u, RGB9E5_EXPONENT_BITS)) - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS;
let scale = exp2(f32(exponent));
return vec3(
f32(extract_bits(v, 0u, RGB9E5_MANTISSA_BITSU)),
f32(extract_bits(v, 9u, RGB9E5_MANTISSA_BITSU)),
f32(extract_bits(v, 18u, RGB9E5_MANTISSA_BITSU))
) * scale;
}

View file

@ -1,4 +1,5 @@
#define_import_path bevy_pbr::utils
#import bevy_pbr::rgb9e5
const PI: f32 = 3.141592653589793;
const HALF_PI: f32 = 1.57079632679;
@ -27,3 +28,23 @@ fn random1D(s: f32) -> f32 {
fn coords_to_viewport_uv(position: vec2<f32>, viewport: vec4<f32>) -> vec2<f32> {
return (position - viewport.xy) / viewport.zw;
}
// https://jcgt.org/published/0003/02/01/paper.pdf
// For encoding normals or unit direction vectors as octahedral coordinates.
fn octahedral_encode(v: vec3<f32>) -> vec2<f32> {
var n = v / (abs(v.x) + abs(v.y) + abs(v.z));
let octahedral_wrap = (1.0 - abs(n.yx)) * select(vec2(-1.0), vec2(1.0), n.xy > 0.0);
let n_xy = select(octahedral_wrap, n.xy, n.z >= 0.0);
return n_xy * 0.5 + 0.5;
}
// For decoding normals or unit direction vectors from octahedral coordinates.
fn octahedral_decode(v: vec2<f32>) -> vec3<f32> {
let f = v * 2.0 - 1.0;
var n = vec3(f.xy, 1.0 - abs(f.x) - abs(f.y));
let t = saturate(-n.z);
let w = select(vec2(t), vec2(-t), n.xy >= vec2(0.0));
n = vec3(n.xy + w, n.z);
return normalize(n);
}

View file

@ -127,8 +127,8 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin {
.add_render_graph_edges(
CORE_3D,
&[
// PREPASS -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS
bevy_core_pipeline::core_3d::graph::node::PREPASS,
// END_PRE_PASSES -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS
bevy_core_pipeline::core_3d::graph::node::END_PREPASSES,
draw_3d_graph::node::SCREEN_SPACE_AMBIENT_OCCLUSION,
bevy_core_pipeline::core_3d::graph::node::START_MAIN_PASS,
],
@ -797,6 +797,7 @@ fn prepare_ssao_bind_groups(
mip_level_count: Some(1),
..default()
};
let preprocess_depth_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
label: Some("ssao_preprocess_depth_bind_group"),
layout: &pipelines.preprocess_depth_bind_group_layout,

View file

@ -61,7 +61,6 @@ fn fallback_image_new(
value: u8,
) -> GpuImage {
// TODO make this configurable per channel
let data = vec![value; format.pixel_size()];
let extents = Extent3d {
width: 1,
@ -72,19 +71,29 @@ fn fallback_image_new(
},
};
let image_dimension = dimension.compatible_texture_dimension();
// We can't create textures with data when it's a depth texture or when using multiple samples
let create_texture_with_data = !format.is_depth_stencil_format() && samples == 1;
let mut image = Image::new_fill(extents, image_dimension, &data, format);
let image_dimension = dimension.compatible_texture_dimension();
let mut image = if create_texture_with_data {
let data = vec![value; format.pixel_size()];
Image::new_fill(extents, image_dimension, &data, format)
} else {
let mut image = Image::default();
image.texture_descriptor.dimension = TextureDimension::D2;
image.texture_descriptor.size = extents;
image.texture_descriptor.format = format;
image
};
image.texture_descriptor.sample_count = samples;
if image_dimension == TextureDimension::D2 {
image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
}
// We can't create textures with data when it's a depth texture or when using multiple samples
let texture = if format.is_depth_stencil_format() || samples > 1 {
render_device.create_texture(&image.texture_descriptor)
} else {
let texture = if create_texture_with_data {
render_device.create_texture_with_data(render_queue, &image.texture_descriptor, &image.data)
} else {
render_device.create_texture(&image.texture_descriptor)
};
let texture_view = texture.create_view(&TextureViewDescriptor {
@ -207,64 +216,30 @@ impl FromWorld for FallbackImageCubemap {
}
}
// TODO these could be combined in one FallbackImage cache.
/// A Cache of fallback textures that uses the sample count as a key
/// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key
///
/// # WARNING
/// Images using MSAA with sample count > 1 are not initialized with data, therefore,
/// you shouldn't sample them before writing data to them first.
#[derive(Resource, Deref, DerefMut, Default)]
pub struct FallbackImageMsaaCache(HashMap<u32, GpuImage>);
/// A Cache of fallback depth textures that uses the sample count as a key
///
/// # WARNING
/// Depth images are never initialized with data, therefore,
/// you shouldn't sample them before writing data to them first.
#[derive(Resource, Deref, DerefMut, Default)]
pub struct FallbackImageDepthCache(HashMap<u32, GpuImage>);
pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>);
#[derive(SystemParam)]
pub struct FallbackImagesMsaa<'w> {
cache: ResMut<'w, FallbackImageMsaaCache>,
pub struct FallbackImageMsaa<'w> {
cache: ResMut<'w, FallbackImageFormatMsaaCache>,
render_device: Res<'w, RenderDevice>,
render_queue: Res<'w, RenderQueue>,
default_sampler: Res<'w, DefaultImageSampler>,
}
impl<'w> FallbackImagesMsaa<'w> {
pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage {
self.cache.entry(sample_count).or_insert_with(|| {
impl<'w> FallbackImageMsaa<'w> {
pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage {
self.cache.entry((sample_count, format)).or_insert_with(|| {
fallback_image_new(
&self.render_device,
&self.render_queue,
&self.default_sampler,
TextureFormat::bevy_default(),
TextureViewDimension::D2,
sample_count,
255,
)
})
}
}
#[derive(SystemParam)]
pub struct FallbackImagesDepth<'w> {
cache: ResMut<'w, FallbackImageDepthCache>,
render_device: Res<'w, RenderDevice>,
render_queue: Res<'w, RenderQueue>,
default_sampler: Res<'w, DefaultImageSampler>,
}
impl<'w> FallbackImagesDepth<'w> {
pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage {
self.cache.entry(sample_count).or_insert_with(|| {
fallback_image_new(
&self.render_device,
&self.render_queue,
&self.default_sampler,
TextureFormat::Depth32Float,
format,
TextureViewDimension::D2,
sample_count,
255,

View file

@ -144,8 +144,7 @@ impl Plugin for ImagePlugin {
.init_resource::<FallbackImage>()
.init_resource::<FallbackImageZero>()
.init_resource::<FallbackImageCubemap>()
.init_resource::<FallbackImageMsaaCache>()
.init_resource::<FallbackImageDepthCache>();
.init_resource::<FallbackImageFormatMsaaCache>();
}
}
}

View file

@ -0,0 +1,428 @@
//! This example compares Forward, Forward + Prepass, and Deferred rendering.
use std::f32::consts::*;
use bevy::{
core_pipeline::{
fxaa::Fxaa,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
},
pbr::NotShadowReceiver,
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
pbr::{DefaultOpaqueRendererMethod, NotShadowCaster, OpaqueRendererMethod},
prelude::*,
render::render_resource::TextureFormat,
};
fn main() {
App::new()
.insert_resource(Msaa::Off)
.insert_resource(DefaultOpaqueRendererMethod::deferred())
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.0 / 5.0f32,
})
.insert_resource(DirectionalLightShadowMap { size: 4096 })
.add_plugins(DefaultPlugins)
.insert_resource(Normal(None))
.insert_resource(Pause(true))
.add_systems(Startup, (setup, setup_parallax))
.add_systems(
Update,
(animate_light_direction, switch_mode, spin, update_normal),
)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
commands.spawn((
Camera3dBundle {
camera: Camera {
// Deferred both supports both hdr: true and hdr: false
hdr: false,
..default()
},
transform: Transform::from_xyz(0.7, 0.7, 1.0)
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
..default()
},
FogSettings {
color: Color::rgba(0.05, 0.05, 0.05, 1.0),
falloff: FogFalloff::Linear {
start: 1.0,
end: 8.0,
},
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
},
DepthPrepass,
MotionVectorPrepass,
DeferredPrepass,
Fxaa::default(),
));
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
cascade_shadow_config: CascadeShadowConfigBuilder {
num_cascades: 3,
maximum_distance: 10.0,
..default()
}
.into(),
transform: Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 0.0, -FRAC_PI_4)),
..default()
});
// FlightHelmet
let helmet_scene = asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0");
commands.spawn(SceneBundle {
scene: helmet_scene.clone(),
..default()
});
commands.spawn(SceneBundle {
scene: helmet_scene,
transform: Transform::from_xyz(-3.0, 0.0, -3.0),
..default()
});
let mut forward_mat: StandardMaterial = Color::rgb(0.1, 0.2, 0.1).into();
forward_mat.opaque_render_method = OpaqueRendererMethod::Forward;
let forward_mat_h = materials.add(forward_mat);
// Plane
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(50.0).into()),
material: forward_mat_h.clone(),
..default()
});
let cube_h = meshes.add(Mesh::from(shape::Cube { size: 0.1 }));
let sphere_h = meshes.add(Mesh::from(shape::UVSphere {
radius: 0.125,
sectors: 128,
stacks: 128,
}));
// Cubes
commands.spawn(PbrBundle {
mesh: cube_h.clone(),
material: forward_mat_h.clone(),
transform: Transform::from_xyz(-0.3, 0.5, -0.2),
..default()
});
commands.spawn(PbrBundle {
mesh: cube_h,
material: forward_mat_h,
transform: Transform::from_xyz(0.2, 0.5, 0.2),
..default()
});
let sphere_color = Color::rgb(10.0, 4.0, 1.0);
let sphere_pos = Transform::from_xyz(0.4, 0.5, -0.8);
// Emissive sphere
let mut unlit_mat: StandardMaterial = sphere_color.into();
unlit_mat.unlit = true;
commands.spawn((
PbrBundle {
mesh: sphere_h.clone(),
material: materials.add(unlit_mat),
transform: sphere_pos,
..default()
},
NotShadowCaster,
));
// Light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1.0,
radius: 0.125,
shadows_enabled: true,
color: sphere_color,
..default()
},
transform: sphere_pos,
..default()
});
// Spheres
for i in 0..6 {
let j = i % 3;
let s_val = if i < 3 { 0.0 } else { 0.2 };
let material = if j == 0 {
materials.add(StandardMaterial {
base_color: Color::rgb(s_val, s_val, 1.0),
perceptual_roughness: 0.089,
metallic: 0.0,
..default()
})
} else if j == 1 {
materials.add(StandardMaterial {
base_color: Color::rgb(s_val, 1.0, s_val),
perceptual_roughness: 0.089,
metallic: 0.0,
..default()
})
} else {
materials.add(StandardMaterial {
base_color: Color::rgb(1.0, s_val, s_val),
perceptual_roughness: 0.089,
metallic: 0.0,
..default()
})
};
commands.spawn(PbrBundle {
mesh: sphere_h.clone(),
material,
transform: Transform::from_xyz(
j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } - 0.4,
0.125,
-j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } + 0.4,
),
..default()
});
}
// sky
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::default())),
material: materials.add(StandardMaterial {
base_color: Color::hex("888888").unwrap(),
unlit: true,
cull_mode: None,
..default()
}),
transform: Transform::from_scale(Vec3::splat(1_000_000.0)),
..default()
},
NotShadowCaster,
NotShadowReceiver,
));
// Example instructions
commands.spawn(
TextBundle::from_section(
"",
TextStyle {
font_size: 18.0,
color: Color::WHITE,
..default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}
#[derive(Resource)]
struct Pause(bool);
fn animate_light_direction(
time: Res<Time>,
mut query: Query<&mut Transform, With<DirectionalLight>>,
pause: Res<Pause>,
) {
if pause.0 {
return;
}
for mut transform in &mut query {
transform.rotate_y(time.delta_seconds() * PI / 5.0);
}
}
fn setup_parallax(
mut commands: Commands,
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
mut normal: ResMut<Normal>,
asset_server: Res<AssetServer>,
) {
// The normal map. Note that to generate it in the GIMP image editor, you should
// open the depth map, and do Filters → Generic → Normal Map
// You should enable the "flip X" checkbox.
let normal_handle = asset_server.load("textures/parallax_example/cube_normal.png");
normal.0 = Some(normal_handle);
let mut cube: Mesh = shape::Cube { size: 0.15 }.into();
// NOTE: for normal maps and depth maps to work, the mesh
// needs tangents generated.
cube.generate_tangents().unwrap();
let parallax_material = materials.add(StandardMaterial {
perceptual_roughness: 0.4,
base_color_texture: Some(asset_server.load("textures/parallax_example/cube_color.png")),
normal_map_texture: normal.0.clone(),
// The depth map is a greyscale texture where black is the highest level and
// white the lowest.
depth_map: Some(asset_server.load("textures/parallax_example/cube_depth.png")),
parallax_depth_scale: 0.09,
parallax_mapping_method: ParallaxMappingMethod::Relief { max_steps: 4 },
max_parallax_layer_count: 5.0f32.exp2(),
..default()
});
commands.spawn((
PbrBundle {
mesh: meshes.add(cube),
material: parallax_material,
transform: Transform::from_xyz(0.4, 0.2, -0.8),
..default()
},
Spin { speed: 0.3 },
));
}
/// Store handle of the normal to later modify its format in [`update_normal`].
#[derive(Resource)]
struct Normal(Option<Handle<Image>>);
// See `examples/3d/parallax_mapping.rs` example for reasoning
fn update_normal(
mut already_ran: Local<bool>,
mut images: ResMut<Assets<Image>>,
normal: Res<Normal>,
) {
if *already_ran {
return;
}
if let Some(normal) = normal.0.as_ref() {
if let Some(image) = images.get_mut(normal) {
image.texture_descriptor.format = TextureFormat::Rgba8Unorm;
*already_ran = true;
}
}
}
#[derive(Component)]
struct Spin {
speed: f32,
}
fn spin(time: Res<Time>, mut query: Query<(&mut Transform, &Spin)>, pause: Res<Pause>) {
if pause.0 {
return;
}
for (mut transform, spin) in query.iter_mut() {
transform.rotate_local_y(spin.speed * time.delta_seconds());
transform.rotate_local_x(spin.speed * time.delta_seconds());
transform.rotate_local_z(-spin.speed * time.delta_seconds());
}
}
#[derive(Resource, Default)]
enum DefaultRenderMode {
#[default]
Deferred,
Forward,
ForwardPrepass,
}
#[allow(clippy::too_many_arguments)]
fn switch_mode(
mut text: Query<&mut Text>,
mut commands: Commands,
keys: Res<Input<KeyCode>>,
mut default_opaque_renderer_method: ResMut<DefaultOpaqueRendererMethod>,
mut materials: ResMut<Assets<StandardMaterial>>,
cameras: Query<Entity, With<Camera>>,
mut pause: ResMut<Pause>,
mut hide_ui: Local<bool>,
mut mode: Local<DefaultRenderMode>,
) {
let mut text = text.single_mut();
let text = &mut text.sections[0].value;
text.clear();
if keys.just_pressed(KeyCode::Space) {
pause.0 = !pause.0;
}
if keys.just_pressed(KeyCode::Key1) {
*mode = DefaultRenderMode::Deferred;
default_opaque_renderer_method.set_to_deferred();
println!("DefaultOpaqueRendererMethod: Deferred");
for _ in materials.iter_mut() {}
for camera in &cameras {
commands.entity(camera).remove::<NormalPrepass>();
commands.entity(camera).insert(DepthPrepass);
commands.entity(camera).insert(MotionVectorPrepass);
commands.entity(camera).insert(DeferredPrepass);
}
}
if keys.just_pressed(KeyCode::Key2) {
*mode = DefaultRenderMode::Forward;
default_opaque_renderer_method.set_to_forward();
println!("DefaultOpaqueRendererMethod: Forward");
for _ in materials.iter_mut() {}
for camera in &cameras {
commands.entity(camera).remove::<NormalPrepass>();
commands.entity(camera).remove::<DepthPrepass>();
commands.entity(camera).remove::<MotionVectorPrepass>();
commands.entity(camera).remove::<DeferredPrepass>();
}
}
if keys.just_pressed(KeyCode::Key3) {
*mode = DefaultRenderMode::ForwardPrepass;
default_opaque_renderer_method.set_to_forward();
println!("DefaultOpaqueRendererMethod: Forward + Prepass");
for _ in materials.iter_mut() {}
for camera in &cameras {
commands.entity(camera).insert(NormalPrepass);
commands.entity(camera).insert(DepthPrepass);
commands.entity(camera).insert(MotionVectorPrepass);
commands.entity(camera).remove::<DeferredPrepass>();
}
}
if keys.just_pressed(KeyCode::H) {
*hide_ui = !*hide_ui;
}
if !*hide_ui {
text.push_str("(H) Hide UI\n");
text.push_str("(Space) Play/Pause\n\n");
text.push_str("Rendering Method:\n");
text.push_str(&format!(
"(1) {} Deferred\n",
if let DefaultRenderMode::Deferred = *mode {
">"
} else {
""
}
));
text.push_str(&format!(
"(2) {} Forward\n",
if let DefaultRenderMode::Forward = *mode {
">"
} else {
""
}
));
text.push_str(&format!(
"(3) {} Forward + Prepass\n",
if let DefaultRenderMode::ForwardPrepass = *mode {
">"
} else {
""
}
));
}
}

View file

@ -121,6 +121,7 @@ Example | Description
[Anti-aliasing](../examples/3d/anti_aliasing.rs) | Compares different anti-aliasing methods
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
[Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
[Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene

View file

@ -6,6 +6,7 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins.set(PbrPlugin {
prepass_enabled: false,
..default()
}))
.run();
}