diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 94d8d8950c..eeb780a6cb 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -29,4 +29,3 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" } bitflags = "1.2" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } -wgpu = { version = "0.12.0", features = ["spirv"] } diff --git a/crates/bevy_pbr/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs index 3a8cd075ab..32446cfa87 100644 --- a/crates/bevy_pbr/src/bundle.rs +++ b/crates/bevy_pbr/src/bundle.rs @@ -1,4 +1,4 @@ -use crate::{DirectionalLight, PointLight, StandardMaterial, DEFAULT_STANDARD_MATERIAL_HANDLE}; +use crate::{DirectionalLight, PointLight, SpecializedMaterial, StandardMaterial}; use bevy_asset::Handle; use bevy_ecs::{bundle::Bundle, component::Component}; use bevy_render::{ @@ -9,10 +9,13 @@ use bevy_render::{ use bevy_transform::components::{GlobalTransform, Transform}; /// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`]. +pub type PbrBundle = MaterialMeshBundle; + +/// A component bundle for entities with a [`Mesh`] and a [`SpecializedMaterial`]. #[derive(Bundle, Clone)] -pub struct PbrBundle { +pub struct MaterialMeshBundle { pub mesh: Handle, - pub material: Handle, + pub material: Handle, pub transform: Transform, pub global_transform: GlobalTransform, /// User indication of whether an entity is visible @@ -21,11 +24,11 @@ pub struct PbrBundle { pub computed_visibility: ComputedVisibility, } -impl Default for PbrBundle { +impl Default for MaterialMeshBundle { fn default() -> Self { Self { mesh: Default::default(), - material: DEFAULT_STANDARD_MATERIAL_HANDLE.typed(), + material: Default::default(), transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f2dc9e1ef8..62129a9009 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -4,21 +4,24 @@ mod alpha; mod bundle; mod light; mod material; +mod pbr_material; mod render; pub use alpha::*; pub use bundle::*; pub use light::*; pub use material::*; +pub use pbr_material::*; pub use render::*; pub mod prelude { #[doc(hidden)] pub use crate::{ alpha::AlphaMode, - bundle::{DirectionalLightBundle, PbrBundle, PointLightBundle}, + bundle::{DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle}, light::{AmbientLight, DirectionalLight, PointLight}, - material::StandardMaterial, + material::{Material, MaterialPlugin}, + pbr_material::StandardMaterial, }; } @@ -31,7 +34,6 @@ pub mod draw_3d_graph { use bevy_app::prelude::*; use bevy_asset::{Assets, Handle, HandleUntyped}; -use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::{ @@ -66,8 +68,8 @@ impl Plugin for PbrPlugin { Shader::from_wgsl(include_str!("render/depth.wgsl")), ); - app.add_plugin(StandardMaterialPlugin) - .add_plugin(MeshRenderPlugin) + app.add_plugin(MeshRenderPlugin) + .add_plugin(MaterialPlugin::::default()) .add_plugin(ExtractComponentPlugin::>::default()) .init_resource::() .init_resource::() @@ -127,7 +129,7 @@ impl Plugin for PbrPlugin { .get_resource_mut::>() .unwrap() .set_untracked( - DEFAULT_STANDARD_MATERIAL_HANDLE, + Handle::::default(), StandardMaterial { base_color: Color::rgb(1.0, 0.0, 0.5), unlit: true, @@ -166,21 +168,15 @@ impl Plugin for PbrPlugin { RenderStage::Queue, render::queue_shadows.label(RenderLightSystems::QueueShadows), ) - .add_system_to_stage(RenderStage::Queue, queue_meshes) .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .init_resource::() .init_resource::() .init_resource::>() .init_resource::() .init_resource::() - .init_resource::>() .init_resource::>(); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); - render_app.add_render_command::(); - render_app.add_render_command::(); - render_app.add_render_command::(); render_app.add_render_command::(); let mut graph = render_app.world.get_resource_mut::().unwrap(); let draw_3d_graph = graph diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 25d07a4be9..d1c5213975 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,321 +1,382 @@ -use crate::{AlphaMode, PbrPipeline, StandardMaterialFlags}; +use crate::{ + AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + SetMeshViewBindGroup, +}; use bevy_app::{App, Plugin}; -use bevy_asset::{AddAsset, Handle, HandleUntyped}; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; -use bevy_math::Vec4; -use bevy_reflect::TypeUuid; +use bevy_asset::{AddAsset, Asset, AssetServer, Handle}; +use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; +use bevy_ecs::{ + entity::Entity, + prelude::World, + system::{ + lifetimeless::{Read, SQuery, SRes}, + Query, Res, ResMut, SystemParamItem, + }, + world::FromWorld, +}; use bevy_render::{ - color::Color, - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + mesh::Mesh, + render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, + render_component::ExtractComponentPlugin, + render_phase::{ + AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, + SetItemPipeline, TrackedRenderPass, + }, render_resource::{ - std140::{AsStd140, Std140}, - BindGroup, Buffer, BufferInitDescriptor, BufferUsages, + BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, + SpecializedPipeline, SpecializedPipelines, }, renderer::RenderDevice, - texture::Image, + view::{ExtractedView, Msaa, VisibleEntities}, + RenderApp, RenderStage, }; -use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; +use std::hash::Hash; +use std::marker::PhantomData; -pub const DEFAULT_STANDARD_MATERIAL_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(StandardMaterial::TYPE_UUID, 13142262394054731189); +/// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle) +/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level +/// way to render [`Mesh`] entities with custom shader logic. For materials that can specialize their [`RenderPipelineDescriptor`] +/// based on specific material values, see [`SpecializedMaterial`]. [`Material`] automatically implements [`SpecializedMaterial`] +/// and can be used anywhere that type is used (such as [`MaterialPlugin`]). +pub trait Material: Asset + RenderAsset { + /// Returns this material's [`BindGroup`]. This should match the layout returned by [`Material::bind_group_layout`]. + fn bind_group(material: &::PreparedAsset) -> &BindGroup; -/// A material with "standard" properties used in PBR lighting -/// Standard property values with pictures here -/// . -/// -/// May be created directly from a [`Color`] or an [`Image`]. -#[derive(Debug, Clone, TypeUuid)] -#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] -pub struct StandardMaterial { - /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything - /// in between. If used together with a base_color_texture, this is factored into the final - /// base color as `base_color * base_color_texture_value` - pub base_color: Color, - pub base_color_texture: Option>, - // Use a color for user friendliness even though we technically don't use the alpha channel - // Might be used in the future for exposure correction in HDR - pub emissive: Color, - pub emissive_texture: Option>, - /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader - /// Defaults to minimum of 0.089 - /// If used together with a roughness/metallic texture, this is factored into the final base - /// color as `roughness * roughness_texture_value` - pub perceptual_roughness: f32, - /// From [0.0, 1.0], dielectric to pure metallic - /// If used together with a roughness/metallic texture, this is factored into the final base - /// color as `metallic * metallic_texture_value` - pub metallic: f32, - pub metallic_roughness_texture: Option>, - /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] - /// defaults to 0.5 which is mapped to 4% reflectance in the shader - pub reflectance: f32, - pub normal_map_texture: Option>, - pub occlusion_texture: Option>, - pub double_sided: bool, - pub unlit: bool, - pub alpha_mode: AlphaMode, + /// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`Material::bind_group`]. + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout; + + /// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used. + /// Defaults to [`None`]. + #[allow(unused_variables)] + fn vertex_shader(asset_server: &AssetServer) -> Option> { + None + } + + /// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used. + /// Defaults to [`None`]. + #[allow(unused_variables)] + fn fragment_shader(asset_server: &AssetServer) -> Option> { + None + } + + /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. + #[allow(unused_variables)] + fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { + AlphaMode::Opaque + } + + /// The dynamic uniform indices to set for the given `material`'s [`BindGroup`]. + /// Defaults to an empty array / no dynamic uniform indices. + #[allow(unused_variables)] + #[inline] + fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { + &[] + } } -impl Default for StandardMaterial { +impl SpecializedMaterial for M { + type Key = (); + + #[inline] + fn key(_material: &::PreparedAsset) -> Self::Key {} + + #[inline] + fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {} + + #[inline] + fn bind_group(material: &::PreparedAsset) -> &BindGroup { + ::bind_group(material) + } + + #[inline] + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { + ::bind_group_layout(render_device) + } + + #[inline] + fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { + ::alpha_mode(material) + } + + #[inline] + fn vertex_shader(asset_server: &AssetServer) -> Option> { + ::vertex_shader(asset_server) + } + + #[inline] + fn fragment_shader(asset_server: &AssetServer) -> Option> { + ::fragment_shader(asset_server) + } + + #[allow(unused_variables)] + #[inline] + fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { + ::dynamic_uniform_indices(material) + } +} + +/// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle) +/// to spawn entities that are rendered with a specific [`SpecializedMaterial`] type. They serve as an easy to use high level +/// way to render [`Mesh`] entities with custom shader logic. [`SpecializedMaterials`](SpecializedMaterial) use their [`SpecializedMaterial::Key`] +/// to customize their [`RenderPipelineDescriptor`] based on specific material values. The slightly simpler [`Material`] trait +/// should be used for materials that do not need specialization. [`Material`] types automatically implement [`SpecializedMaterial`]. +pub trait SpecializedMaterial: Asset + RenderAsset { + /// The key used to specialize this material's [`RenderPipelineDescriptor`]. + type Key: PartialEq + Eq + Hash + Clone + Send + Sync; + + /// Extract the [`SpecializedMaterial::Key`] for the "prepared" version of this material. This key will be + /// passed in to the [`SpecializedMaterial::specialize`] function when compiling the [`RenderPipeline`](bevy_render::render_resource::RenderPipeline) + /// for a given entity's material. + fn key(material: &::PreparedAsset) -> Self::Key; + + /// Specializes the given `descriptor` according to the given `key`. + fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor); + + /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. + fn bind_group(material: &::PreparedAsset) -> &BindGroup; + + /// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`SpecializedMaterial::bind_group`]. + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout; + + /// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used. + /// Defaults to [`None`]. + #[allow(unused_variables)] + fn vertex_shader(asset_server: &AssetServer) -> Option> { + None + } + + /// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used. + /// Defaults to [`None`]. + #[allow(unused_variables)] + fn fragment_shader(asset_server: &AssetServer) -> Option> { + None + } + + /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. + #[allow(unused_variables)] + fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { + AlphaMode::Opaque + } + + /// The dynamic uniform indices to set for the given `material`'s [`BindGroup`]. + /// Defaults to an empty array / no dynamic uniform indices. + #[allow(unused_variables)] + #[inline] + fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { + &[] + } +} + +/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`SpecializedMaterial`] +/// asset type (which includes [`Material`] types). +pub struct MaterialPlugin(PhantomData); + +impl Default for MaterialPlugin { fn default() -> Self { - StandardMaterial { - base_color: Color::rgb(1.0, 1.0, 1.0), - base_color_texture: None, - emissive: Color::BLACK, - emissive_texture: None, - // This is the minimum the roughness is clamped to in shader code - // See - // It's the minimum floating point value that won't be rounded down to 0 in the - // calculations used. Although technically for 32-bit floats, 0.045 could be - // used. - perceptual_roughness: 0.089, - // Few materials are purely dielectric or metallic - // This is just a default for mostly-dielectric - metallic: 0.01, - metallic_roughness_texture: None, - // Minimum real-world reflectance is 2%, most materials between 2-5% - // Expressed in a linear scale and equivalent to 4% reflectance see - // - reflectance: 0.5, - occlusion_texture: None, - normal_map_texture: None, - double_sided: false, - unlit: false, - alpha_mode: AlphaMode::Opaque, - } + Self(Default::default()) } } -impl From for StandardMaterial { - fn from(color: Color) -> Self { - StandardMaterial { - base_color: color, - ..Default::default() - } - } -} - -impl From> for StandardMaterial { - fn from(texture: Handle) -> Self { - StandardMaterial { - base_color_texture: Some(texture), - ..Default::default() - } - } -} - -/// The GPU representation of the uniform data of a [`StandardMaterial`]. -#[derive(Clone, Default, AsStd140)] -pub struct StandardMaterialUniformData { - /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything - /// in between. - pub base_color: Vec4, - // Use a color for user friendliness even though we technically don't use the alpha channel - // Might be used in the future for exposure correction in HDR - pub emissive: Vec4, - /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader - /// Defaults to minimum of 0.089 - pub roughness: f32, - /// From [0.0, 1.0], dielectric to pure metallic - pub metallic: f32, - /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] - /// 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, -} - -/// This plugin adds the [`StandardMaterial`] asset to the app. -pub struct StandardMaterialPlugin; - -impl Plugin for StandardMaterialPlugin { +impl Plugin for MaterialPlugin { fn build(&self, app: &mut App) { - app.add_plugin(RenderAssetPlugin::::default()) - .add_asset::(); + app.add_asset::() + .add_plugin(ExtractComponentPlugin::>::default()) + .add_plugin(RenderAssetPlugin::::default()); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::>() + .add_render_command::>() + .add_render_command::>() + .init_resource::>() + .init_resource::>>() + .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); + } } } -/// The GPU representation of a [`StandardMaterial`]. -#[derive(Debug, Clone)] -pub struct GpuStandardMaterial { - /// A buffer containing the [`StandardMaterialUniformData`] of the material. - pub buffer: Buffer, - /// The bind group specifying how the [`StandardMaterialUniformData`] and - /// all the textures of the material are bound. - pub bind_group: BindGroup, - pub has_normal_map: bool, - pub flags: StandardMaterialFlags, - pub base_color_texture: Option>, - pub alpha_mode: AlphaMode, +pub struct MaterialPipeline { + pub mesh_pipeline: MeshPipeline, + pub material_layout: BindGroupLayout, + pub vertex_shader: Option>, + pub fragment_shader: Option>, + marker: PhantomData, } -impl RenderAsset for StandardMaterial { - type ExtractedAsset = StandardMaterial; - type PreparedAsset = GpuStandardMaterial; - type Param = ( - SRes, - SRes, - SRes>, - ); +impl SpecializedPipeline for MaterialPipeline { + type Key = (MeshPipelineKey, M::Key); - fn extract_asset(&self) -> Self::ExtractedAsset { - self.clone() + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(key.0); + if let Some(vertex_shader) = &self.vertex_shader { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = &self.fragment_shader { + descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); + } + descriptor.layout = Some(vec![ + self.mesh_pipeline.view_layout.clone(), + self.material_layout.clone(), + self.mesh_pipeline.mesh_layout.clone(), + ]); + + M::specialize(key.1, &mut descriptor); + descriptor } +} - fn prepare_asset( - material: Self::ExtractedAsset, - (render_device, pbr_pipeline, gpu_images): &mut SystemParamItem, - ) -> Result> { - let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline - .mesh_pipeline - .get_image_texture(gpu_images, &material.base_color_texture) - { - result - } else { - return Err(PrepareAssetError::RetryNextUpdate(material)); - }; +impl FromWorld for MaterialPipeline { + fn from_world(world: &mut World) -> Self { + let asset_server = world.get_resource::().unwrap(); + let render_device = world.get_resource::().unwrap(); + let material_layout = M::bind_group_layout(render_device); - let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline - .mesh_pipeline - .get_image_texture(gpu_images, &material.emissive_texture) - { - result - } else { - return Err(PrepareAssetError::RetryNextUpdate(material)); - }; + MaterialPipeline { + mesh_pipeline: world.get_resource::().unwrap().clone(), + material_layout, + vertex_shader: M::vertex_shader(asset_server), + fragment_shader: M::fragment_shader(asset_server), + marker: PhantomData, + } + } +} - let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) = - pbr_pipeline - .mesh_pipeline - .get_image_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) = pbr_pipeline - .mesh_pipeline - .get_image_texture(gpu_images, &material.normal_map_texture) - { - result - } else { - return Err(PrepareAssetError::RetryNextUpdate(material)); - }; - let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline - .mesh_pipeline - .get_image_texture(gpu_images, &material.occlusion_texture) - { - result - } else { - return Err(PrepareAssetError::RetryNextUpdate(material)); - }; - let mut flags = StandardMaterialFlags::NONE; - if material.base_color_texture.is_some() { - flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE; - } - if material.emissive_texture.is_some() { - flags |= StandardMaterialFlags::EMISSIVE_TEXTURE; - } - if material.metallic_roughness_texture.is_some() { - flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE; - } - if material.occlusion_texture.is_some() { - flags |= StandardMaterialFlags::OCCLUSION_TEXTURE; - } - if material.double_sided { - flags |= StandardMaterialFlags::DOUBLE_SIDED; - } - if material.unlit { - flags |= StandardMaterialFlags::UNLIT; - } - 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 +type DrawMaterial = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMaterialBindGroup, + SetMeshBindGroup<2>, + DrawMesh, +); + +pub struct SetMaterialBindGroup(PhantomData); +impl EntityRenderCommand for SetMaterialBindGroup { + type Param = (SRes>, SQuery>>); + fn render<'w>( + _view: Entity, + item: Entity, + (materials, query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let material_handle = query.get(item).unwrap(); + let material = materials.into_inner().get(material_handle).unwrap(); + pass.set_bind_group( + I, + M::bind_group(material), + M::dynamic_uniform_indices(material), + ); + RenderCommandResult::Success + } +} + +#[allow(clippy::too_many_arguments)] +pub fn queue_material_meshes( + opaque_draw_functions: Res>, + alpha_mask_draw_functions: Res>, + transparent_draw_functions: Res>, + material_pipeline: Res>, + mut pipelines: ResMut>>, + mut pipeline_cache: ResMut, + msaa: Res, + render_meshes: Res>, + render_materials: Res>, + material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, + mut views: Query<( + &ExtractedView, + &VisibleEntities, + &mut RenderPhase, + &mut RenderPhase, + &mut RenderPhase, + )>, +) { + for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in + views.iter_mut() + { + let draw_opaque_pbr = opaque_draw_functions + .read() + .get_id::>() + .unwrap(); + let draw_alpha_mask_pbr = alpha_mask_draw_functions + .read() + .get_id::>() + .unwrap(); + let draw_transparent_pbr = transparent_draw_functions + .read() + .get_id::>() + .unwrap(); + + let inverse_view_matrix = view.transform.compute_matrix().inverse(); + let inverse_view_row_2 = inverse_view_matrix.row(2); + let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples); + + for visible_entity in &visible_entities.entities { + if let Ok((material_handle, mesh_handle, mesh_uniform)) = + material_meshes.get(*visible_entity) + { + if let Some(material) = render_materials.get(material_handle) { + let mut mesh_key = mesh_key; + if let Some(mesh) = render_meshes.get(mesh_handle) { + if mesh.has_tangents { + mesh_key |= MeshPipelineKey::VERTEX_TANGENTS; + } + mesh_key |= + MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + } + let alpha_mode = M::alpha_mode(material); + if let AlphaMode::Blend = alpha_mode { + mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS + } + + let specialized_key = M::key(material); + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &material_pipeline, + (mesh_key, specialized_key), + ); + + // 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 = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); + match alpha_mode { + AlphaMode::Opaque => { + opaque_phase.add(Opaque3d { + entity: *visible_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, + 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, + 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, + }); + } + } + } } - AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND, - }; - - let value = StandardMaterialUniformData { - base_color: material.base_color.as_linear_rgba_f32().into(), - emissive: material.emissive.into(), - roughness: material.perceptual_roughness, - metallic: material.metallic, - reflectance: material.reflectance, - flags: flags.bits(), - alpha_cutoff, - }; - let value_std140 = value.as_std140(); - - let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("pbr_standard_material_uniform_buffer"), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - contents: value_std140.as_bytes(), - }); - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::TextureView(base_color_texture_view), - }, - BindGroupEntry { - binding: 2, - resource: BindingResource::Sampler(base_color_sampler), - }, - BindGroupEntry { - binding: 3, - resource: BindingResource::TextureView(emissive_texture_view), - }, - BindGroupEntry { - binding: 4, - resource: BindingResource::Sampler(emissive_sampler), - }, - BindGroupEntry { - binding: 5, - resource: BindingResource::TextureView(metallic_roughness_texture_view), - }, - BindGroupEntry { - binding: 6, - resource: BindingResource::Sampler(metallic_roughness_sampler), - }, - BindGroupEntry { - binding: 7, - resource: BindingResource::TextureView(occlusion_texture_view), - }, - BindGroupEntry { - binding: 8, - resource: BindingResource::Sampler(occlusion_sampler), - }, - BindGroupEntry { - binding: 9, - resource: BindingResource::TextureView(normal_map_texture_view), - }, - BindGroupEntry { - binding: 10, - resource: BindingResource::Sampler(normal_map_sampler), - }, - ], - label: Some("pbr_standard_material_bind_group"), - layout: &pbr_pipeline.material_layout, - }); - - Ok(GpuStandardMaterial { - buffer, - bind_group, - flags, - has_normal_map, - base_color_texture: material.base_color_texture, - alpha_mode: material.alpha_mode, - }) + } } } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs new file mode 100644 index 0000000000..26ed132faf --- /dev/null +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -0,0 +1,480 @@ +use crate::{AlphaMode, MaterialPipeline, SpecializedMaterial, PBR_SHADER_HANDLE}; +use bevy_asset::{AssetServer, Handle}; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; +use bevy_math::Vec4; +use bevy_reflect::TypeUuid; +use bevy_render::{ + color::Color, + prelude::Shader, + render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, + render_resource::{ + std140::{AsStd140, Std140}, + *, + }, + renderer::RenderDevice, + texture::Image, +}; + +/// A material with "standard" properties used in PBR lighting +/// Standard property values with pictures here +/// . +/// +/// May be created directly from a [`Color`] or an [`Image`]. +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] +pub struct StandardMaterial { + /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything + /// in between. If used together with a base_color_texture, this is factored into the final + /// base color as `base_color * base_color_texture_value` + pub base_color: Color, + pub base_color_texture: Option>, + // Use a color for user friendliness even though we technically don't use the alpha channel + // Might be used in the future for exposure correction in HDR + pub emissive: Color, + pub emissive_texture: Option>, + /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader + /// Defaults to minimum of 0.089 + /// If used together with a roughness/metallic texture, this is factored into the final base + /// color as `roughness * roughness_texture_value` + pub perceptual_roughness: f32, + /// From [0.0, 1.0], dielectric to pure metallic + /// If used together with a roughness/metallic texture, this is factored into the final base + /// color as `metallic * metallic_texture_value` + pub metallic: f32, + pub metallic_roughness_texture: Option>, + /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] + /// defaults to 0.5 which is mapped to 4% reflectance in the shader + pub reflectance: f32, + pub normal_map_texture: Option>, + pub occlusion_texture: Option>, + pub double_sided: bool, + pub unlit: bool, + pub alpha_mode: AlphaMode, +} + +impl Default for StandardMaterial { + fn default() -> Self { + StandardMaterial { + base_color: Color::rgb(1.0, 1.0, 1.0), + base_color_texture: None, + emissive: Color::BLACK, + emissive_texture: None, + // This is the minimum the roughness is clamped to in shader code + // See + // It's the minimum floating point value that won't be rounded down to 0 in the + // calculations used. Although technically for 32-bit floats, 0.045 could be + // used. + perceptual_roughness: 0.089, + // Few materials are purely dielectric or metallic + // This is just a default for mostly-dielectric + metallic: 0.01, + metallic_roughness_texture: None, + // Minimum real-world reflectance is 2%, most materials between 2-5% + // Expressed in a linear scale and equivalent to 4% reflectance see + // + reflectance: 0.5, + occlusion_texture: None, + normal_map_texture: None, + double_sided: false, + unlit: false, + alpha_mode: AlphaMode::Opaque, + } + } +} + +impl From for StandardMaterial { + fn from(color: Color) -> Self { + StandardMaterial { + base_color: color, + ..Default::default() + } + } +} + +impl From> for StandardMaterial { + fn from(texture: Handle) -> Self { + StandardMaterial { + base_color_texture: Some(texture), + ..Default::default() + } + } +} + +// NOTE: These must match the bit flags in bevy_pbr/src/render/pbr.wgsl! +bitflags::bitflags! { + #[repr(transparent)] + pub struct StandardMaterialFlags: u32 { + const BASE_COLOR_TEXTURE = (1 << 0); + const EMISSIVE_TEXTURE = (1 << 1); + const METALLIC_ROUGHNESS_TEXTURE = (1 << 2); + const OCCLUSION_TEXTURE = (1 << 3); + const DOUBLE_SIDED = (1 << 4); + const UNLIT = (1 << 5); + const ALPHA_MODE_OPAQUE = (1 << 6); + const ALPHA_MODE_MASK = (1 << 7); + const ALPHA_MODE_BLEND = (1 << 8); + const NONE = 0; + const UNINITIALIZED = 0xFFFF; + } +} + +/// The GPU representation of the uniform data of a [`StandardMaterial`]. +#[derive(Clone, Default, AsStd140)] +pub struct StandardMaterialUniformData { + /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything + /// in between. + pub base_color: Vec4, + // Use a color for user friendliness even though we technically don't use the alpha channel + // Might be used in the future for exposure correction in HDR + pub emissive: Vec4, + /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader + /// Defaults to minimum of 0.089 + pub roughness: f32, + /// From [0.0, 1.0], dielectric to pure metallic + pub metallic: f32, + /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] + /// 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, +} + +/// The GPU representation of a [`StandardMaterial`]. +#[derive(Debug, Clone)] +pub struct GpuStandardMaterial { + /// A buffer containing the [`StandardMaterialUniformData`] of the material. + pub buffer: Buffer, + /// The bind group specifying how the [`StandardMaterialUniformData`] and + /// all the textures of the material are bound. + pub bind_group: BindGroup, + pub has_normal_map: bool, + pub flags: StandardMaterialFlags, + pub base_color_texture: Option>, + pub alpha_mode: AlphaMode, +} + +impl RenderAsset for StandardMaterial { + type ExtractedAsset = StandardMaterial; + type PreparedAsset = GpuStandardMaterial; + type Param = ( + SRes, + SRes>, + SRes>, + ); + + fn extract_asset(&self) -> Self::ExtractedAsset { + self.clone() + } + + fn prepare_asset( + material: Self::ExtractedAsset, + (render_device, pbr_pipeline, gpu_images): &mut SystemParamItem, + ) -> Result> { + let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.base_color_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.emissive_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) = + pbr_pipeline + .mesh_pipeline + .get_image_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) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.normal_map_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.occlusion_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let mut flags = StandardMaterialFlags::NONE; + if material.base_color_texture.is_some() { + flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE; + } + if material.emissive_texture.is_some() { + flags |= StandardMaterialFlags::EMISSIVE_TEXTURE; + } + if material.metallic_roughness_texture.is_some() { + flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE; + } + if material.occlusion_texture.is_some() { + flags |= StandardMaterialFlags::OCCLUSION_TEXTURE; + } + if material.double_sided { + flags |= StandardMaterialFlags::DOUBLE_SIDED; + } + if material.unlit { + flags |= StandardMaterialFlags::UNLIT; + } + 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_linear_rgba_f32().into(), + emissive: material.emissive.into(), + roughness: material.perceptual_roughness, + metallic: material.metallic, + reflectance: material.reflectance, + flags: flags.bits(), + alpha_cutoff, + }; + let value_std140 = value.as_std140(); + + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + label: Some("pbr_standard_material_uniform_buffer"), + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + contents: value_std140.as_bytes(), + }); + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(base_color_texture_view), + }, + BindGroupEntry { + binding: 2, + resource: BindingResource::Sampler(base_color_sampler), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(emissive_texture_view), + }, + BindGroupEntry { + binding: 4, + resource: BindingResource::Sampler(emissive_sampler), + }, + BindGroupEntry { + binding: 5, + resource: BindingResource::TextureView(metallic_roughness_texture_view), + }, + BindGroupEntry { + binding: 6, + resource: BindingResource::Sampler(metallic_roughness_sampler), + }, + BindGroupEntry { + binding: 7, + resource: BindingResource::TextureView(occlusion_texture_view), + }, + BindGroupEntry { + binding: 8, + resource: BindingResource::Sampler(occlusion_sampler), + }, + BindGroupEntry { + binding: 9, + resource: BindingResource::TextureView(normal_map_texture_view), + }, + BindGroupEntry { + binding: 10, + resource: BindingResource::Sampler(normal_map_sampler), + }, + ], + label: Some("pbr_standard_material_bind_group"), + layout: &pbr_pipeline.material_layout, + }); + + Ok(GpuStandardMaterial { + buffer, + bind_group, + flags, + has_normal_map, + base_color_texture: material.base_color_texture, + alpha_mode: material.alpha_mode, + }) + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct StandardMaterialKey { + normal_map: bool, +} + +impl SpecializedMaterial for StandardMaterial { + type Key = StandardMaterialKey; + + fn key(render_asset: &::PreparedAsset) -> Self::Key { + StandardMaterialKey { + normal_map: render_asset.has_normal_map, + } + } + + fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor) { + if key.normal_map { + descriptor + .fragment + .as_mut() + .unwrap() + .shader_defs + .push(String::from("STANDARDMATERIAL_NORMAL_MAP")); + } + if let Some(label) = &mut descriptor.label { + *label = format!("pbr_{}", *label).into(); + } + } + + fn fragment_shader(_asset_server: &AssetServer) -> Option> { + Some(PBR_SHADER_HANDLE.typed()) + } + + #[inline] + fn bind_group(render_asset: &::PreparedAsset) -> &BindGroup { + &render_asset.bind_group + } + + fn bind_group_layout( + render_device: &RenderDevice, + ) -> bevy_render::render_resource::BindGroupLayout { + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: BufferSize::new( + StandardMaterialUniformData::std140_size_static() as u64, + ), + }, + count: None, + }, + // Base Color Texture + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Base Color Texture Sampler + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + // Emissive Texture + BindGroupLayoutEntry { + binding: 3, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Emissive Texture Sampler + BindGroupLayoutEntry { + binding: 4, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + // Metallic Roughness Texture + BindGroupLayoutEntry { + binding: 5, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Metallic Roughness Texture Sampler + BindGroupLayoutEntry { + binding: 6, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + // Occlusion Texture + BindGroupLayoutEntry { + binding: 7, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Occlusion Texture Sampler + BindGroupLayoutEntry { + binding: 8, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + // Normal Map Texture + BindGroupLayoutEntry { + binding: 9, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // Normal Map Texture Sampler + BindGroupLayoutEntry { + binding: 10, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("pbr_material_layout"), + }) + } + + #[inline] + fn alpha_mode(render_asset: &::PreparedAsset) -> AlphaMode { + render_asset.alpha_mode + } +} diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index eae586c32e..92fd42b190 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -22,10 +22,6 @@ use bevy_render::{ RenderApp, RenderStage, }; use bevy_transform::components::GlobalTransform; -use wgpu::{ - Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, SamplerBindingType, TextureDimension, - TextureFormat, TextureViewDescriptor, -}; #[derive(Default)] pub struct MeshRenderPlugin; @@ -311,7 +307,7 @@ impl FromWorld for MeshPipeline { texture: &texture, mip_level: 0, origin: Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, + aspect: TextureAspect::All, }, &image.data, ImageDataLayout { diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 5b8b379183..353f05be1d 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -3,341 +3,3 @@ mod mesh; pub use light::*; pub use mesh::*; -use wgpu::SamplerBindingType; - -use crate::{AlphaMode, StandardMaterial, StandardMaterialUniformData, PBR_SHADER_HANDLE}; -use bevy_asset::Handle; -use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; -use bevy_ecs::{ - prelude::*, - system::{lifetimeless::*, SystemParamItem}, -}; -use bevy_render::{ - mesh::Mesh, - render_asset::RenderAssets, - render_phase::{ - DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, - TrackedRenderPass, - }, - render_resource::{std140::AsStd140, *}, - renderer::RenderDevice, - view::{ExtractedView, Msaa, VisibleEntities}, -}; - -// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl! -bitflags::bitflags! { - #[repr(transparent)] - pub struct StandardMaterialFlags: u32 { - const BASE_COLOR_TEXTURE = (1 << 0); - const EMISSIVE_TEXTURE = (1 << 1); - const METALLIC_ROUGHNESS_TEXTURE = (1 << 2); - const OCCLUSION_TEXTURE = (1 << 3); - const DOUBLE_SIDED = (1 << 4); - const UNLIT = (1 << 5); - const ALPHA_MODE_OPAQUE = (1 << 6); - const ALPHA_MODE_MASK = (1 << 7); - const ALPHA_MODE_BLEND = (1 << 8); - const NONE = 0; - const UNINITIALIZED = 0xFFFF; - } -} - -#[derive(Clone)] -pub struct PbrPipeline { - pub mesh_pipeline: MeshPipeline, - pub material_layout: BindGroupLayout, -} - -impl FromWorld for PbrPipeline { - fn from_world(world: &mut World) -> Self { - let render_device = world.get_resource::().unwrap(); - - let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: BufferSize::new( - StandardMaterialUniformData::std140_size_static() as u64, - ), - }, - count: None, - }, - // Base Color Texture - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - // Base Color Texture Sampler - BindGroupLayoutEntry { - binding: 2, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - // Emissive Texture - BindGroupLayoutEntry { - binding: 3, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - // Emissive Texture Sampler - BindGroupLayoutEntry { - binding: 4, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - // Metallic Roughness Texture - BindGroupLayoutEntry { - binding: 5, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - // Metallic Roughness Texture Sampler - BindGroupLayoutEntry { - binding: 6, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - // Occlusion Texture - BindGroupLayoutEntry { - binding: 7, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - // Occlusion Texture Sampler - BindGroupLayoutEntry { - binding: 8, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - // Normal Map Texture - BindGroupLayoutEntry { - binding: 9, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - // Normal Map Texture Sampler - BindGroupLayoutEntry { - binding: 10, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("pbr_material_layout"), - }); - - PbrPipeline { - material_layout, - mesh_pipeline: world.get_resource::().unwrap().clone(), - } - } -} - -#[derive(Clone, Copy, Hash, PartialEq, Eq)] -pub struct PbrPipelineKey { - pub mesh_key: MeshPipelineKey, - pub normal_map: bool, -} - -impl SpecializedPipeline for PbrPipeline { - type Key = PbrPipelineKey; - - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key); - descriptor.fragment.as_mut().unwrap().shader = PBR_SHADER_HANDLE.typed::(); - descriptor.layout = Some(vec![ - self.mesh_pipeline.view_layout.clone(), - self.material_layout.clone(), - self.mesh_pipeline.mesh_layout.clone(), - ]); - - if key.normal_map { - descriptor - .fragment - .as_mut() - .unwrap() - .shader_defs - .push(String::from("STANDARDMATERIAL_NORMAL_MAP")); - } - if let Some(label) = &mut descriptor.label { - *label = format!("pbr_{}", *label).into(); - } - descriptor - } -} - -pub struct PbrViewBindGroup { - pub value: BindGroup, -} - -#[allow(clippy::too_many_arguments)] -pub fn queue_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - transparent_draw_functions: Res>, - pbr_pipeline: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, - msaa: Res, - render_meshes: Res>, - render_materials: Res>, - standard_material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, - mut views: Query<( - &ExtractedView, - &VisibleEntities, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, - )>, -) { - for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in - views.iter_mut() - { - let draw_opaque_pbr = opaque_draw_functions.read().get_id::().unwrap(); - let draw_alpha_mask_pbr = alpha_mask_draw_functions - .read() - .get_id::() - .unwrap(); - let draw_transparent_pbr = transparent_draw_functions - .read() - .get_id::() - .unwrap(); - - let inverse_view_matrix = view.transform.compute_matrix().inverse(); - let inverse_view_row_2 = inverse_view_matrix.row(2); - let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples); - - for visible_entity in &visible_entities.entities { - if let Ok((material_handle, mesh_handle, mesh_uniform)) = - standard_material_meshes.get(*visible_entity) - { - if let Some(material) = render_materials.get(material_handle) { - let mut pbr_key = PbrPipelineKey { - mesh_key, - normal_map: material.has_normal_map, - }; - if let Some(mesh) = render_meshes.get(mesh_handle) { - if mesh.has_tangents { - pbr_key.mesh_key |= MeshPipelineKey::VERTEX_TANGENTS; - } - pbr_key.mesh_key |= - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - } - - if let AlphaMode::Blend = material.alpha_mode { - pbr_key.mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS - } - - let pipeline_id = - pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, pbr_key); - - // 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 = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); - match material.alpha_mode { - AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { - entity: *visible_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, - 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, - 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, - }); - } - } - } - } - } - } -} - -pub type DrawPbr = ( - SetItemPipeline, - SetMeshViewBindGroup<0>, - SetStandardMaterialBindGroup<1>, - SetMeshBindGroup<2>, - DrawMesh, -); - -pub struct SetStandardMaterialBindGroup; -impl EntityRenderCommand for SetStandardMaterialBindGroup { - type Param = ( - SRes>, - SQuery>>, - ); - #[inline] - fn render<'w>( - _view: Entity, - item: Entity, - (materials, handle_query): SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let handle = handle_query.get(item).unwrap(); - let materials = materials.into_inner(); - let material = materials.get(handle).unwrap(); - - pass.set_bind_group(I, &material.bind_group, &[]); - RenderCommandResult::Success - } -} diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index c8d88b7d76..88775d21d2 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -5,6 +5,7 @@ use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_core_pipeline::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::{Reflect, TypeUuid}; +use bevy_render::render_resource::PolygonMode; use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, @@ -13,7 +14,6 @@ use bevy_render::{ view::{ExtractedView, Msaa}, RenderApp, RenderStage, }; -use wgpu::PolygonMode; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766); diff --git a/examples/shader/shader_material.rs b/examples/shader/shader_material.rs index 66af365ede..a54f2ac049 100644 --- a/examples/shader/shader_material.rs +++ b/examples/shader/shader_material.rs @@ -1,34 +1,22 @@ use bevy::{ - core_pipeline::Transparent3d, - ecs::system::{lifetimeless::*, SystemParamItem}, - pbr::{ - DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, - SetMeshViewBindGroup, - }, + ecs::system::{lifetimeless::SRes, SystemParamItem}, + pbr::MaterialPipeline, prelude::*, reflect::TypeUuid, render::{ - camera::PerspectiveCameraBundle, - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, - render_component::ExtractComponentPlugin, - render_phase::{ - AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, - SetItemPipeline, TrackedRenderPass, - }, + render_asset::{PrepareAssetError, RenderAsset}, render_resource::{ std140::{AsStd140, Std140}, *, }, renderer::RenderDevice, - view::{ExtractedView, Msaa}, - RenderApp, RenderStage, }, }; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(CustomMaterialPlugin) + .add_plugin(MaterialPlugin::::default()) .add_startup_system(setup) .run(); } @@ -40,16 +28,14 @@ fn setup( mut materials: ResMut>, ) { // cube - commands.spawn().insert_bundle(( - meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - Transform::from_xyz(0.0, 0.5, 0.0), - GlobalTransform::default(), - Visibility::default(), - ComputedVisibility::default(), - materials.add(CustomMaterial { + commands.spawn().insert_bundle(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + material: materials.add(CustomMaterial { color: Color::GREEN, }), - )); + ..Default::default() + }); // camera commands.spawn_bundle(PerspectiveCameraBundle { @@ -73,14 +59,14 @@ pub struct GpuCustomMaterial { impl RenderAsset for CustomMaterial { type ExtractedAsset = CustomMaterial; type PreparedAsset = GpuCustomMaterial; - type Param = (SRes, SRes); + type Param = (SRes, SRes>); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( extracted_asset: Self::ExtractedAsset, - (render_device, custom_pipeline): &mut SystemParamItem, + (render_device, material_pipeline): &mut SystemParamItem, ) -> Result> { let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { @@ -94,7 +80,7 @@ impl RenderAsset for CustomMaterial { resource: buffer.as_entire_binding(), }], label: None, - layout: &custom_pipeline.material_layout, + layout: &material_pipeline.material_layout, }); Ok(GpuCustomMaterial { @@ -103,51 +89,18 @@ impl RenderAsset for CustomMaterial { }) } } -pub struct CustomMaterialPlugin; -impl Plugin for CustomMaterialPlugin { - fn build(&self, app: &mut App) { - app.add_asset::() - .add_plugin(ExtractComponentPlugin::>::default()) - .add_plugin(RenderAssetPlugin::::default()); - app.sub_app_mut(RenderApp) - .add_render_command::() - .init_resource::() - .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_custom); +impl Material for CustomMaterial { + fn fragment_shader(asset_server: &AssetServer) -> Option> { + Some(asset_server.load("shaders/custom_material.wgsl")) } -} -pub struct CustomPipeline { - mesh_pipeline: MeshPipeline, - material_layout: BindGroupLayout, - shader: Handle, -} - -impl SpecializedPipeline for CustomPipeline { - type Key = MeshPipelineKey; - - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key); - descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); - descriptor.layout = Some(vec![ - self.mesh_pipeline.view_layout.clone(), - self.material_layout.clone(), - self.mesh_pipeline.mesh_layout.clone(), - ]); - descriptor + fn bind_group(render_asset: &::PreparedAsset) -> &BindGroup { + &render_asset.bind_group } -} -impl FromWorld for CustomPipeline { - fn from_world(world: &mut World) -> Self { - let asset_server = world.get_resource::().unwrap(); - // Watch for changes, allowing for hot shader reloading - // Try changing custom_material.wgsl while the app is running! - asset_server.watch_for_changes().unwrap(); - - let render_device = world.get_resource::().unwrap(); - let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, @@ -159,80 +112,6 @@ impl FromWorld for CustomPipeline { count: None, }], label: None, - }); - - CustomPipeline { - mesh_pipeline: world.get_resource::().unwrap().clone(), - shader: asset_server.load("shaders/custom_material.wgsl"), - material_layout, - } - } -} - -#[allow(clippy::too_many_arguments)] -pub fn queue_custom( - transparent_3d_draw_functions: Res>, - materials: Res>, - render_meshes: Res>, - custom_pipeline: Res, - mut pipeline_cache: ResMut, - mut specialized_pipelines: ResMut>, - msaa: Res, - material_meshes: Query<(Entity, &Handle, &Handle, &MeshUniform)>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, -) { - let draw_custom = transparent_3d_draw_functions - .read() - .get_id::() - .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples); - for (view, mut transparent_phase) in views.iter_mut() { - let view_matrix = view.transform.compute_matrix(); - let view_row_2 = view_matrix.row(2); - for (entity, material_handle, mesh_handle, mesh_uniform) in material_meshes.iter() { - if materials.contains_key(material_handle) { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = - key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - transparent_phase.add(Transparent3d { - entity, - pipeline: specialized_pipelines.specialize( - &mut pipeline_cache, - &custom_pipeline, - key, - ), - draw_function: draw_custom, - distance: view_row_2.dot(mesh_uniform.transform.col(3)), - }); - } - } - } - } -} - -type DrawCustom = ( - SetItemPipeline, - SetMeshViewBindGroup<0>, - SetCustomMaterialBindGroup, - SetMeshBindGroup<2>, - DrawMesh, -); - -struct SetCustomMaterialBindGroup; -impl EntityRenderCommand for SetCustomMaterialBindGroup { - type Param = ( - SRes>, - SQuery>>, - ); - fn render<'w>( - _view: Entity, - item: Entity, - (materials, query): SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let material_handle = query.get(item).unwrap(); - let material = materials.into_inner().get(material_handle).unwrap(); - pass.set_bind_group(1, &material.bind_group, &[]); - RenderCommandResult::Success + }) } }