Use prepass shaders for shadows (#7784)

# Objective

- Fixes #4372.

## Solution

- Use the prepass shaders for the shadow passes.
- Move `DEPTH_CLAMP_ORTHO` from `ShadowPipelineKey` to `MeshPipelineKey` and the associated clamp operation from `depth.wgsl` to `prepass.wgsl`.
- Remove `depth.wgsl` .
- Replace `ShadowPipeline` with `ShadowSamplers`.

Instead of running the custom `ShadowPipeline` we run the `PrepassPipeline` with the `DEPTH_PREPASS` flag and additionally the `DEPTH_CLAMP_ORTHO` flag for directional lights as well as the `ALPHA_MASK` flag for materials that use `AlphaMode::Mask(_)`.
This commit is contained in:
Edgar Geier 2023-03-02 08:21:21 +00:00
parent a39c22386b
commit e54103fd69
8 changed files with 125 additions and 260 deletions

View file

@ -52,8 +52,8 @@ use bevy_render::{
extract_resource::ExtractResourcePlugin,
prelude::Color,
render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
render_resource::{Shader, SpecializedMeshPipelines},
render_phase::sort_phase_system,
render_resource::Shader,
view::{ViewSet, VisibilitySystems},
ExtractSchedule, RenderApp, RenderSet,
};
@ -80,8 +80,6 @@ pub const PBR_FUNCTIONS_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 16550102964439850292);
pub const PBR_AMBIENT_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2441520459096337034);
pub const SHADOW_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696);
/// Sets up the entire PBR infrastructure of bevy.
pub struct PbrPlugin {
@ -144,12 +142,6 @@ impl Plugin for PbrPlugin {
Shader::from_wgsl
);
load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
SHADOW_SHADER_HANDLE,
"render/depth.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_PREPASS_SHADER_HANDLE,
@ -293,17 +285,12 @@ impl Plugin for PbrPlugin {
.after(render::prepare_lights)
.in_set(RenderLightSystems::PrepareClusters),
)
.add_system(render::queue_shadows.in_set(RenderLightSystems::QueueShadows))
.add_system(render::queue_shadow_view_bind_group.in_set(RenderSet::Queue))
.add_system(sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort))
.init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<ShadowSamplers>()
.init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedMeshPipelines<ShadowPipeline>>();
.init_resource::<GlobalLightMeta>();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME)

View file

@ -1,6 +1,7 @@
use crate::{
AlphaMode, DrawMesh, EnvironmentMapLight, MeshPipeline, MeshPipelineKey, MeshUniform,
PrepassPlugin, SetMeshBindGroup, SetMeshViewBindGroup,
queue_mesh_view_bind_groups, render, AlphaMode, DrawMesh, DrawPrepass, EnvironmentMapLight,
MeshPipeline, MeshPipelineKey, MeshUniform, PrepassPlugin, RenderLightSystems,
SetMeshBindGroup, SetMeshViewBindGroup, Shadow,
};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
@ -188,6 +189,8 @@ where
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<DrawFunctions<Shadow>>()
.add_render_command::<Shadow, DrawPrepass<M>>()
.add_render_command::<Transparent3d, DrawMaterial<M>>()
.add_render_command::<Opaque3d, DrawMaterial<M>>()
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
@ -201,6 +204,12 @@ where
.in_set(RenderSet::Prepare)
.after(PrepareAssetSet::PreAssetPrepare),
)
.add_system(render::queue_shadows::<M>.in_set(RenderLightSystems::QueueShadows))
.add_system(
render::queue_shadow_view_bind_group::<M>
.in_set(RenderSet::Queue)
.ambiguous_with(queue_mesh_view_bind_groups), // queue_mesh_view_bind_groups does not read `shadow_view_bind_group`),
)
.add_system(queue_material_meshes::<M>.in_set(RenderSet::Queue));
}

View file

@ -202,6 +202,13 @@ where
shader_defs.push("ALPHA_MASK".into());
}
let blend_key = key
.mesh_key
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA {
shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into());
}
if layout.contains(Mesh::ATTRIBUTE_POSITION) {
shader_defs.push("VERTEX_POSITIONS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
@ -215,6 +222,9 @@ where
"MAX_CASCADES_PER_LIGHT".to_string(),
MAX_CASCADES_PER_LIGHT as i32,
));
if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) {
shader_defs.push("DEPTH_CLAMP_ORTHO".into());
}
if layout.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
@ -244,9 +254,11 @@ where
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
// The fragment shader is only used when the normal prepass is enabled or the material uses an alpha mask
let fragment = if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS)
|| key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK)
// The fragment shader is only used when the normal prepass is enabled or the material uses alpha cutoff values
let fragment = if key
.mesh_key
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::ALPHA_MASK)
|| blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA
{
// Use the fragment shader from the material if present
let frag_shader_handle = if let Some(handle) = &self.material_fragment_shader {

View file

@ -49,6 +49,9 @@ fn vertex(vertex: Vertex) -> VertexOutput {
#endif // SKINNED
out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0));
#ifdef DEPTH_CLAMP_ORTHO
out.clip_position.z = min(out.clip_position.z, 1.0);
#endif // DEPTH_CLAMP_ORTHO
#ifdef VERTEX_UVS
out.uv = vertex.uv;

View file

@ -1,46 +0,0 @@
#import bevy_pbr::mesh_view_types
#import bevy_pbr::mesh_types
@group(0) @binding(0)
var<uniform> view: View;
@group(1) @binding(0)
var<uniform> mesh: Mesh;
#ifdef SKINNED
@group(1) @binding(1)
var<uniform> joint_matrices: SkinnedMesh;
#import bevy_pbr::skinning
#endif
// NOTE: Bindings must come before functions that use them!
#import bevy_pbr::mesh_functions
struct Vertex {
@location(0) position: vec3<f32>,
#ifdef SKINNED
@location(4) joint_indices: vec4<u32>,
@location(5) joint_weights: vec4<f32>,
#endif
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
#ifdef SKINNED
let model = skin_model(vertex.joint_indices, vertex.joint_weights);
#else
let model = mesh.model;
#endif
var out: VertexOutput;
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
#ifdef DEPTH_CLAMP_ORTHO
out.clip_position.z = min(out.clip_position.z, 1.0);
#endif
return out;
}

View file

@ -1,9 +1,10 @@
use crate::{
directional_light_order, point_light_order, AmbientLight, Cascade, CascadeShadowConfig,
Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities, DirectionalLight,
DirectionalLightShadowMap, DrawMesh, EnvironmentMapLight, GlobalVisiblePointLights,
MeshPipeline, NotShadowCaster, PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight,
VisiblePointLights, SHADOW_SHADER_HANDLE,
directional_light_order, point_light_order, AlphaMode, AmbientLight, Cascade,
CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities,
DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight,
GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey,
NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight,
VisiblePointLights,
};
use bevy_asset::Handle;
use bevy_core_pipeline::core_3d::Transparent3d;
@ -15,20 +16,17 @@ use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles
use bevy_render::{
camera::Camera,
color::Color,
mesh::{Mesh, MeshVertexBufferLayout},
mesh::Mesh,
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{
CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, RenderCommand,
RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass,
RenderCommandResult, RenderPhase, TrackedRenderPass,
},
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::*,
view::{
ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms,
VisibleEntities,
},
view::{ComputedVisibility, ExtractedView, ViewUniformOffset, ViewUniforms, VisibleEntities},
Extract,
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
@ -37,7 +35,10 @@ use bevy_utils::{
tracing::{error, warn},
HashMap,
};
use std::num::{NonZeroU32, NonZeroU64};
use std::{
hash::Hash,
num::{NonZeroU32, NonZeroU64},
};
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderLightSystems {
@ -231,43 +232,17 @@ pub const MAX_CASCADES_PER_LIGHT: usize = 1;
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
#[derive(Resource, Clone)]
pub struct ShadowPipeline {
pub view_layout: BindGroupLayout,
pub mesh_layout: BindGroupLayout,
pub skinned_mesh_layout: BindGroupLayout,
pub struct ShadowSamplers {
pub point_light_sampler: Sampler,
pub directional_light_sampler: Sampler,
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
impl FromWorld for ShadowPipeline {
impl FromWorld for ShadowSamplers {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
// View
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
],
label: Some("shadow_view_layout"),
});
let mesh_pipeline = world.resource::<MeshPipeline>();
let skinned_mesh_layout = mesh_pipeline.skinned_mesh_layout.clone();
ShadowPipeline {
view_layout,
mesh_layout: mesh_pipeline.mesh_layout.clone(),
skinned_mesh_layout,
ShadowSamplers {
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
@ -292,120 +267,6 @@ impl FromWorld for ShadowPipeline {
}
}
bitflags::bitflags! {
#[repr(transparent)]
pub struct ShadowPipelineKey: u32 {
const NONE = 0;
const DEPTH_CLAMP_ORTHO = 1;
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
}
}
impl ShadowPipelineKey {
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = 32 - 3;
pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self {
let primitive_topology_bits = ((primitive_topology as u32)
& Self::PRIMITIVE_TOPOLOGY_MASK_BITS)
<< Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
Self::from_bits(primitive_topology_bits).unwrap()
}
pub fn primitive_topology(&self) -> PrimitiveTopology {
let primitive_topology_bits =
(self.bits >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS;
match primitive_topology_bits {
x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList,
x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList,
x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip,
x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList,
x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip,
_ => PrimitiveTopology::default(),
}
}
}
impl SpecializedMeshPipeline for ShadowPipeline {
type Key = ShadowPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut vertex_attributes = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)];
let mut bind_group_layout = vec![self.view_layout.clone()];
let mut shader_defs = Vec::new();
shader_defs.push(ShaderDefVal::UInt(
"MAX_DIRECTIONAL_LIGHTS".to_string(),
MAX_DIRECTIONAL_LIGHTS as u32,
));
shader_defs.push(ShaderDefVal::UInt(
"MAX_CASCADES_PER_LIGHT".to_string(),
MAX_CASCADES_PER_LIGHT as u32,
));
if key.contains(ShadowPipelineKey::DEPTH_CLAMP_ORTHO) {
// Avoid clipping shadow casters that are behind the near plane.
shader_defs.push("DEPTH_CLAMP_ORTHO".into());
}
if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
&& layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
{
shader_defs.push("SKINNED".into());
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4));
vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5));
bind_group_layout.push(self.skinned_mesh_layout.clone());
} else {
bind_group_layout.push(self.mesh_layout.clone());
}
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
Ok(RenderPipelineDescriptor {
vertex: VertexState {
shader: SHADOW_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs,
buffers: vec![vertex_buffer_layout],
},
fragment: None,
layout: bind_group_layout,
primitive: PrimitiveState {
topology: key.primitive_topology(),
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(DepthStencilState {
format: SHADOW_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::GreaterEqual,
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(),
label: Some("shadow_pipeline".into()),
push_constant_ranges: Vec::new(),
})
}
}
#[derive(Component)]
pub struct ExtractedClusterConfig {
/// Special near value for cluster calculations
@ -1688,9 +1549,9 @@ pub fn prepare_clusters(
}
}
pub fn queue_shadow_view_bind_group(
pub fn queue_shadow_view_bind_group<M: Material>(
render_device: Res<RenderDevice>,
shadow_pipeline: Res<ShadowPipeline>,
prepass_pipeline: Res<PrepassPipeline<M>>,
mut light_meta: ResMut<LightMeta>,
view_uniforms: Res<ViewUniforms>,
) {
@ -1702,27 +1563,30 @@ pub fn queue_shadow_view_bind_group(
resource: view_binding,
}],
label: Some("shadow_view_bind_group"),
layout: &shadow_pipeline.view_layout,
layout: &prepass_pipeline.view_layout,
}));
}
}
#[allow(clippy::too_many_arguments)]
pub fn queue_shadows(
pub fn queue_shadows<M: Material>(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
shadow_pipeline: Res<ShadowPipeline>,
casting_meshes: Query<&Handle<Mesh>, Without<NotShadowCaster>>,
prepass_pipeline: Res<PrepassPipeline<M>>,
casting_meshes: Query<(&Handle<Mesh>, &Handle<M>), Without<NotShadowCaster>>,
render_meshes: Res<RenderAssets<Mesh>>,
mut pipelines: ResMut<SpecializedMeshPipelines<ShadowPipeline>>,
render_materials: Res<RenderMaterials<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
pipeline_cache: Res<PipelineCache>,
view_lights: Query<(Entity, &ViewLightEntities)>,
mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>,
point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>,
directional_light_entities: Query<&CascadesVisibleEntities, With<ExtractedDirectionalLight>>,
spot_light_entities: Query<&VisibleEntities, With<ExtractedPointLight>>,
) {
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
for (entity, view_lights) in &view_lights {
let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawShadowMesh>();
let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
for view_light_entity in view_lights.lights.iter().copied() {
let (light_entity, mut shadow_phase) =
view_light_shadow_phases.get_mut(view_light_entity).unwrap();
@ -1753,17 +1617,34 @@ pub fn queue_shadows(
// NOTE: Lights with shadow mapping disabled will have no visible entities
// so no meshes will be queued
for entity in visible_entities.iter().copied() {
if let Ok(mesh_handle) = casting_meshes.get(entity) {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let mut key =
ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) {
if let (Some(mesh), Some(material)) = (
render_meshes.get(mesh_handle),
render_materials.get(material_handle),
) {
let mut mesh_key =
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
| MeshPipelineKey::DEPTH_PREPASS;
if is_directional_light {
key |= ShadowPipelineKey::DEPTH_CLAMP_ORTHO;
mesh_key |= MeshPipelineKey::DEPTH_CLAMP_ORTHO;
}
let alpha_mode = material.properties.alpha_mode;
match alpha_mode {
AlphaMode::Mask(_) => {
mesh_key |= MeshPipelineKey::ALPHA_MASK;
}
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add => {
mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA;
}
_ => {}
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&shadow_pipeline,
key,
&prepass_pipeline,
MaterialPipelineKey {
mesh_key,
bind_group_data: material.key.clone(),
},
&mesh.layout,
);
@ -1892,13 +1773,6 @@ impl Node for ShadowPassNode {
}
}
pub type DrawShadowMesh = (
SetItemPipeline,
SetShadowViewBindGroup<0>,
SetMeshBindGroup<1>,
DrawMesh,
);
pub struct SetShadowViewBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Shadow> for SetShadowViewBindGroup<I> {
type Param = SRes<LightMeta>;

View file

@ -1,9 +1,8 @@
use crate::{
environment_map, prepass, queue_shadow_view_bind_group, EnvironmentMapLight, FogMeta,
GlobalLightMeta, GpuFog, GpuLights, GpuPointLights, LightMeta, NotShadowCaster,
NotShadowReceiver, ShadowPipeline, ViewClusterBindings, ViewFogUniformOffset,
ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights,
GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, ShadowSamplers,
ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
};
use bevy_app::{IntoSystemAppConfigs, Plugin};
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
@ -111,11 +110,7 @@ impl Plugin for MeshRenderPlugin {
.add_systems((extract_meshes, extract_skinned_meshes).in_schedule(ExtractSchedule))
.add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare))
.add_system(queue_mesh_bind_group.in_set(RenderSet::Queue))
.add_system(
queue_mesh_view_bind_groups
.in_set(RenderSet::Queue)
.ambiguous_with(queue_shadow_view_bind_group), // queue_mesh_view_bind_groups does not read `shadow_view_bind_group`
);
.add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue));
}
}
}
@ -579,6 +574,7 @@ bitflags::bitflags! {
const NORMAL_PREPASS = (1 << 4);
const ALPHA_MASK = (1 << 5);
const ENVIRONMENT_MAP = (1 << 6);
const DEPTH_CLAMP_ORTHO = (1 << 7);
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); //
@ -937,7 +933,7 @@ pub fn queue_mesh_view_bind_groups(
mut commands: Commands,
render_device: Res<RenderDevice>,
mesh_pipeline: Res<MeshPipeline>,
shadow_pipeline: Res<ShadowPipeline>,
shadow_samplers: Res<ShadowSamplers>,
light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalLightMeta>,
fog_meta: Res<FogMeta>,
@ -1003,7 +999,7 @@ pub fn queue_mesh_view_bind_groups(
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_pipeline.point_light_sampler),
resource: BindingResource::Sampler(&shadow_samplers.point_light_sampler),
},
BindGroupEntry {
binding: 4,
@ -1013,7 +1009,7 @@ pub fn queue_mesh_view_bind_groups(
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(&shadow_pipeline.directional_light_sampler),
resource: BindingResource::Sampler(&shadow_samplers.directional_light_sampler),
},
BindGroupEntry {
binding: 6,

View file

@ -1,6 +1,8 @@
#import bevy_pbr::prepass_bindings
#import bevy_pbr::pbr_bindings
#ifdef NORMAL_PREPASS
#import bevy_pbr::pbr_functions
#endif // NORMAL_PREPASS
struct FragmentInput {
@builtin(front_facing) is_front: bool,
@ -16,9 +18,23 @@ struct FragmentInput {
#endif // NORMAL_PREPASS
};
// 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 ALPHA_MASK
// This is a workaround since the preprocessor does not support
// #if defined(ALPHA_MASK) || defined(BLEND_PREMULTIPLIED_ALPHA)
#ifndef ALPHA_MASK
#ifndef BLEND_PREMULTIPLIED_ALPHA
#define EMPTY_PREPASS_ALPHA_DISCARD
#endif // BLEND_PREMULTIPLIED_ALPHA not defined
#endif // ALPHA_MASK not defined
#ifndef EMPTY_PREPASS_ALPHA_DISCARD
var output_color: vec4<f32> = material.base_color;
#ifdef VERTEX_UVS
@ -27,10 +43,24 @@ fn prepass_alpha_discard(in: FragmentInput) {
}
#endif // VERTEX_UVS
#ifdef ALPHA_MASK
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) && output_color.a < material.alpha_cutoff {
discard;
}
#endif // ALPHA_MASK
#ifdef BLEND_PREMULTIPLIED_ALPHA
let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD)
&& output_color.a < PREMULTIPLIED_ALPHA_CUTOFF {
discard;
} else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED
&& all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) {
discard;
}
#endif // BLEND_PREMULTIPLIED_ALPHA
#endif // EMPTY_PREPASS_ALPHA_DISCARD not defined
}
#ifdef NORMAL_PREPASS