use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Handle}; use bevy_math::Vec4; use bevy_reflect::TypeUuid; use bevy_render2::{ color::Color, render_asset::{RenderAsset, RenderAssetPlugin}, render_resource::{Buffer, BufferInitDescriptor, BufferUsage}, renderer::{RenderDevice, RenderQueue}, texture::Image, }; use crevice::std140::{AsStd140, Std140}; // NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag! bitflags::bitflags! { #[repr(transparent)] 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 NONE = 0; const UNINITIALIZED = 0xFFFF; } } /// A material with "standard" properties used in PBR lighting /// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf #[derive(Debug, Clone, TypeUuid)] #[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] pub struct StandardMaterial { /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything /// in between. If used together with a base_color_texture, this is factored into the final /// base color as `base_color * base_color_texture_value` pub base_color: Color, pub base_color_texture: Option>, // Use a color for user friendliness even though we technically don't use the alpha channel // Might be used in the future for exposure correction in HDR pub emissive: Color, pub emissive_texture: Option>, /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader /// Defaults to minimum of 0.089 /// If used together with a roughness/metallic texture, this is factored into the final base /// color as `roughness * roughness_texture_value` pub perceptual_roughness: f32, /// From [0.0, 1.0], dielectric to pure metallic /// If used together with a roughness/metallic texture, this is factored into the final base /// color as `metallic * metallic_texture_value` pub metallic: f32, pub metallic_roughness_texture: Option>, /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] /// defaults to 0.5 which is mapped to 4% reflectance in the shader pub reflectance: f32, pub occlusion_texture: Option>, pub double_sided: bool, pub unlit: bool, } 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, double_sided: false, unlit: false, } } } impl From for StandardMaterial { fn from(color: Color) -> Self { StandardMaterial { base_color: color, ..Default::default() } } } impl From> for StandardMaterial { fn from(texture: Handle) -> Self { StandardMaterial { base_color_texture: Some(texture), ..Default::default() } } } #[derive(Clone, 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, } pub struct StandardMaterialPlugin; impl Plugin for StandardMaterialPlugin { fn build(&self, app: &mut App) { app.add_plugin(RenderAssetPlugin::::default()) .add_asset::(); } } #[derive(Debug, Clone)] pub struct GpuStandardMaterial { pub buffer: Buffer, // FIXME: image handles feel unnecessary here but the extracted asset is discarded pub base_color_texture: Option>, pub emissive_texture: Option>, pub metallic_roughness_texture: Option>, pub occlusion_texture: Option>, } impl RenderAsset for StandardMaterial { type ExtractedAsset = StandardMaterial; type PreparedAsset = GpuStandardMaterial; fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( material: Self::ExtractedAsset, render_device: &RenderDevice, _render_queue: &RenderQueue, ) -> Self::PreparedAsset { 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 value = StandardMaterialUniformData { base_color: material.base_color.as_rgba_linear().into(), emissive: material.emissive.into(), roughness: material.perceptual_roughness, metallic: material.metallic, reflectance: material.reflectance, flags: flags.bits, }; let value_std140 = value.as_std140(); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { label: None, usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST, contents: value_std140.as_bytes(), }); GpuStandardMaterial { buffer, base_color_texture: material.base_color_texture, emissive_texture: material.emissive_texture, metallic_roughness_texture: material.metallic_roughness_texture, occlusion_texture: material.occlusion_texture, } } }