2021-12-25 21:45:43 +00:00
|
|
|
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,
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
mesh::MeshVertexBufferLayout,
|
2021-12-25 21:45:43 +00:00
|
|
|
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>>,
|
2022-04-07 15:50:14 +00:00
|
|
|
/// 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,
|
2021-12-25 21:45:43 +00:00
|
|
|
pub occlusion_texture: Option<Handle<Image>>,
|
2022-03-05 03:37:23 +00:00
|
|
|
/// 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`.
|
2021-12-25 21:45:43 +00:00
|
|
|
pub double_sided: bool,
|
2022-03-05 03:37:23 +00:00
|
|
|
/// Whether to cull the "front", "back" or neither side of a mesh
|
|
|
|
/// defaults to `Face::Back`
|
|
|
|
pub cull_mode: Option<Face>,
|
2021-12-25 21:45:43 +00:00
|
|
|
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,
|
2022-04-07 15:50:14 +00:00
|
|
|
flip_normal_map_y: false,
|
2021-12-25 21:45:43 +00:00
|
|
|
double_sided: false,
|
2022-03-05 03:37:23 +00:00
|
|
|
cull_mode: Some(Face::Back),
|
2021-12-25 21:45:43 +00:00
|
|
|
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);
|
2022-03-15 22:26:46 +00:00
|
|
|
const TWO_COMPONENT_NORMAL_MAP = (1 << 9);
|
2022-04-07 15:50:14 +00:00
|
|
|
const FLIP_NORMAL_MAP_Y = (1 << 10);
|
2021-12-25 21:45:43 +00:00
|
|
|
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,
|
2022-03-05 03:37:23 +00:00
|
|
|
pub cull_mode: Option<Face>,
|
2021-12-25 21:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2022-03-15 22:26:46 +00:00
|
|
|
if has_normal_map {
|
|
|
|
match gpu_images
|
|
|
|
.get(material.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
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2022-04-07 15:50:14 +00:00
|
|
|
if material.flip_normal_map_y {
|
|
|
|
flags |= StandardMaterialFlags::FLIP_NORMAL_MAP_Y;
|
|
|
|
}
|
2022-03-15 22:26:46 +00:00
|
|
|
}
|
2021-12-25 21:45:43 +00:00
|
|
|
// 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;
|
2022-02-13 22:33:55 +00:00
|
|
|
flags |= StandardMaterialFlags::ALPHA_MODE_MASK;
|
2021-12-25 21:45:43 +00:00
|
|
|
}
|
|
|
|
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,
|
2022-03-05 03:37:23 +00:00
|
|
|
cull_mode: material.cull_mode,
|
2021-12-25 21:45:43 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
|
|
pub struct StandardMaterialKey {
|
|
|
|
normal_map: bool,
|
2022-03-05 03:37:23 +00:00
|
|
|
cull_mode: Option<Face>,
|
2021-12-25 21:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2022-03-05 03:37:23 +00:00
|
|
|
cull_mode: render_asset.cull_mode,
|
2021-12-25 21:45:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
fn specialize(
|
2022-04-07 16:16:35 +00:00
|
|
|
_pipeline: &MaterialPipeline<Self>,
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
|
|
key: Self::Key,
|
|
|
|
_layout: &MeshVertexBufferLayout,
|
|
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
2021-12-25 21:45:43 +00:00
|
|
|
if key.normal_map {
|
|
|
|
descriptor
|
|
|
|
.fragment
|
|
|
|
.as_mut()
|
|
|
|
.unwrap()
|
|
|
|
.shader_defs
|
|
|
|
.push(String::from("STANDARDMATERIAL_NORMAL_MAP"));
|
|
|
|
}
|
2022-03-05 03:37:23 +00:00
|
|
|
descriptor.primitive.cull_mode = key.cull_mode;
|
2021-12-25 21:45:43 +00:00
|
|
|
if let Some(label) = &mut descriptor.label {
|
|
|
|
*label = format!("pbr_{}", *label).into();
|
|
|
|
}
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
Ok(())
|
2021-12-25 21:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|