Materials and MaterialPlugin (#3428)

This adds "high level" `Material` and `SpecializedMaterial` traits, which can be used with a `MaterialPlugin<T: SpecializedMaterial>`. `MaterialPlugin` automatically registers the appropriate resources, draw functions, and queue systems. The `Material` trait is simpler, and should cover most use cases. `SpecializedMaterial` is like `Material`, but it also requires defining a "specialization key" (see #3031). `Material` has a trivial blanket impl of `SpecializedMaterial`, which allows us to use the same types + functions for both.

This makes defining custom 3d materials much simpler (see the `shader_material` example diff) and ensures consistent behavior across all 3d materials (both built in and custom). I ported the built in `StandardMaterial` to `MaterialPlugin`. There is also a new `MaterialMeshBundle<T: SpecializedMaterial>`, which `PbrBundle` aliases to.
This commit is contained in:
Carter Anderson 2021-12-25 21:45:43 +00:00
parent 22c665fa39
commit 963e2f08a2
9 changed files with 873 additions and 797 deletions

View File

@ -29,4 +29,3 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" }
bitflags = "1.2" bitflags = "1.2"
# direct dependency required for derive macro # direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive"] } bytemuck = { version = "1", features = ["derive"] }
wgpu = { version = "0.12.0", features = ["spirv"] }

View File

@ -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_asset::Handle;
use bevy_ecs::{bundle::Bundle, component::Component}; use bevy_ecs::{bundle::Bundle, component::Component};
use bevy_render::{ use bevy_render::{
@ -9,10 +9,13 @@ use bevy_render::{
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`]. /// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
/// A component bundle for entities with a [`Mesh`] and a [`SpecializedMaterial`].
#[derive(Bundle, Clone)] #[derive(Bundle, Clone)]
pub struct PbrBundle { pub struct MaterialMeshBundle<M: SpecializedMaterial> {
pub mesh: Handle<Mesh>, pub mesh: Handle<Mesh>,
pub material: Handle<StandardMaterial>, pub material: Handle<M>,
pub transform: Transform, pub transform: Transform,
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
/// User indication of whether an entity is visible /// User indication of whether an entity is visible
@ -21,11 +24,11 @@ pub struct PbrBundle {
pub computed_visibility: ComputedVisibility, pub computed_visibility: ComputedVisibility,
} }
impl Default for PbrBundle { impl<M: SpecializedMaterial> Default for MaterialMeshBundle<M> {
fn default() -> Self { fn default() -> Self {
Self { Self {
mesh: Default::default(), mesh: Default::default(),
material: DEFAULT_STANDARD_MATERIAL_HANDLE.typed(), material: Default::default(),
transform: Default::default(), transform: Default::default(),
global_transform: Default::default(), global_transform: Default::default(),
visibility: Default::default(), visibility: Default::default(),

View File

@ -4,21 +4,24 @@ mod alpha;
mod bundle; mod bundle;
mod light; mod light;
mod material; mod material;
mod pbr_material;
mod render; mod render;
pub use alpha::*; pub use alpha::*;
pub use bundle::*; pub use bundle::*;
pub use light::*; pub use light::*;
pub use material::*; pub use material::*;
pub use pbr_material::*;
pub use render::*; pub use render::*;
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
alpha::AlphaMode, alpha::AlphaMode,
bundle::{DirectionalLightBundle, PbrBundle, PointLightBundle}, bundle::{DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle},
light::{AmbientLight, DirectionalLight, PointLight}, 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_app::prelude::*;
use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_render::{ use bevy_render::{
@ -66,8 +68,8 @@ impl Plugin for PbrPlugin {
Shader::from_wgsl(include_str!("render/depth.wgsl")), Shader::from_wgsl(include_str!("render/depth.wgsl")),
); );
app.add_plugin(StandardMaterialPlugin) app.add_plugin(MeshRenderPlugin)
.add_plugin(MeshRenderPlugin) .add_plugin(MaterialPlugin::<StandardMaterial>::default())
.add_plugin(ExtractComponentPlugin::<Handle<StandardMaterial>>::default()) .add_plugin(ExtractComponentPlugin::<Handle<StandardMaterial>>::default())
.init_resource::<AmbientLight>() .init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>() .init_resource::<DirectionalLightShadowMap>()
@ -127,7 +129,7 @@ impl Plugin for PbrPlugin {
.get_resource_mut::<Assets<StandardMaterial>>() .get_resource_mut::<Assets<StandardMaterial>>()
.unwrap() .unwrap()
.set_untracked( .set_untracked(
DEFAULT_STANDARD_MATERIAL_HANDLE, Handle::<StandardMaterial>::default(),
StandardMaterial { StandardMaterial {
base_color: Color::rgb(1.0, 0.0, 0.5), base_color: Color::rgb(1.0, 0.0, 0.5),
unlit: true, unlit: true,
@ -166,21 +168,15 @@ impl Plugin for PbrPlugin {
RenderStage::Queue, RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows), 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::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>) .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.init_resource::<PbrPipeline>()
.init_resource::<ShadowPipeline>() .init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>() .init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>() .init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>() .init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedPipelines<PbrPipeline>>()
.init_resource::<SpecializedPipelines<ShadowPipeline>>(); .init_resource::<SpecializedPipelines<ShadowPipeline>>();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); 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>(); render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap(); let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
let draw_3d_graph = graph let draw_3d_graph = graph

View File

@ -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_app::{App, Plugin};
use bevy_asset::{AddAsset, Handle, HandleUntyped}; use bevy_asset::{AddAsset, Asset, AssetServer, Handle};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_math::Vec4; use bevy_ecs::{
use bevy_reflect::TypeUuid; entity::Entity,
prelude::World,
system::{
lifetimeless::{Read, SQuery, SRes},
Query, Res, ResMut, SystemParamItem,
},
world::FromWorld,
};
use bevy_render::{ use bevy_render::{
color::Color, mesh::Mesh,
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
},
render_resource::{ render_resource::{
std140::{AsStd140, Std140}, BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader,
BindGroup, Buffer, BufferInitDescriptor, BufferUsages, SpecializedPipeline, SpecializedPipelines,
}, },
renderer::RenderDevice, 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 = /// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle)
HandleUntyped::weak_from_u64(StandardMaterial::TYPE_UUID, 13142262394054731189); /// 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: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup;
/// A material with "standard" properties used in PBR lighting /// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`Material::bind_group`].
/// Standard property values with pictures here fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
/// <https://google.github.io/filament/Material%20Properties.pdf>.
/// /// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used.
/// May be created directly from a [`Color`] or an [`Image`]. /// Defaults to [`None`].
#[derive(Debug, Clone, TypeUuid)] #[allow(unused_variables)]
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
pub struct StandardMaterial { None
/// 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` /// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used.
pub base_color: Color, /// Defaults to [`None`].
pub base_color_texture: Option<Handle<Image>>, #[allow(unused_variables)]
// Use a color for user friendliness even though we technically don't use the alpha channel fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
// Might be used in the future for exposure correction in HDR None
pub emissive: Color, }
pub emissive_texture: Option<Handle<Image>>,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
/// Defaults to minimum of 0.089 #[allow(unused_variables)]
/// If used together with a roughness/metallic texture, this is factored into the final base fn alpha_mode(material: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
/// color as `roughness * roughness_texture_value` AlphaMode::Opaque
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 /// The dynamic uniform indices to set for the given `material`'s [`BindGroup`].
/// color as `metallic * metallic_texture_value` /// Defaults to an empty array / no dynamic uniform indices.
pub metallic: f32, #[allow(unused_variables)]
pub metallic_roughness_texture: Option<Handle<Image>>, #[inline]
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0] fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
/// defaults to 0.5 which is mapped to 4% reflectance in the shader &[]
pub reflectance: f32, }
pub normal_map_texture: Option<Handle<Image>>,
pub occlusion_texture: Option<Handle<Image>>,
pub double_sided: bool,
pub unlit: bool,
pub alpha_mode: AlphaMode,
} }
impl Default for StandardMaterial { impl<M: Material> SpecializedMaterial for M {
type Key = ();
#[inline]
fn key(_material: &<Self as RenderAsset>::PreparedAsset) -> Self::Key {}
#[inline]
fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {}
#[inline]
fn bind_group(material: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
<M as Material>::bind_group(material)
}
#[inline]
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
<M as Material>::bind_group_layout(render_device)
}
#[inline]
fn alpha_mode(material: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
<M as Material>::alpha_mode(material)
}
#[inline]
fn vertex_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
<M as Material>::vertex_shader(asset_server)
}
#[inline]
fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
<M as Material>::fragment_shader(asset_server)
}
#[allow(unused_variables)]
#[inline]
fn dynamic_uniform_indices(material: &<Self as RenderAsset>::PreparedAsset) -> &[u32] {
<M as Material>::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: &<Self as RenderAsset>::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: &<Self as RenderAsset>::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<Handle<Shader>> {
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<Handle<Shader>> {
None
}
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
#[allow(unused_variables)]
fn alpha_mode(material: &<Self as RenderAsset>::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: &<Self as RenderAsset>::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<M: SpecializedMaterial>(PhantomData<M>);
impl<M: SpecializedMaterial> Default for MaterialPlugin<M> {
fn default() -> Self { fn default() -> Self {
StandardMaterial { Self(Default::default())
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 <https://google.github.io/filament/Filament.html#materialsystem/parameterization/>
// 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
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5,
occlusion_texture: None,
normal_map_texture: None,
double_sided: false,
unlit: false,
alpha_mode: AlphaMode::Opaque,
}
} }
} }
impl From<Color> for StandardMaterial { impl<M: SpecializedMaterial> Plugin for MaterialPlugin<M> {
fn from(color: Color) -> Self {
StandardMaterial {
base_color: color,
..Default::default()
}
}
}
impl From<Handle<Image>> for StandardMaterial {
fn from(texture: Handle<Image>) -> 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 {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugin(RenderAssetPlugin::<StandardMaterial>::default()) app.add_asset::<M>()
.add_asset::<StandardMaterial>(); .add_plugin(ExtractComponentPlugin::<Handle<M>>::default())
.add_plugin(RenderAssetPlugin::<M>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<Transparent3d, DrawMaterial<M>>()
.add_render_command::<Opaque3d, DrawMaterial<M>>()
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
.init_resource::<MaterialPipeline<M>>()
.init_resource::<SpecializedPipelines<MaterialPipeline<M>>>()
.add_system_to_stage(RenderStage::Queue, queue_material_meshes::<M>);
}
} }
} }
/// The GPU representation of a [`StandardMaterial`]. pub struct MaterialPipeline<M: SpecializedMaterial> {
#[derive(Debug, Clone)] pub mesh_pipeline: MeshPipeline,
pub struct GpuStandardMaterial { pub material_layout: BindGroupLayout,
/// A buffer containing the [`StandardMaterialUniformData`] of the material. pub vertex_shader: Option<Handle<Shader>>,
pub buffer: Buffer, pub fragment_shader: Option<Handle<Shader>>,
/// The bind group specifying how the [`StandardMaterialUniformData`] and marker: PhantomData<M>,
/// 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<Handle<Image>>,
pub alpha_mode: AlphaMode,
} }
impl RenderAsset for StandardMaterial { impl<M: SpecializedMaterial> SpecializedPipeline for MaterialPipeline<M> {
type ExtractedAsset = StandardMaterial; type Key = (MeshPipelineKey, M::Key);
type PreparedAsset = GpuStandardMaterial;
type Param = (
SRes<RenderDevice>,
SRes<PbrPipeline>,
SRes<RenderAssets<Image>>,
);
fn extract_asset(&self) -> Self::ExtractedAsset { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
self.clone() 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( impl<M: SpecializedMaterial> FromWorld for MaterialPipeline<M> {
material: Self::ExtractedAsset, fn from_world(world: &mut World) -> Self {
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>, let asset_server = world.get_resource::<AssetServer>().unwrap();
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> { let render_device = world.get_resource::<RenderDevice>().unwrap();
let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline let material_layout = M::bind_group_layout(render_device);
.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 MaterialPipeline {
.mesh_pipeline mesh_pipeline: world.get_resource::<MeshPipeline>().unwrap().clone(),
.get_image_texture(gpu_images, &material.emissive_texture) material_layout,
{ vertex_shader: M::vertex_shader(asset_server),
result fragment_shader: M::fragment_shader(asset_server),
} else { marker: PhantomData,
return Err(PrepareAssetError::RetryNextUpdate(material)); }
}; }
}
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) = type DrawMaterial<M> = (
pbr_pipeline SetItemPipeline,
.mesh_pipeline SetMeshViewBindGroup<0>,
.get_image_texture(gpu_images, &material.metallic_roughness_texture) SetMaterialBindGroup<M, 1>,
{ SetMeshBindGroup<2>,
result DrawMesh,
} else { );
return Err(PrepareAssetError::RetryNextUpdate(material));
}; pub struct SetMaterialBindGroup<M: SpecializedMaterial, const I: usize>(PhantomData<M>);
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = pbr_pipeline impl<M: SpecializedMaterial, const I: usize> EntityRenderCommand for SetMaterialBindGroup<M, I> {
.mesh_pipeline type Param = (SRes<RenderAssets<M>>, SQuery<Read<Handle<M>>>);
.get_image_texture(gpu_images, &material.normal_map_texture) fn render<'w>(
{ _view: Entity,
result item: Entity,
} else { (materials, query): SystemParamItem<'w, '_, Self::Param>,
return Err(PrepareAssetError::RetryNextUpdate(material)); pass: &mut TrackedRenderPass<'w>,
}; ) -> RenderCommandResult {
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline let material_handle = query.get(item).unwrap();
.mesh_pipeline let material = materials.into_inner().get(material_handle).unwrap();
.get_image_texture(gpu_images, &material.occlusion_texture) pass.set_bind_group(
{ I,
result M::bind_group(material),
} else { M::dynamic_uniform_indices(material),
return Err(PrepareAssetError::RetryNextUpdate(material)); );
}; RenderCommandResult::Success
let mut flags = StandardMaterialFlags::NONE; }
if material.base_color_texture.is_some() { }
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
} #[allow(clippy::too_many_arguments)]
if material.emissive_texture.is_some() { pub fn queue_material_meshes<M: SpecializedMaterial>(
flags |= StandardMaterialFlags::EMISSIVE_TEXTURE; opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
} alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
if material.metallic_roughness_texture.is_some() { transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE; material_pipeline: Res<MaterialPipeline<M>>,
} mut pipelines: ResMut<SpecializedPipelines<MaterialPipeline<M>>>,
if material.occlusion_texture.is_some() { mut pipeline_cache: ResMut<RenderPipelineCache>,
flags |= StandardMaterialFlags::OCCLUSION_TEXTURE; msaa: Res<Msaa>,
} render_meshes: Res<RenderAssets<Mesh>>,
if material.double_sided { render_materials: Res<RenderAssets<M>>,
flags |= StandardMaterialFlags::DOUBLE_SIDED; material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
} mut views: Query<(
if material.unlit { &ExtractedView,
flags |= StandardMaterialFlags::UNLIT; &VisibleEntities,
} &mut RenderPhase<Opaque3d>,
let has_normal_map = material.normal_map_texture.is_some(); &mut RenderPhase<AlphaMask3d>,
// NOTE: 0.5 is from the glTF default - do we want this? &mut RenderPhase<Transparent3d>,
let mut alpha_cutoff = 0.5; )>,
match material.alpha_mode { ) {
AlphaMode::Opaque => flags |= StandardMaterialFlags::ALPHA_MODE_OPAQUE, for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in
AlphaMode::Mask(c) => { views.iter_mut()
alpha_cutoff = c; {
flags |= StandardMaterialFlags::ALPHA_MODE_MASK let draw_opaque_pbr = opaque_draw_functions
.read()
.get_id::<DrawMaterial<M>>()
.unwrap();
let draw_alpha_mask_pbr = alpha_mask_draw_functions
.read()
.get_id::<DrawMaterial<M>>()
.unwrap();
let draw_transparent_pbr = transparent_draw_functions
.read()
.get_id::<DrawMaterial<M>>()
.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,
})
} }
} }

View File

@ -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
/// <https://google.github.io/filament/Material%20Properties.pdf>.
///
/// 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<Handle<Image>>,
// 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<Handle<Image>>,
/// 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<Handle<Image>>,
/// 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<Handle<Image>>,
pub occlusion_texture: Option<Handle<Image>>,
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 <https://google.github.io/filament/Filament.html#materialsystem/parameterization/>
// 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
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5,
occlusion_texture: None,
normal_map_texture: None,
double_sided: false,
unlit: false,
alpha_mode: AlphaMode::Opaque,
}
}
}
impl From<Color> for StandardMaterial {
fn from(color: Color) -> Self {
StandardMaterial {
base_color: color,
..Default::default()
}
}
}
impl From<Handle<Image>> for StandardMaterial {
fn from(texture: Handle<Image>) -> 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<Handle<Image>>,
pub alpha_mode: AlphaMode,
}
impl RenderAsset for StandardMaterial {
type ExtractedAsset = StandardMaterial;
type PreparedAsset = GpuStandardMaterial;
type Param = (
SRes<RenderDevice>,
SRes<MaterialPipeline<StandardMaterial>>,
SRes<RenderAssets<Image>>,
);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
fn prepare_asset(
material: Self::ExtractedAsset,
(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) = 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: &<Self as RenderAsset>::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<Handle<Shader>> {
Some(PBR_SHADER_HANDLE.typed())
}
#[inline]
fn bind_group(render_asset: &<Self as RenderAsset>::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: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode {
render_asset.alpha_mode
}
}

View File

@ -22,10 +22,6 @@ use bevy_render::{
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use wgpu::{
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, SamplerBindingType, TextureDimension,
TextureFormat, TextureViewDescriptor,
};
#[derive(Default)] #[derive(Default)]
pub struct MeshRenderPlugin; pub struct MeshRenderPlugin;
@ -311,7 +307,7 @@ impl FromWorld for MeshPipeline {
texture: &texture, texture: &texture,
mip_level: 0, mip_level: 0,
origin: Origin3d::ZERO, origin: Origin3d::ZERO,
aspect: wgpu::TextureAspect::All, aspect: TextureAspect::All,
}, },
&image.data, &image.data,
ImageDataLayout { ImageDataLayout {

View File

@ -3,341 +3,3 @@ mod mesh;
pub use light::*; pub use light::*;
pub use mesh::*; 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::<RenderDevice>().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::<MeshPipeline>().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::<Shader>();
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<DrawFunctions<Opaque3d>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
pbr_pipeline: Res<PbrPipeline>,
mut pipelines: ResMut<SpecializedPipelines<PbrPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderAssets<StandardMaterial>>,
standard_material_meshes: Query<(&Handle<StandardMaterial>, &Handle<Mesh>, &MeshUniform)>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transparent3d>,
)>,
) {
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::<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 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<const I: usize>;
impl<const I: usize> EntityRenderCommand for SetStandardMaterialBindGroup<I> {
type Param = (
SRes<RenderAssets<StandardMaterial>>,
SQuery<Read<Handle<StandardMaterial>>>,
);
#[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
}
}

View File

@ -5,6 +5,7 @@ use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_core_pipeline::Opaque3d; use bevy_core_pipeline::Opaque3d;
use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_reflect::{Reflect, TypeUuid}; use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::render_resource::PolygonMode;
use bevy_render::{ use bevy_render::{
mesh::Mesh, mesh::Mesh,
render_asset::RenderAssets, render_asset::RenderAssets,
@ -13,7 +14,6 @@ use bevy_render::{
view::{ExtractedView, Msaa}, view::{ExtractedView, Msaa},
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
use wgpu::PolygonMode;
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766); HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);

View File

@ -1,34 +1,22 @@
use bevy::{ use bevy::{
core_pipeline::Transparent3d, ecs::system::{lifetimeless::SRes, SystemParamItem},
ecs::system::{lifetimeless::*, SystemParamItem}, pbr::MaterialPipeline,
pbr::{
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
SetMeshViewBindGroup,
},
prelude::*, prelude::*,
reflect::TypeUuid, reflect::TypeUuid,
render::{ render::{
camera::PerspectiveCameraBundle, render_asset::{PrepareAssetError, RenderAsset},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
},
render_resource::{ render_resource::{
std140::{AsStd140, Std140}, std140::{AsStd140, Std140},
*, *,
}, },
renderer::RenderDevice, renderer::RenderDevice,
view::{ExtractedView, Msaa},
RenderApp, RenderStage,
}, },
}; };
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugin(CustomMaterialPlugin) .add_plugin(MaterialPlugin::<CustomMaterial>::default())
.add_startup_system(setup) .add_startup_system(setup)
.run(); .run();
} }
@ -40,16 +28,14 @@ fn setup(
mut materials: ResMut<Assets<CustomMaterial>>, mut materials: ResMut<Assets<CustomMaterial>>,
) { ) {
// cube // cube
commands.spawn().insert_bundle(( commands.spawn().insert_bundle(MaterialMeshBundle {
meshes.add(Mesh::from(shape::Cube { size: 1.0 })), mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Transform::from_xyz(0.0, 0.5, 0.0), transform: Transform::from_xyz(0.0, 0.5, 0.0),
GlobalTransform::default(), material: materials.add(CustomMaterial {
Visibility::default(),
ComputedVisibility::default(),
materials.add(CustomMaterial {
color: Color::GREEN, color: Color::GREEN,
}), }),
)); ..Default::default()
});
// camera // camera
commands.spawn_bundle(PerspectiveCameraBundle { commands.spawn_bundle(PerspectiveCameraBundle {
@ -73,14 +59,14 @@ pub struct GpuCustomMaterial {
impl RenderAsset for CustomMaterial { impl RenderAsset for CustomMaterial {
type ExtractedAsset = CustomMaterial; type ExtractedAsset = CustomMaterial;
type PreparedAsset = GpuCustomMaterial; type PreparedAsset = GpuCustomMaterial;
type Param = (SRes<RenderDevice>, SRes<CustomPipeline>); type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>);
fn extract_asset(&self) -> Self::ExtractedAsset { fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone() self.clone()
} }
fn prepare_asset( fn prepare_asset(
extracted_asset: Self::ExtractedAsset, extracted_asset: Self::ExtractedAsset,
(render_device, custom_pipeline): &mut SystemParamItem<Self::Param>, (render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> { ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
@ -94,7 +80,7 @@ impl RenderAsset for CustomMaterial {
resource: buffer.as_entire_binding(), resource: buffer.as_entire_binding(),
}], }],
label: None, label: None,
layout: &custom_pipeline.material_layout, layout: &material_pipeline.material_layout,
}); });
Ok(GpuCustomMaterial { Ok(GpuCustomMaterial {
@ -103,51 +89,18 @@ impl RenderAsset for CustomMaterial {
}) })
} }
} }
pub struct CustomMaterialPlugin;
impl Plugin for CustomMaterialPlugin { impl Material for CustomMaterial {
fn build(&self, app: &mut App) { fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> {
app.add_asset::<CustomMaterial>() Some(asset_server.load("shaders/custom_material.wgsl"))
.add_plugin(ExtractComponentPlugin::<Handle<CustomMaterial>>::default())
.add_plugin(RenderAssetPlugin::<CustomMaterial>::default());
app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<CustomPipeline>()
.init_resource::<SpecializedPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_custom);
} }
}
pub struct CustomPipeline { fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup {
mesh_pipeline: MeshPipeline, &render_asset.bind_group
material_layout: BindGroupLayout,
shader: Handle<Shader>,
}
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
} }
}
impl FromWorld for CustomPipeline { fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
fn from_world(world: &mut World) -> Self { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
let asset_server = world.get_resource::<AssetServer>().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::<RenderDevice>().unwrap();
let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: ShaderStages::FRAGMENT, visibility: ShaderStages::FRAGMENT,
@ -159,80 +112,6 @@ impl FromWorld for CustomPipeline {
count: None, count: None,
}], }],
label: None, label: None,
}); })
CustomPipeline {
mesh_pipeline: world.get_resource::<MeshPipeline>().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<DrawFunctions<Transparent3d>>,
materials: Res<RenderAssets<CustomMaterial>>,
render_meshes: Res<RenderAssets<Mesh>>,
custom_pipeline: Res<CustomPipeline>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
mut specialized_pipelines: ResMut<SpecializedPipelines<CustomPipeline>>,
msaa: Res<Msaa>,
material_meshes: Query<(Entity, &Handle<CustomMaterial>, &Handle<Mesh>, &MeshUniform)>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
) {
let draw_custom = transparent_3d_draw_functions
.read()
.get_id::<DrawCustom>()
.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<RenderAssets<CustomMaterial>>,
SQuery<Read<Handle<CustomMaterial>>>,
);
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
} }
} }