mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
029a7c03d8
commit
213839f503
10 changed files with 420 additions and 130 deletions
|
@ -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(
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<'_>,
|
||||
|
|
22
pipelined/bevy_pbr2/src/alpha.rs
Normal file
22
pipelined/bevy_pbr2/src/alpha.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}],
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue