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:
parent
22c665fa39
commit
963e2f08a2
@ -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"] }
|
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
480
crates/bevy_pbr/src/pbr_material.rs
Normal file
480
crates/bevy_pbr/src/pbr_material.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user