mirror of
https://github.com/bevyengine/bevy
synced 2024-12-20 01:53:12 +00:00
747b0c69b0
# Objective This PR reworks Bevy's Material system, making the user experience of defining Materials _much_ nicer. Bevy's previous material system leaves a lot to be desired: * Materials require manually implementing the `RenderAsset` trait, which involves manually generating the bind group, handling gpu buffer data transfer, looking up image textures, etc. Even the simplest single-texture material involves writing ~80 unnecessary lines of code. This was never the long term plan. * There are two material traits, which is confusing, hard to document, and often redundant: `Material` and `SpecializedMaterial`. `Material` implicitly implements `SpecializedMaterial`, and `SpecializedMaterial` is used in most high level apis to support both use cases. Most users shouldn't need to think about specialization at all (I consider it a "power-user tool"), so the fact that `SpecializedMaterial` is front-and-center in our apis is a miss. * Implementing either material trait involves a lot of "type soup". The "prepared asset" parameter is particularly heinous: `&<Self as RenderAsset>::PreparedAsset`. Defining vertex and fragment shaders is also more verbose than it needs to be. ## Solution Say hello to the new `Material` system: ```rust #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } impl Material for CoolMaterial { fn fragment_shader() -> ShaderRef { "cool_material.wgsl".into() } } ``` Thats it! This same material would have required [~80 lines of complicated "type heavy" code](https://github.com/bevyengine/bevy/blob/v0.7.0/examples/shader/shader_material.rs) in the old Material system. Now it is just 14 lines of simple, readable code. This is thanks to a new consolidated `Material` trait and the new `AsBindGroup` trait / derive. ### The new `Material` trait The old "split" `Material` and `SpecializedMaterial` traits have been removed in favor of a new consolidated `Material` trait. All of the functions on the trait are optional. The difficulty of implementing `Material` has been reduced by simplifying dataflow and removing type complexity: ```rust // Old impl Material for CustomMaterial { fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { Some(asset_server.load("custom_material.wgsl")) } fn alpha_mode(render_asset: &<Self as RenderAsset>::PreparedAsset) -> AlphaMode { render_asset.alpha_mode } } // New impl Material for CustomMaterial { fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } fn alpha_mode(&self) -> AlphaMode { self.alpha_mode } } ``` Specialization is still supported, but it is hidden by default under the `specialize()` function (more on this later). ### The `AsBindGroup` trait / derive The `Material` trait now requires the `AsBindGroup` derive. This can be implemented manually relatively easily, but deriving it will almost always be preferable. Field attributes like `uniform` and `texture` are used to define which fields should be bindings, what their binding type is, and what index they should be bound at: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } ``` In WGSL shaders, the binding looks like this: ```wgsl struct CoolMaterial { color: vec4<f32>; }; [[group(1), binding(0)]] var<uniform> material: CoolMaterial; [[group(1), binding(1)]] var color_texture: texture_2d<f32>; [[group(1), binding(2)]] var color_sampler: sampler; ``` Note that the "group" index is determined by the usage context. It is not defined in `AsBindGroup`. Bevy material bind groups are bound to group 1. The following field-level attributes are supported: * `uniform(BINDING_INDEX)` * The field will be converted to a shader-compatible type using the `ShaderType` trait, written to a `Buffer`, and bound as a uniform. It can also be derived for custom structs. * `texture(BINDING_INDEX)` * This field's `Handle<Image>` will be used to look up the matching `Texture` gpu resource, which will be bound as a texture in shaders. The field will be assumed to implement `Into<Option<Handle<Image>>>`. In practice, most fields should be a `Handle<Image>` or `Option<Handle<Image>>`. If the value of an `Option<Handle<Image>>` is `None`, the new `FallbackImage` resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute (with a different binding index). * `sampler(BINDING_INDEX)` * Behaves exactly like the `texture` attribute, but sets the Image's sampler binding instead of the texture. Note that fields without field-level binding attributes will be ignored. ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, this_field_is_ignored: String, } ``` As mentioned above, `Option<Handle<Image>>` is also supported: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Option<Handle<Image>>, } ``` This is useful if you want a texture to be optional. When the value is `None`, the `FallbackImage` will be used for the binding instead, which defaults to "pure white". Field uniforms with the same binding index will be combined into a single binding: ```rust #[derive(AsBindGroup)] struct CoolMaterial { #[uniform(0)] color: Color, #[uniform(0)] roughness: f32, } ``` In WGSL shaders, the binding would look like this: ```wgsl struct CoolMaterial { color: vec4<f32>; roughness: f32; }; [[group(1), binding(0)]] var<uniform> material: CoolMaterial; ``` Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes: * `uniform(BINDING_INDEX, ConvertedShaderType)` * Similar to the field-level `uniform` attribute, but instead the entire `AsBindGroup` value is converted to `ConvertedShaderType`, which must implement `ShaderType`. This is useful if more complicated conversion logic is required. * `bind_group_data(DataType)` * The `AsBindGroup` type will be converted to some `DataType` using `Into<DataType>` and stored as `AsBindGroup::Data` as part of the `AsBindGroup::as_bind_group` call. This is useful if data needs to be stored alongside the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute is "shader pipeline specialization". The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can also be equivalently represented with a single struct-level uniform attribute: ```rust #[derive(AsBindGroup)] #[uniform(0, CoolMaterialUniform)] struct CoolMaterial { color: Color, roughness: f32, } #[derive(ShaderType)] struct CoolMaterialUniform { color: Color, roughness: f32, } impl From<&CoolMaterial> for CoolMaterialUniform { fn from(material: &CoolMaterial) -> CoolMaterialUniform { CoolMaterialUniform { color: material.color, roughness: material.roughness, } } } ``` ### Material Specialization Material shader specialization is now _much_ simpler: ```rust #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] #[bind_group_data(CoolMaterialKey)] struct CoolMaterial { #[uniform(0)] color: Color, is_red: bool, } #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct CoolMaterialKey { is_red: bool, } impl From<&CoolMaterial> for CoolMaterialKey { fn from(material: &CoolMaterial) -> CoolMaterialKey { CoolMaterialKey { is_red: material.is_red, } } } impl Material for CoolMaterial { fn fragment_shader() -> ShaderRef { "cool_material.wgsl".into() } fn specialize( pipeline: &MaterialPipeline<Self>, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, key: MaterialPipelineKey<Self>, ) -> Result<(), SpecializedMeshPipelineError> { if key.bind_group_data.is_red { let fragment = descriptor.fragment.as_mut().unwrap(); fragment.shader_defs.push("IS_RED".to_string()); } Ok(()) } } ``` Setting `bind_group_data` is not required for specialization (it defaults to `()`). Scenarios like "custom vertex attributes" also benefit from this system: ```rust impl Material for CustomMaterial { fn vertex_shader() -> ShaderRef { "custom_material.wgsl".into() } fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } fn specialize( pipeline: &MaterialPipeline<Self>, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, key: MaterialPipelineKey<Self>, ) -> Result<(), SpecializedMeshPipelineError> { let vertex_layout = layout.get_layout(&[ Mesh::ATTRIBUTE_POSITION.at_shader_location(0), ATTRIBUTE_BLEND_COLOR.at_shader_location(1), ])?; descriptor.vertex.buffers = vec![vertex_layout]; Ok(()) } } ``` ### Ported `StandardMaterial` to the new `Material` system Bevy's built-in PBR material uses the new Material system (including the AsBindGroup derive): ```rust #[derive(AsBindGroup, Debug, Clone, TypeUuid)] #[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] #[bind_group_data(StandardMaterialKey)] #[uniform(0, StandardMaterialUniform)] pub struct StandardMaterial { pub base_color: Color, #[texture(1)] #[sampler(2)] pub base_color_texture: Option<Handle<Image>>, /* other fields omitted for brevity */ ``` ### Ported Bevy examples to the new `Material` system The overall complexity of Bevy's "custom shader examples" has gone down significantly. Take a look at the diffs if you want a dopamine spike. Please note that while this PR has a net increase in "lines of code", most of those extra lines come from added documentation. There is a significant reduction in the overall complexity of the code (even accounting for the new derive logic). --- ## Changelog ### Added * `AsBindGroup` trait and derive, which make it much easier to transfer data to the gpu and generate bind groups for a given type. ### Changed * The old `Material` and `SpecializedMaterial` traits have been replaced by a consolidated (much simpler) `Material` trait. Materials no longer implement `RenderAsset`. * `StandardMaterial` was ported to the new material system. There are no user-facing api changes to the `StandardMaterial` struct api, but it now implements `AsBindGroup` and `Material` instead of `RenderAsset` and `SpecializedMaterial`. ## Migration Guide The Material system has been reworked to be much simpler. We've removed a lot of boilerplate with the new `AsBindGroup` derive and the `Material` trait is simpler as well! ### Bevy 0.7 (old) ```rust #[derive(Debug, Clone, TypeUuid)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { color: Color, color_texture: Handle<Image>, } #[derive(Clone)] pub struct GpuCustomMaterial { _buffer: Buffer, bind_group: BindGroup, } impl RenderAsset for CustomMaterial { type ExtractedAsset = CustomMaterial; type PreparedAsset = GpuCustomMaterial; type Param = (SRes<RenderDevice>, SRes<MaterialPipeline<Self>>); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( extracted_asset: Self::ExtractedAsset, (render_device, material_pipeline): &mut SystemParamItem<Self::Param>, ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> { let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); let byte_buffer = [0u8; Vec4::SIZE.get() as usize]; let mut buffer = encase::UniformBuffer::new(byte_buffer); buffer.write(&color).unwrap(); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { contents: buffer.as_ref(), label: None, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); let (texture_view, texture_sampler) = if let Some(result) = material_pipeline .mesh_pipeline .get_image_texture(gpu_images, &Some(extracted_asset.color_texture.clone())) { result } else { return Err(PrepareAssetError::RetryNextUpdate(extracted_asset)); }; let bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: buffer.as_entire_binding(), }, BindGroupEntry { binding: 0, resource: BindingResource::TextureView(texture_view), }, BindGroupEntry { binding: 1, resource: BindingResource::Sampler(texture_sampler), }, ], label: None, layout: &material_pipeline.material_layout, }); Ok(GpuCustomMaterial { _buffer: buffer, bind_group, }) } } impl Material for CustomMaterial { fn fragment_shader(asset_server: &AssetServer) -> Option<Handle<Shader>> { Some(asset_server.load("custom_material.wgsl")) } fn bind_group(render_asset: &<Self as RenderAsset>::PreparedAsset) -> &BindGroup { &render_asset.bind_group } fn bind_group_layout(render_device: &RenderDevice) -> 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: Some(Vec4::min_size()), }, count: None, }, BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Float { filterable: true }, view_dimension: TextureViewDimension::D2Array, }, count: None, }, BindGroupLayoutEntry { binding: 2, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, ], label: None, }) } } ``` ### Bevy 0.8 (new) ```rust impl Material for CustomMaterial { fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } } #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Handle<Image>, } ``` ## Future Work * Add support for more binding types (cubemaps, buffers, etc). This PR intentionally includes a bare minimum number of binding types to keep "reviewability" in check. * Consider optionally eliding binding indices using binding names. `AsBindGroup` could pass in (optional?) reflection info as a "hint". * This would make it possible for the derive to do this: ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { #[uniform] color: Color, #[texture] #[sampler] color_texture: Option<Handle<Image>>, alpha_mode: AlphaMode, } ``` * Or this ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { #[binding] color: Color, #[binding] color_texture: Option<Handle<Image>>, alpha_mode: AlphaMode, } ``` * Or even this (if we flip to "include bindings by default") ```rust #[derive(AsBindGroup)] pub struct CustomMaterial { color: Color, color_texture: Option<Handle<Image>>, #[binding(ignore)] alpha_mode: AlphaMode, } ``` * If we add the option to define custom draw functions for materials (which could be done in a type-erased way), I think that would be enough to support extra non-material bindings. Worth considering!
284 lines
11 KiB
Rust
284 lines
11 KiB
Rust
use crate::{AlphaMode, Material, MaterialPipeline, MaterialPipelineKey, PBR_SHADER_HANDLE};
|
|
use bevy_asset::Handle;
|
|
use bevy_math::Vec4;
|
|
use bevy_reflect::TypeUuid;
|
|
use bevy_render::{
|
|
color::Color, mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_resource::*,
|
|
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(AsBindGroup, Debug, Clone, TypeUuid)]
|
|
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
|
|
#[bind_group_data(StandardMaterialKey)]
|
|
#[uniform(0, StandardMaterialUniform)]
|
|
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,
|
|
#[texture(1)]
|
|
#[sampler(2)]
|
|
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,
|
|
#[texture(3)]
|
|
#[sampler(4)]
|
|
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,
|
|
#[texture(5)]
|
|
#[sampler(6)]
|
|
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,
|
|
#[texture(9)]
|
|
#[sampler(10)]
|
|
pub normal_map_texture: Option<Handle<Image>>,
|
|
/// Normal map textures authored for DirectX have their y-component flipped. Set this to flip
|
|
/// it to right-handed conventions.
|
|
pub flip_normal_map_y: bool,
|
|
#[texture(7)]
|
|
#[sampler(8)]
|
|
pub occlusion_texture: Option<Handle<Image>>,
|
|
/// Support two-sided lighting by automatically flipping the normals for "back" faces
|
|
/// within the PBR lighting shader.
|
|
/// Defaults to false.
|
|
/// This does not automatically configure backface culling, which can be done via
|
|
/// `cull_mode`.
|
|
pub double_sided: bool,
|
|
/// Whether to cull the "front", "back" or neither side of a mesh
|
|
/// defaults to `Face::Back`
|
|
pub cull_mode: Option<Face>,
|
|
pub unlit: bool,
|
|
pub alpha_mode: AlphaMode,
|
|
pub depth_bias: f32,
|
|
}
|
|
|
|
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,
|
|
flip_normal_map_y: false,
|
|
double_sided: false,
|
|
cull_mode: Some(Face::Back),
|
|
unlit: false,
|
|
alpha_mode: AlphaMode::Opaque,
|
|
depth_bias: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Color> for StandardMaterial {
|
|
fn from(color: Color) -> Self {
|
|
StandardMaterial {
|
|
base_color: color,
|
|
alpha_mode: if color.a() < 1.0 {
|
|
AlphaMode::Blend
|
|
} else {
|
|
AlphaMode::Opaque
|
|
},
|
|
..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_types.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 TWO_COMPONENT_NORMAL_MAP = (1 << 9);
|
|
const FLIP_NORMAL_MAP_Y = (1 << 10);
|
|
const NONE = 0;
|
|
const UNINITIALIZED = 0xFFFF;
|
|
}
|
|
}
|
|
|
|
/// The GPU representation of the uniform data of a [`StandardMaterial`].
|
|
#[derive(Clone, Default, ShaderType)]
|
|
pub struct StandardMaterialUniform {
|
|
/// 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,
|
|
}
|
|
|
|
impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|
fn as_bind_group_shader_type(&self, images: &RenderAssets<Image>) -> StandardMaterialUniform {
|
|
let mut flags = StandardMaterialFlags::NONE;
|
|
if self.base_color_texture.is_some() {
|
|
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
|
|
}
|
|
if self.emissive_texture.is_some() {
|
|
flags |= StandardMaterialFlags::EMISSIVE_TEXTURE;
|
|
}
|
|
if self.metallic_roughness_texture.is_some() {
|
|
flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE;
|
|
}
|
|
if self.occlusion_texture.is_some() {
|
|
flags |= StandardMaterialFlags::OCCLUSION_TEXTURE;
|
|
}
|
|
if self.double_sided {
|
|
flags |= StandardMaterialFlags::DOUBLE_SIDED;
|
|
}
|
|
if self.unlit {
|
|
flags |= StandardMaterialFlags::UNLIT;
|
|
}
|
|
let has_normal_map = self.normal_map_texture.is_some();
|
|
if has_normal_map {
|
|
match images
|
|
.get(self.normal_map_texture.as_ref().unwrap())
|
|
.unwrap()
|
|
.texture_format
|
|
{
|
|
// All 2-component unorm formats
|
|
TextureFormat::Rg8Unorm
|
|
| TextureFormat::Rg16Unorm
|
|
| TextureFormat::Bc5RgUnorm
|
|
| TextureFormat::EacRg11Unorm => {
|
|
flags |= StandardMaterialFlags::TWO_COMPONENT_NORMAL_MAP;
|
|
}
|
|
_ => {}
|
|
}
|
|
if self.flip_normal_map_y {
|
|
flags |= StandardMaterialFlags::FLIP_NORMAL_MAP_Y;
|
|
}
|
|
}
|
|
// NOTE: 0.5 is from the glTF default - do we want this?
|
|
let mut alpha_cutoff = 0.5;
|
|
match self.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,
|
|
};
|
|
|
|
StandardMaterialUniform {
|
|
base_color: self.base_color.as_linear_rgba_f32().into(),
|
|
emissive: self.emissive.into(),
|
|
roughness: self.perceptual_roughness,
|
|
metallic: self.metallic,
|
|
reflectance: self.reflectance,
|
|
flags: flags.bits(),
|
|
alpha_cutoff,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct StandardMaterialKey {
|
|
normal_map: bool,
|
|
cull_mode: Option<Face>,
|
|
}
|
|
|
|
impl From<&StandardMaterial> for StandardMaterialKey {
|
|
fn from(material: &StandardMaterial) -> Self {
|
|
StandardMaterialKey {
|
|
normal_map: material.normal_map_texture.is_some(),
|
|
cull_mode: material.cull_mode,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Material for StandardMaterial {
|
|
fn specialize(
|
|
_pipeline: &MaterialPipeline<Self>,
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
_layout: &MeshVertexBufferLayout,
|
|
key: MaterialPipelineKey<Self>,
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
if key.bind_group_data.normal_map {
|
|
descriptor
|
|
.fragment
|
|
.as_mut()
|
|
.unwrap()
|
|
.shader_defs
|
|
.push(String::from("STANDARDMATERIAL_NORMAL_MAP"));
|
|
}
|
|
descriptor.primitive.cull_mode = key.bind_group_data.cull_mode;
|
|
if let Some(label) = &mut descriptor.label {
|
|
*label = format!("pbr_{}", *label).into();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn fragment_shader() -> ShaderRef {
|
|
PBR_SHADER_HANDLE.typed().into()
|
|
}
|
|
|
|
#[inline]
|
|
fn alpha_mode(&self) -> AlphaMode {
|
|
self.alpha_mode
|
|
}
|
|
|
|
#[inline]
|
|
fn depth_bias(&self) -> f32 {
|
|
self.depth_bias
|
|
}
|
|
}
|