Add support for opaque, alpha mask, and alpha blend modes (#3072)

# Objective

Add depth prepass and support for opaque, alpha mask, and alpha blend modes for the 3D PBR target.

## Solution

NOTE: This is based on top of #2861 frustum culling. Just lining it up to keep @cart loaded with the review train. 🚂 

There are a lot of important details here. Big thanks to @cwfitzgerald of wgpu, naga, and rend3 fame for explaining how to do it properly!

* An `AlphaMode` component is added that defines whether a material should be considered opaque, an alpha mask (with a cutoff value that defaults to 0.5, the same as glTF), or transparent and should be alpha blended
* Two depth prepasses are added:
  * Opaque does a plain vertex stage
  * Alpha mask does the vertex stage but also a fragment stage that samples the colour for the fragment and discards if its alpha value is below the cutoff value
  * Both are sorted front to back, not that it matters for these passes. (Maybe there should be a way to skip sorting?)
* Three main passes are added:
  * Opaque and alpha mask passes use a depth comparison function of Equal such that only the geometry that was closest is processed further, due to early-z testing
  * The transparent pass uses the Greater depth comparison function so that only transparent objects that are closer than anything opaque are rendered
  * The opaque fragment shading is as before except that alpha is explicitly set to 1.0
  * Alpha mask fragment shading sets the alpha value to 1.0 if it is equal to or above the cutoff, as defined by glTF
  * Opaque and alpha mask are sorted front to back (again not that it matters as we will skip anything that is not equal... maybe sorting is no longer needed here?)
  * Transparent is sorted back to front. Transparent fragment shading uses the alpha blending over operator

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Robert Swain 2021-11-16 03:03:27 +00:00
parent 029a7c03d8
commit 213839f503
10 changed files with 420 additions and 130 deletions

View file

@ -75,11 +75,15 @@ impl Plugin for CorePipelinePlugin {
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
@ -147,6 +151,76 @@ impl PhaseItem for Transparent2d {
}
}
pub struct Opaque3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for Opaque3d {
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}
impl EntityPhaseItem for Opaque3d {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}
impl CachedPipelinePhaseItem for Opaque3d {
#[inline]
fn cached_pipeline(&self) -> CachedPipelineId {
self.pipeline
}
}
pub struct AlphaMask3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for AlphaMask3d {
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}
impl EntityPhaseItem for AlphaMask3d {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}
impl CachedPipelinePhaseItem for AlphaMask3d {
#[inline]
fn cached_pipeline(&self) -> CachedPipelineId {
self.pipeline
}
}
pub struct Transparent3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
@ -203,9 +277,11 @@ pub fn extract_core_pipeline_camera_phases(
}
if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) {
if let Some(entity) = camera_3d.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent3d>::default());
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
}
}
@ -215,7 +291,14 @@ pub fn prepare_core_views_system(
mut texture_cache: ResMut<TextureCache>,
msaa: Res<Msaa>,
render_device: Res<RenderDevice>,
views_3d: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3d>>>,
views_3d: Query<
(Entity, &ExtractedView),
(
With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>,
With<RenderPhase<Transparent3d>>,
),
>,
) {
for (entity, view) in views_3d.iter() {
let cached_texture = texture_cache.get(

View file

@ -1,12 +1,9 @@
use crate::{ClearColor, Transparent3d};
use crate::{AlphaMask3d, ClearColor, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPassDescriptor,
},
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget},
};
@ -14,6 +11,8 @@ use bevy_render2::{
pub struct MainPass3dNode {
query: QueryState<
(
&'static RenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
@ -48,52 +47,119 @@ impl Node for MainPass3dNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (transparent_phase, target, depth) = self
let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth) = self
.query
.get_manual(world, view_entity)
.expect("view entity should exist");
let clear_color = world.get_resource::<ClearColor>().unwrap();
let pass_descriptor = RenderPassDescriptor {
label: Some("main_pass_3d"),
color_attachments: &[RenderPassColorAttachment {
view: if let Some(sampled_target) = &target.sampled_target {
sampled_target
} else {
&target.view
},
resolve_target: if target.sampled_target.is_some() {
Some(&target.view)
} else {
None
},
ops: Operations {
{
// Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_opaque_pass_3d"),
// NOTE: The opaque pass clears and initializes the color
// buffer as well as writing to it.
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Clear(clear_color.0.into()),
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Clear(0.0),
store: true,
})],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The opaque main pass clears and writes to the depth buffer.
depth_ops: Some(Operations {
load: LoadOp::Clear(0.0),
store: true,
}),
stencil_ops: None,
}),
stencil_ops: None,
}),
};
};
let draw_functions = world
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();
let draw_functions = world.get_resource::<DrawFunctions<Opaque3d>>().unwrap();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in opaque_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
{
// Run the alpha mask pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_alpha_mask_pass_3d"),
// NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
})],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The alpha mask pass loads the depth buffer and possibly overwrites it
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};
let draw_functions = world.get_resource::<DrawFunctions<AlphaMask3d>>().unwrap();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in alpha_mask_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
{
// Run the transparent pass, sorted back-to-front
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_transparent_pass_3d"),
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
})],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: For the transparent pass we load the depth buffer but do not write to it.
// As the opaque and alpha mask passes run first, opaque meshes can occlude
// transparent ones.
depth_ops: Some(Operations {
load: LoadOp::Load,
store: false,
}),
stencil_ops: None,
}),
};
let draw_functions = world
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
Ok(())
}
}

View file

@ -6,7 +6,7 @@ use bevy_core::Name;
use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_math::{Mat4, Vec3};
use bevy_pbr2::{PbrBundle, StandardMaterial};
use bevy_pbr2::{AlphaMode, PbrBundle, StandardMaterial};
use bevy_render2::{
camera::{
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
@ -438,6 +438,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
emissive_texture,
unlit: material.unlit(),
alpha_mode: alpha_mode(material),
..Default::default()
}),
)
@ -649,6 +650,14 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
}
}
fn alpha_mode(material: &Material) -> AlphaMode {
match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}
async fn load_buffers(
gltf: &gltf::Gltf,
load_context: &LoadContext<'_>,

View file

@ -0,0 +1,22 @@
use bevy_ecs::reflect::ReflectComponent;
use bevy_reflect::Reflect;
// FIXME: This should probably be part of bevy_render2!
/// Alpha mode
#[derive(Debug, Reflect, Clone, PartialEq)]
#[reflect(Component)]
pub enum AlphaMode {
Opaque,
/// An alpha cutoff must be supplied where alpha values >= the cutoff
/// will be fully opaque and < will be fully transparent
Mask(f32),
Blend,
}
impl Eq for AlphaMode {}
impl Default for AlphaMode {
fn default() -> Self {
AlphaMode::Opaque
}
}

View file

@ -1,8 +1,10 @@
mod alpha;
mod bundle;
mod light;
mod material;
mod render;
pub use alpha::*;
pub use bundle::*;
pub use light::*;
pub use material::*;
@ -10,7 +12,7 @@ pub use render::*;
use bevy_app::prelude::*;
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_core_pipeline::Transparent3d;
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_render2::{
@ -109,6 +111,8 @@ impl Plugin for PbrPlugin {
.init_resource::<SpecializedPipelines<ShadowPipeline>>();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Opaque3d, DrawPbr>();
render_app.add_render_command::<AlphaMask3d, DrawPbr>();
render_app.add_render_command::<Transparent3d, DrawPbr>();
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();

View file

@ -1,4 +1,4 @@
use crate::{PbrPipeline, StandardMaterialFlags};
use crate::{AlphaMode, PbrPipeline, StandardMaterialFlags};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Handle};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
@ -7,9 +7,7 @@ use bevy_reflect::TypeUuid;
use bevy_render2::{
color::Color,
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_resource::{
BindGroup, Buffer, BufferInitDescriptor, BufferUsages, Sampler, TextureView,
},
render_resource::{BindGroup, Buffer, BufferInitDescriptor, BufferUsages},
renderer::RenderDevice,
texture::Image,
};
@ -47,6 +45,7 @@ pub struct StandardMaterial {
pub occlusion_texture: Option<Handle<Image>>,
pub double_sided: bool,
pub unlit: bool,
pub alpha_mode: AlphaMode,
}
impl Default for StandardMaterial {
@ -73,6 +72,7 @@ impl Default for StandardMaterial {
normal_map_texture: None,
double_sided: false,
unlit: false,
alpha_mode: AlphaMode::Opaque,
}
}
}
@ -95,7 +95,7 @@ impl From<Handle<Image>> for StandardMaterial {
}
}
#[derive(Clone, AsStd140)]
#[derive(Clone, Default, AsStd140)]
pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
@ -112,6 +112,9 @@ pub struct StandardMaterialUniformData {
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
pub reflectance: f32,
pub flags: u32,
/// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque,
/// and any below means fully transparent.
pub alpha_cutoff: f32,
}
pub struct StandardMaterialPlugin;
@ -127,7 +130,10 @@ impl Plugin for StandardMaterialPlugin {
pub struct GpuStandardMaterial {
pub buffer: Buffer,
pub bind_group: BindGroup,
pub has_normal_map: bool,
pub flags: StandardMaterialFlags,
pub base_color_texture: Option<Handle<Image>>,
pub alpha_mode: AlphaMode,
}
impl RenderAsset for StandardMaterial {
@ -148,7 +154,7 @@ impl RenderAsset for StandardMaterial {
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let (base_color_texture_view, base_color_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.base_color_texture)
pbr_pipeline.image_handle_to_texture(gpu_images, &material.base_color_texture)
{
result
} else {
@ -156,7 +162,7 @@ impl RenderAsset for StandardMaterial {
};
let (emissive_texture_view, emissive_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.emissive_texture)
pbr_pipeline.image_handle_to_texture(gpu_images, &material.emissive_texture)
{
result
} else {
@ -164,24 +170,21 @@ impl RenderAsset for StandardMaterial {
};
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) =
image_handle_to_view_sampler(
pbr_pipeline,
gpu_images,
&material.metallic_roughness_texture,
) {
pbr_pipeline.image_handle_to_texture(gpu_images, &material.metallic_roughness_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.normal_map_texture)
pbr_pipeline.image_handle_to_texture(gpu_images, &material.normal_map_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_pipeline, gpu_images, &material.occlusion_texture)
pbr_pipeline.image_handle_to_texture(gpu_images, &material.occlusion_texture)
{
result
} else {
@ -206,9 +209,18 @@ impl RenderAsset for StandardMaterial {
if material.unlit {
flags |= StandardMaterialFlags::UNLIT;
}
if material.normal_map_texture.is_some() {
flags |= StandardMaterialFlags::NORMAL_MAP_TEXTURE;
}
let has_normal_map = material.normal_map_texture.is_some();
// NOTE: 0.5 is from the glTF default - do we want this?
let mut alpha_cutoff = 0.5;
match material.alpha_mode {
AlphaMode::Opaque => flags |= StandardMaterialFlags::ALPHA_MODE_OPAQUE,
AlphaMode::Mask(c) => {
alpha_cutoff = c;
flags |= StandardMaterialFlags::ALPHA_MODE_MASK
}
AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND,
};
let value = StandardMaterialUniformData {
base_color: material.base_color.as_rgba_linear().into(),
emissive: material.emissive.into(),
@ -216,6 +228,7 @@ impl RenderAsset for StandardMaterial {
metallic: material.metallic,
reflectance: material.reflectance,
flags: flags.bits(),
alpha_cutoff,
};
let value_std140 = value.as_std140();
@ -279,22 +292,9 @@ impl RenderAsset for StandardMaterial {
buffer,
bind_group,
flags,
has_normal_map,
base_color_texture: material.base_color_texture,
alpha_mode: material.alpha_mode,
})
}
}
fn image_handle_to_view_sampler<'a>(
pbr_pipeline: &'a PbrPipeline,
gpu_images: &'a RenderAssets<Image>,
handle_option: &Option<Handle<Image>>,
) -> Option<(&'a TextureView, &'a Sampler)> {
if let Some(handle) = handle_option {
let gpu_image = gpu_images.get(handle)?;
Some((&gpu_image.texture_view, &gpu_image.sampler))
} else {
Some((
&pbr_pipeline.dummy_white_gpu_image.texture_view,
&pbr_pipeline.dummy_white_gpu_image.sampler,
))
}
}

View file

@ -3,11 +3,11 @@ mod light;
pub use light::*;
use crate::{
NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData,
AlphaMode, NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData,
PBR_SHADER_HANDLE,
};
use bevy_asset::Handle;
use bevy_core_pipeline::Transparent3d;
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
@ -61,7 +61,9 @@ bitflags::bitflags! {
const OCCLUSION_TEXTURE = (1 << 3);
const DOUBLE_SIDED = (1 << 4);
const UNLIT = (1 << 5);
const NORMAL_MAP_TEXTURE = (1 << 6);
const ALPHA_MODE_OPAQUE = (1 << 6);
const ALPHA_MODE_MASK = (1 << 7);
const ALPHA_MODE_BLEND = (1 << 8);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
@ -153,6 +155,24 @@ pub struct PbrPipeline {
pub dummy_white_gpu_image: GpuImage,
}
impl PbrPipeline {
pub fn image_handle_to_texture<'a>(
&'a self,
gpu_images: &'a RenderAssets<Image>,
handle_option: &Option<Handle<Image>>,
) -> Option<(&'a TextureView, &'a Sampler)> {
if let Some(handle) = handle_option {
let gpu_image = gpu_images.get(handle)?;
Some((&gpu_image.texture_view, &gpu_image.sampler))
} else {
Some((
&self.dummy_white_gpu_image.texture_view,
&self.dummy_white_gpu_image.sampler,
))
}
}
}
impl FromWorld for PbrPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.get_resource::<RenderDevice>().unwrap();
@ -426,6 +446,9 @@ bitflags::bitflags! {
const NONE = 0;
const VERTEX_TANGENTS = (1 << 0);
const STANDARDMATERIAL_NORMAL_MAP = (1 << 1);
const OPAQUE_MAIN_PASS = (1 << 2);
const ALPHA_MASK_MAIN_PASS = (1 << 3);
const TRANSPARENT_MAIN_PASS = (1 << 4);
const MSAA_RESERVED_BITS = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS;
}
}
@ -511,6 +534,21 @@ impl SpecializedPipeline for PbrPipeline {
if key.contains(PbrPipelineKey::STANDARDMATERIAL_NORMAL_MAP) {
shader_defs.push(String::from("STANDARDMATERIAL_NORMAL_MAP"));
}
let (label, blend, depth_write_enabled);
if key.contains(PbrPipelineKey::TRANSPARENT_MAIN_PASS) {
label = Some("transparent_pbr_pipeline".into());
blend = Some(BlendState::ALPHA_BLENDING);
// For the transparent pass, fragments that are closer will be alpha blended
// but their depth is not written to the depth buffer
depth_write_enabled = false;
} else {
label = Some("opaque_pbr_pipeline".into());
blend = Some(BlendState::REPLACE);
// 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
depth_write_enabled = true;
}
RenderPipelineDescriptor {
vertex: VertexState {
shader: PBR_SHADER_HANDLE.typed::<Shader>(),
@ -528,18 +566,7 @@ impl SpecializedPipeline for PbrPipeline {
entry_point: "fragment".into(),
targets: vec![ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
blend,
write_mask: ColorWrites::ALL,
}],
}),
@ -559,7 +586,7 @@ impl SpecializedPipeline for PbrPipeline {
},
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_write_enabled,
depth_compare: CompareFunction::Greater,
stencil: StencilState {
front: StencilFaceState::IGNORE,
@ -578,7 +605,7 @@ impl SpecializedPipeline for PbrPipeline {
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("pbr_pipeline".into()),
label,
}
}
}
@ -614,7 +641,9 @@ pub struct PbrViewBindGroup {
#[allow(clippy::too_many_arguments)]
pub fn queue_meshes(
mut commands: Commands,
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
render_device: Res<RenderDevice>,
pbr_pipeline: Res<PbrPipeline>,
shadow_pipeline: Res<ShadowPipeline>,
@ -631,6 +660,8 @@ pub fn queue_meshes(
&ExtractedView,
&ViewLights,
&VisibleEntities,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transparent3d>,
)>,
) {
@ -638,7 +669,15 @@ pub fn queue_meshes(
view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(),
) {
for (entity, view, view_lights, visible_entities, mut transparent_phase) in views.iter_mut()
for (
entity,
view,
view_lights,
visible_entities,
mut opaque_phase,
mut alpha_mask_phase,
mut transparent_phase,
) in views.iter_mut()
{
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
@ -681,46 +720,85 @@ pub fn queue_meshes(
value: view_bind_group,
});
let draw_pbr = transparent_3d_draw_functions
let draw_opaque_pbr = opaque_draw_functions.read().get_id::<DrawPbr>().unwrap();
let draw_alpha_mask_pbr = alpha_mask_draw_functions
.read()
.get_id::<DrawPbr>()
.unwrap();
let draw_transparent_pbr = transparent_draw_functions
.read()
.get_id::<DrawPbr>()
.unwrap();
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
let inverse_view_matrix = view.transform.compute_matrix().inverse();
let inverse_view_row_2 = inverse_view_matrix.row(2);
for visible_entity in &visible_entities.entities {
if let Ok((material_handle, mesh_handle, mesh_uniform)) =
standard_material_meshes.get(visible_entity.entity)
{
let mut key = PbrPipelineKey::from_msaa_samples(msaa.samples);
if let Some(material) = render_materials.get(material_handle) {
if material
.flags
.contains(StandardMaterialFlags::NORMAL_MAP_TEXTURE)
{
let alpha_mode = if let Some(material) = render_materials.get(material_handle) {
if material.has_normal_map {
key |= PbrPipelineKey::STANDARDMATERIAL_NORMAL_MAP;
}
material.alpha_mode.clone()
} else {
continue;
}
};
if let Some(mesh) = render_meshes.get(mesh_handle) {
if mesh.has_tangents {
key |= PbrPipelineKey::VERTEX_TANGENTS;
}
}
key |= match alpha_mode {
AlphaMode::Opaque => PbrPipelineKey::OPAQUE_MAIN_PASS,
AlphaMode::Mask(_) => PbrPipelineKey::ALPHA_MASK_MAIN_PASS,
AlphaMode::Blend => PbrPipelineKey::TRANSPARENT_MAIN_PASS,
};
let pipeline_id = pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, key);
// NOTE: row 2 of the view matrix dotted with column 3 of the model matrix
// NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3));
// TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material
transparent_phase.add(Transparent3d {
entity: visible_entity.entity,
draw_function: draw_pbr,
pipeline: pipeline_id,
distance: mesh_z,
});
let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3));
match alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: visible_entity.entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
// NOTE: Front-to-back ordering for opaque with ascending sort means near should have the
// lowest sort key and getting further away should increase. As we have
// -z in front of the camera, values in view space decrease away from the
// camera. Flipping the sign of mesh_z results in the correct front-to-back ordering
distance: -mesh_z,
});
}
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
entity: visible_entity.entity,
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
// NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the
// lowest sort key and getting further away should increase. As we have
// -z in front of the camera, values in view space decrease away from the
// camera. Flipping the sign of mesh_z results in the correct front-to-back ordering
distance: -mesh_z,
});
}
AlphaMode::Blend => {
transparent_phase.add(Transparent3d {
entity: visible_entity.entity,
draw_function: draw_transparent_pbr,
pipeline: pipeline_id,
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
// lowest sort key and getting closer should increase. As we have
// -z in front of the camera, the largest distance is -far with values increasing toward the
// camera. As such we can just use mesh_z as the distance
distance: mesh_z,
});
}
}
}
}
}

View file

@ -110,6 +110,7 @@ struct StandardMaterial {
reflectance: f32;
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
flags: u32;
alpha_cutoff: f32;
};
let STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u;
@ -118,6 +119,9 @@ let STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u;
let STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u;
let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u;
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u;
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u;
struct PointLight {
projection: mat4x4<f32>;
@ -559,6 +563,20 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
#endif
#endif
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) {
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
output_color.a = 1.0;
} elseif ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) {
if (output_color.a >= material.alpha_cutoff) {
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
output_color.a = 1.0;
} else {
// NOTE: output_color.a < material.alpha_cutoff should not is not rendered
// NOTE: This and any other discards mean that early-z testing cannot be done!
discard;
}
}
var V: vec3<f32>;
if (view.projection[3].w != 1.0) { // If the projection is not orthographic
// Only valid for a perpective projection

View file

@ -2,7 +2,10 @@ pub mod visibility;
pub mod window;
pub use visibility::*;
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
use wgpu::{
Color, Extent3d, Operations, RenderPassColorAttachment, TextureDescriptor, TextureDimension,
TextureFormat, TextureUsages,
};
pub use window::*;
use crate::{
@ -83,6 +86,24 @@ pub struct ViewTarget {
pub sampled_target: Option<TextureView>,
}
impl ViewTarget {
pub fn get_color_attachment(&self, ops: Operations<Color>) -> RenderPassColorAttachment {
RenderPassColorAttachment {
view: if let Some(sampled_target) = &self.sampled_target {
sampled_target
} else {
&self.view
},
resolve_target: if self.sampled_target.is_some() {
Some(&self.view)
} else {
None
},
ops,
}
}
}
pub struct ViewDepthTexture {
pub texture: Texture,
pub view: TextureView,

View file

@ -133,18 +133,7 @@ impl SpecializedPipeline for SpritePipeline {
entry_point: "fragment".into(),
targets: vec![ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
}],
}),