mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Use GpuArrayBuffer for MeshUniform (#9254)
# Objective - Reduce the number of rebindings to enable batching of draw commands ## Solution - Use the new `GpuArrayBuffer` for `MeshUniform` data to store all `MeshUniform` data in arrays within fewer bindings - Sort opaque/alpha mask prepass, opaque/alpha mask main, and shadow phases also by the batch per-object data binding dynamic offset to improve performance on WebGL2. --- ## Changelog - Changed: Per-object `MeshUniform` data is now managed by `GpuArrayBuffer` as arrays in buffers that need to be indexed into. ## Migration Guide Accessing the `model` member of an individual mesh object's shader `Mesh` struct the old way where each `MeshUniform` was stored at its own dynamic offset: ```rust struct Vertex { @location(0) position: vec3<f32>, }; fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; out.clip_position = mesh_position_local_to_clip( mesh.model, vec4<f32>(vertex.position, 1.0) ); return out; } ``` The new way where one needs to index into the array of `Mesh`es for the batch: ```rust struct Vertex { @builtin(instance_index) instance_index: u32, @location(0) position: vec3<f32>, }; fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; out.clip_position = mesh_position_local_to_clip( mesh[vertex.instance_index].model, vec4<f32>(vertex.position, 1.0) ); return out; } ``` Note that using the instance_index is the default way to pass the per-object index into the shader, but if you wish to do custom rendering approaches you can pass it in however you like. --------- Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com> Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
This commit is contained in:
parent
fb9c5a6cbb
commit
e6405bb7b4
21 changed files with 335 additions and 151 deletions
|
@ -8,6 +8,7 @@ struct CustomMaterial {
|
|||
var<uniform> material: CustomMaterial;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) blend_color: vec4<f32>,
|
||||
};
|
||||
|
@ -21,8 +22,8 @@ struct VertexOutput {
|
|||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = mesh_position_local_to_clip(
|
||||
mesh.model,
|
||||
vec4<f32>(vertex.position, 1.0)
|
||||
mesh[vertex.instance_index].model,
|
||||
vec4<f32>(vertex.position, 1.0),
|
||||
);
|
||||
out.blend_color = vertex.blend_color;
|
||||
return out;
|
||||
|
|
|
@ -19,8 +19,12 @@ struct VertexOutput {
|
|||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz;
|
||||
var out: VertexOutput;
|
||||
// NOTE: The 0 index into the Mesh array is a hack for this example as the
|
||||
// instance_index builtin would map to the wrong index in the Mesh array.
|
||||
// This index could be passed in via another uniform instead but it's
|
||||
// unnecessary for the example.
|
||||
out.clip_position = mesh_position_local_to_clip(
|
||||
mesh.model,
|
||||
mesh[0].model,
|
||||
vec4<f32>(position, 1.0)
|
||||
);
|
||||
out.color = vertex.i_color;
|
||||
|
|
|
@ -136,14 +136,18 @@ impl Plugin for Core3dPlugin {
|
|||
|
||||
pub struct Opaque3d {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for Opaque3d {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -152,7 +156,10 @@ impl PhaseItem for Opaque3d {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -163,7 +170,9 @@ impl PhaseItem for Opaque3d {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,14 +185,18 @@ impl CachedRenderPipelinePhaseItem for Opaque3d {
|
|||
|
||||
pub struct AlphaMask3d {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for AlphaMask3d {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort.
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -192,7 +205,10 @@ impl PhaseItem for AlphaMask3d {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -203,7 +219,9 @@ impl PhaseItem for AlphaMask3d {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,14 +80,18 @@ pub struct ViewPrepassTextures {
|
|||
/// Used to render all 3D meshes with materials that have no transparency.
|
||||
pub struct Opaque3dPrepass {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline_id: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for Opaque3dPrepass {
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -96,7 +100,10 @@ impl PhaseItem for Opaque3dPrepass {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -107,7 +114,9 @@ impl PhaseItem for Opaque3dPrepass {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,14 +134,18 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass {
|
|||
/// Used to render all meshes with a material with an alpha mask.
|
||||
pub struct AlphaMask3dPrepass {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline_id: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for AlphaMask3dPrepass {
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort.
|
||||
type SortKey = Reverse<FloatOrd>;
|
||||
// NOTE: (dynamic offset, -distance)
|
||||
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||
type SortKey = (u32, Reverse<FloatOrd>);
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -141,7 +154,10 @@ impl PhaseItem for AlphaMask3dPrepass {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
Reverse(FloatOrd(self.distance))
|
||||
(
|
||||
self.per_object_binding_dynamic_offset,
|
||||
Reverse(FloatOrd(self.distance)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -152,7 +168,9 @@ impl PhaseItem for AlphaMask3dPrepass {
|
|||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// Key negated to match reversed SortKey ordering
|
||||
radsort::sort_by_key(items, |item| -item.distance);
|
||||
radsort::sort_by_key(items, |item| {
|
||||
(item.per_object_binding_dynamic_offset, -item.distance)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,5 +29,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" }
|
|||
bitflags = "2.3"
|
||||
# direct dependency required for derive macro
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
radsort = "0.1"
|
||||
naga_oil = "0.8"
|
||||
radsort = "0.1"
|
||||
smallvec = "1.6"
|
||||
|
|
|
@ -30,9 +30,9 @@ use bevy_render::{
|
|||
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
||||
},
|
||||
render_resource::{
|
||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
|
||||
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, GpuArrayBufferIndex,
|
||||
OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::FallbackImage,
|
||||
|
@ -379,7 +379,12 @@ pub fn queue_material_meshes<M: Material>(
|
|||
msaa: Res<Msaa>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
|
||||
material_meshes: Query<(
|
||||
&Handle<M>,
|
||||
&Handle<Mesh>,
|
||||
&MeshUniform,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
images: Res<RenderAssets<Image>>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
|
@ -463,7 +468,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for visible_entity in &visible_entities.entities {
|
||||
if let Ok((material_handle, mesh_handle, mesh_uniform)) =
|
||||
if let Ok((material_handle, mesh_handle, mesh_uniform, batch_indices)) =
|
||||
material_meshes.get(*visible_entity)
|
||||
{
|
||||
if let (Some(mesh), Some(material)) = (
|
||||
|
@ -520,6 +525,9 @@ pub fn queue_material_meshes<M: Material>(
|
|||
draw_function: draw_opaque_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Mask(_) => {
|
||||
|
@ -528,6 +536,9 @@ pub fn queue_material_meshes<M: Material>(
|
|||
draw_function: draw_alpha_mask_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend
|
||||
|
|
|
@ -30,9 +30,9 @@ use bevy_render::{
|
|||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
|
||||
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
|
||||
DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache,
|
||||
PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages,
|
||||
ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||
DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState,
|
||||
PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||
ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
|
||||
TextureViewDimension, VertexState,
|
||||
},
|
||||
|
@ -751,7 +751,12 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
msaa: Res<Msaa>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
|
||||
material_meshes: Query<(
|
||||
&Handle<M>,
|
||||
&Handle<Mesh>,
|
||||
&MeshUniform,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
mut views: Query<(
|
||||
&ExtractedView,
|
||||
&VisibleEntities,
|
||||
|
@ -796,7 +801,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
let rangefinder = view.rangefinder3d();
|
||||
|
||||
for visible_entity in &visible_entities.entities {
|
||||
let Ok((material_handle, mesh_handle, mesh_uniform)) = material_meshes.get(*visible_entity) else {
|
||||
let Ok((material_handle, mesh_handle, mesh_uniform, batch_indices)) = material_meshes.get(*visible_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -848,6 +853,9 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
draw_function: opaque_draw_prepass,
|
||||
pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Mask(_) => {
|
||||
|
@ -856,6 +864,9 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
draw_function: alpha_mask_draw_prepass,
|
||||
pipeline_id,
|
||||
distance,
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
// Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can
|
||||
// pass them to custom prepass shaders like pbr_prepass.wgsl.
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
|
||||
#ifdef VERTEX_UVS
|
||||
|
@ -88,7 +89,9 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
|||
#ifdef SKINNED
|
||||
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||
#else // SKINNED
|
||||
var model = mesh.model;
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
var model = mesh[vertex_no_morph.instance_index].model;
|
||||
#endif // SKINNED
|
||||
|
||||
out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0));
|
||||
|
@ -105,17 +108,33 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
|||
#ifdef SKINNED
|
||||
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
||||
#else // SKINNED
|
||||
out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world(vertex.normal);
|
||||
out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world(
|
||||
vertex.normal,
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
vertex_no_morph.instance_index
|
||||
);
|
||||
#endif // SKINNED
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent);
|
||||
out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world(
|
||||
model,
|
||||
vertex.tangent,
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
vertex_no_morph.instance_index
|
||||
);
|
||||
#endif // VERTEX_TANGENTS
|
||||
#endif // NORMAL_PREPASS
|
||||
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0));
|
||||
out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh.previous_model, vec4<f32>(vertex.position, 1.0));
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(
|
||||
mesh[vertex_no_morph.instance_index].previous_model,
|
||||
vec4<f32>(vertex.position, 1.0)
|
||||
);
|
||||
#endif // MOTION_VECTOR_PREPASS
|
||||
|
||||
return out;
|
||||
|
|
|
@ -15,5 +15,4 @@ var<uniform> previous_view_proj: mat4x4<f32>;
|
|||
|
||||
// Material bindings will be in @group(1)
|
||||
|
||||
@group(2) @binding(0)
|
||||
var<uniform> mesh: bevy_pbr::mesh_types::Mesh;
|
||||
#import bevy_pbr::mesh_bindings mesh
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
|||
CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities,
|
||||
DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight,
|
||||
GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey,
|
||||
NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight,
|
||||
VisiblePointLights,
|
||||
MeshUniform, NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline,
|
||||
RenderMaterials, SpotLight, VisiblePointLights,
|
||||
};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_3d::Transparent3d;
|
||||
|
@ -1556,7 +1556,10 @@ pub fn prepare_clusters(
|
|||
pub fn queue_shadows<M: Material>(
|
||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
||||
casting_meshes: Query<(&Handle<Mesh>, &Handle<M>), Without<NotShadowCaster>>,
|
||||
casting_meshes: Query<
|
||||
(&GpuArrayBufferIndex<MeshUniform>, &Handle<Mesh>, &Handle<M>),
|
||||
Without<NotShadowCaster>,
|
||||
>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_materials: Res<RenderMaterials<M>>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
|
||||
|
@ -1601,7 +1604,9 @@ pub fn queue_shadows<M: Material>(
|
|||
// NOTE: Lights with shadow mapping disabled will have no visible entities
|
||||
// so no meshes will be queued
|
||||
for entity in visible_entities.iter().copied() {
|
||||
if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) {
|
||||
if let Ok((batch_indices, mesh_handle, material_handle)) =
|
||||
casting_meshes.get(entity)
|
||||
{
|
||||
if let (Some(mesh), Some(material)) = (
|
||||
render_meshes.get(mesh_handle),
|
||||
render_materials.get(material_handle),
|
||||
|
@ -1647,7 +1652,10 @@ pub fn queue_shadows<M: Material>(
|
|||
draw_function: draw_shadow_mesh,
|
||||
pipeline: pipeline_id,
|
||||
entity,
|
||||
distance: 0.0, // TODO: sort back-to-front
|
||||
distance: 0.0, // TODO: sort front-to-back
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1658,13 +1666,16 @@ pub fn queue_shadows<M: Material>(
|
|||
|
||||
pub struct Shadow {
|
||||
pub distance: f32,
|
||||
// Per-object data may be bound at different dynamic offsets within a buffer. If it is, then
|
||||
// each batch of per-object data starts at the same dynamic offset.
|
||||
pub per_object_binding_dynamic_offset: u32,
|
||||
pub entity: Entity,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
}
|
||||
|
||||
impl PhaseItem for Shadow {
|
||||
type SortKey = usize;
|
||||
type SortKey = (usize, u32);
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
|
@ -1673,7 +1684,7 @@ impl PhaseItem for Shadow {
|
|||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
self.pipeline.id()
|
||||
(self.pipeline.id(), self.per_object_binding_dynamic_offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1686,7 +1697,7 @@ impl PhaseItem for Shadow {
|
|||
// The shadow phase is sorted by pipeline id for performance reasons.
|
||||
// Grouping all draw commands using the same pipeline together performs
|
||||
// better than rebinding everything at a high rate.
|
||||
radsort::sort_by_key(items, |item| item.pipeline.id());
|
||||
radsort::sort_by_key(items, |item| item.sort_key());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ use bevy_ecs::{
|
|||
use bevy_math::{Mat3A, Mat4, Vec2};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
||||
globals::{GlobalsBuffer, GlobalsUniform},
|
||||
gpu_component_array_buffer::GpuComponentArrayBufferPlugin,
|
||||
mesh::{
|
||||
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||
GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout,
|
||||
|
@ -105,12 +105,6 @@ impl Plugin for MeshRenderPlugin {
|
|||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", Shader::from_wgsl);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
MESH_BINDINGS_HANDLE,
|
||||
"mesh_bindings.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
MESH_FUNCTIONS_HANDLE,
|
||||
|
@ -121,7 +115,7 @@ impl Plugin for MeshRenderPlugin {
|
|||
load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
|
||||
load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.add_plugins(UniformComponentPlugin::<MeshUniform>::default());
|
||||
app.add_plugins(GpuComponentArrayBufferPlugin::<MeshUniform>::default());
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
|
@ -145,9 +139,30 @@ impl Plugin for MeshRenderPlugin {
|
|||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
let mut mesh_bindings_shader_defs = Vec::with_capacity(1);
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::<MeshUniform>::batch_size(
|
||||
render_app.world.resource::<RenderDevice>(),
|
||||
) {
|
||||
mesh_bindings_shader_defs.push(ShaderDefVal::UInt(
|
||||
"PER_OBJECT_BUFFER_BATCH_SIZE".into(),
|
||||
per_object_buffer_batch_size,
|
||||
));
|
||||
}
|
||||
|
||||
render_app.init_resource::<MeshPipeline>();
|
||||
}
|
||||
|
||||
// Load the mesh_bindings shader module here as it depends on runtime information about
|
||||
// whether storage buffers are supported, or the maximum uniform buffer binding size.
|
||||
load_internal_asset!(
|
||||
app,
|
||||
MESH_BINDINGS_HANDLE,
|
||||
"mesh_bindings.wgsl",
|
||||
Shader::from_wgsl_with_defs,
|
||||
mesh_bindings_shader_defs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,7 +324,6 @@ pub struct MeshPipeline {
|
|||
// This dummy white texture is to be used in place of optional StandardMaterial textures
|
||||
pub dummy_white_gpu_image: GpuImage,
|
||||
pub clustered_forward_buffer_binding_type: BufferBindingType,
|
||||
|
||||
pub mesh_layouts: MeshLayouts,
|
||||
}
|
||||
|
||||
|
@ -709,6 +723,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
let mut shader_defs = Vec::new();
|
||||
let mut vertex_attributes = Vec::new();
|
||||
|
||||
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
|
||||
|
||||
if layout.contains(Mesh::ATTRIBUTE_POSITION) {
|
||||
shader_defs.push("VERTEX_POSITIONS".into());
|
||||
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
|
||||
|
@ -932,29 +948,29 @@ pub fn queue_mesh_bind_group(
|
|||
mut groups: ResMut<MeshBindGroups>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_uniforms: Res<ComponentUniforms<MeshUniform>>,
|
||||
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
|
||||
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
|
||||
weights_uniform: Res<MorphUniform>,
|
||||
) {
|
||||
groups.reset();
|
||||
let layouts = &mesh_pipeline.mesh_layouts;
|
||||
let Some(model) = mesh_uniforms.buffer() else {
|
||||
let Some(model) = mesh_uniforms.binding() else {
|
||||
return;
|
||||
};
|
||||
groups.model_only = Some(layouts.model_only(&render_device, model));
|
||||
groups.model_only = Some(layouts.model_only(&render_device, &model));
|
||||
|
||||
let skin = skinned_mesh_uniform.buffer.buffer();
|
||||
if let Some(skin) = skin {
|
||||
groups.skinned = Some(layouts.skinned(&render_device, model, skin));
|
||||
groups.skinned = Some(layouts.skinned(&render_device, &model, skin));
|
||||
}
|
||||
|
||||
if let Some(weights) = weights_uniform.buffer.buffer() {
|
||||
for (id, gpu_mesh) in meshes.iter() {
|
||||
if let Some(targets) = gpu_mesh.morph_targets.as_ref() {
|
||||
let group = if let Some(skin) = skin.filter(|_| is_skinned(&gpu_mesh.layout)) {
|
||||
layouts.morphed_skinned(&render_device, model, skin, weights, targets)
|
||||
layouts.morphed_skinned(&render_device, &model, skin, weights, targets)
|
||||
} else {
|
||||
layouts.morphed(&render_device, model, weights, targets)
|
||||
layouts.morphed(&render_device, &model, weights, targets)
|
||||
};
|
||||
groups.morph_targets.insert(id.id(), group);
|
||||
}
|
||||
|
@ -1198,7 +1214,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
|||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = (
|
||||
Read<Handle<Mesh>>,
|
||||
Read<DynamicUniformIndex<MeshUniform>>,
|
||||
Read<GpuArrayBufferIndex<MeshUniform>>,
|
||||
Option<Read<SkinnedMeshJoints>>,
|
||||
Option<Read<MorphIndex>>,
|
||||
);
|
||||
|
@ -1207,7 +1223,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
|||
fn render<'w>(
|
||||
_item: &P,
|
||||
_view: (),
|
||||
(mesh, mesh_index, skin_index, morph_index): ROQueryItem<Self::ItemWorldQuery>,
|
||||
(mesh, batch_indices, skin_index, morph_index): ROQueryItem<Self::ItemWorldQuery>,
|
||||
bind_groups: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
|
@ -1223,14 +1239,23 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
|||
);
|
||||
return RenderCommandResult::Failure;
|
||||
};
|
||||
let mut set_bind_group = |indices: &[u32]| pass.set_bind_group(I, bind_group, indices);
|
||||
let mesh_index = mesh_index.index();
|
||||
match (skin_index, morph_index) {
|
||||
(None, None) => set_bind_group(&[mesh_index]),
|
||||
(Some(skin), None) => set_bind_group(&[mesh_index, skin.index]),
|
||||
(None, Some(morph)) => set_bind_group(&[mesh_index, morph.index]),
|
||||
(Some(skin), Some(morph)) => set_bind_group(&[mesh_index, skin.index, morph.index]),
|
||||
};
|
||||
|
||||
let mut dynamic_offsets: [u32; 3] = Default::default();
|
||||
let mut index_count = 0;
|
||||
if let Some(mesh_index) = batch_indices.dynamic_offset {
|
||||
dynamic_offsets[index_count] = mesh_index;
|
||||
index_count += 1;
|
||||
}
|
||||
if let Some(skin_index) = skin_index {
|
||||
dynamic_offsets[index_count] = skin_index.index;
|
||||
index_count += 1;
|
||||
}
|
||||
if let Some(morph_index) = morph_index {
|
||||
dynamic_offsets[index_count] = morph_index.index;
|
||||
index_count += 1;
|
||||
}
|
||||
pass.set_bind_group(I, bind_group, &dynamic_offsets[0..index_count]);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
@ -1239,12 +1264,12 @@ pub struct DrawMesh;
|
|||
impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
|
||||
type Param = SRes<RenderAssets<Mesh>>;
|
||||
type ViewWorldQuery = ();
|
||||
type ItemWorldQuery = Read<Handle<Mesh>>;
|
||||
type ItemWorldQuery = (Read<GpuArrayBufferIndex<MeshUniform>>, Read<Handle<Mesh>>);
|
||||
#[inline]
|
||||
fn render<'w>(
|
||||
_item: &P,
|
||||
_view: (),
|
||||
mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>,
|
||||
(batch_indices, mesh_handle): ROQueryItem<'_, Self::ItemWorldQuery>,
|
||||
meshes: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
|
@ -1257,10 +1282,13 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
|
|||
count,
|
||||
} => {
|
||||
pass.set_index_buffer(buffer.slice(..), 0, *index_format);
|
||||
pass.draw_indexed(0..*count, 0, 0..1);
|
||||
pass.draw_indexed(0..*count, 0, batch_indices.index..batch_indices.index + 1);
|
||||
}
|
||||
GpuBufferInfo::NonIndexed => {
|
||||
pass.draw(0..gpu_mesh.vertex_count, 0..1);
|
||||
pass.draw(
|
||||
0..gpu_mesh.vertex_count,
|
||||
batch_indices.index..batch_indices.index + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
RenderCommandResult::Success
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
#ifdef VERTEX_POSITIONS
|
||||
@location(0) position: vec3<f32>,
|
||||
#endif
|
||||
|
@ -63,14 +64,21 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput {
|
|||
#ifdef SKINNED
|
||||
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||
#else
|
||||
var model = mesh.model;
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
var model = mesh[vertex_no_morph.instance_index].model;
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_NORMALS
|
||||
#ifdef SKINNED
|
||||
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
||||
#else
|
||||
out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal);
|
||||
out.world_normal = mesh_functions::mesh_normal_local_to_world(
|
||||
vertex.normal,
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
vertex_no_morph.instance_index
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -84,13 +92,25 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput {
|
|||
#endif
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent);
|
||||
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
|
||||
model,
|
||||
vertex.tangent,
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
vertex_no_morph.instance_index
|
||||
);
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_COLORS
|
||||
out.color = vertex.color;
|
||||
#endif
|
||||
|
||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416
|
||||
out.instance_index = vertex_no_morph.instance_index;
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
use bevy_render::{
|
||||
mesh::morph::MAX_MORPH_WEIGHTS,
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, Buffer,
|
||||
TextureView,
|
||||
BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||
BindingResource, Buffer, TextureView,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
};
|
||||
|
@ -17,9 +17,12 @@ mod layout_entry {
|
|||
use super::MORPH_BUFFER_SIZE;
|
||||
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
||||
use crate::MeshUniform;
|
||||
use bevy_render::render_resource::{
|
||||
BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, ShaderStages, ShaderType,
|
||||
TextureSampleType, TextureViewDimension,
|
||||
use bevy_render::{
|
||||
render_resource::{
|
||||
BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, GpuArrayBuffer,
|
||||
ShaderStages, TextureSampleType, TextureViewDimension,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
};
|
||||
|
||||
fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry {
|
||||
|
@ -34,9 +37,12 @@ mod layout_entry {
|
|||
},
|
||||
}
|
||||
}
|
||||
pub(super) fn model(binding: u32) -> BindGroupLayoutEntry {
|
||||
let size = MeshUniform::min_size().get();
|
||||
buffer(binding, size, ShaderStages::VERTEX | ShaderStages::FRAGMENT)
|
||||
pub(super) fn model(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry {
|
||||
GpuArrayBuffer::<MeshUniform>::binding_layout(
|
||||
binding,
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
render_device,
|
||||
)
|
||||
}
|
||||
pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry {
|
||||
buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX)
|
||||
|
@ -62,9 +68,8 @@ mod layout_entry {
|
|||
mod entry {
|
||||
use super::MORPH_BUFFER_SIZE;
|
||||
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
||||
use crate::MeshUniform;
|
||||
use bevy_render::render_resource::{
|
||||
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, ShaderType, TextureView,
|
||||
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView,
|
||||
};
|
||||
|
||||
fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry {
|
||||
|
@ -77,8 +82,8 @@ mod entry {
|
|||
}),
|
||||
}
|
||||
}
|
||||
pub(super) fn model(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
||||
entry(binding, MeshUniform::min_size().get(), buffer)
|
||||
pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry {
|
||||
BindGroupEntry { binding, resource }
|
||||
}
|
||||
pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
||||
entry(binding, JOINT_BUFFER_SIZE as u64, buffer)
|
||||
|
@ -132,20 +137,23 @@ impl MeshLayouts {
|
|||
|
||||
fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[layout_entry::model(0)],
|
||||
entries: &[layout_entry::model(render_device, 0)],
|
||||
label: Some("mesh_layout"),
|
||||
})
|
||||
}
|
||||
fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[layout_entry::model(0), layout_entry::skinning(1)],
|
||||
entries: &[
|
||||
layout_entry::model(render_device, 0),
|
||||
layout_entry::skinning(1),
|
||||
],
|
||||
label: Some("skinned_mesh_layout"),
|
||||
})
|
||||
}
|
||||
fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
layout_entry::model(0),
|
||||
layout_entry::model(render_device, 0),
|
||||
layout_entry::weights(2),
|
||||
layout_entry::targets(3),
|
||||
],
|
||||
|
@ -155,7 +163,7 @@ impl MeshLayouts {
|
|||
fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
layout_entry::model(0),
|
||||
layout_entry::model(render_device, 0),
|
||||
layout_entry::skinning(1),
|
||||
layout_entry::weights(2),
|
||||
layout_entry::targets(3),
|
||||
|
@ -166,9 +174,9 @@ impl MeshLayouts {
|
|||
|
||||
// ---------- BindGroup methods ----------
|
||||
|
||||
pub fn model_only(&self, render_device: &RenderDevice, model: &Buffer) -> BindGroup {
|
||||
pub fn model_only(&self, render_device: &RenderDevice, model: &BindingResource) -> BindGroup {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[entry::model(0, model)],
|
||||
entries: &[entry::model(0, model.clone())],
|
||||
layout: &self.model_only,
|
||||
label: Some("model_only_mesh_bind_group"),
|
||||
})
|
||||
|
@ -176,11 +184,11 @@ impl MeshLayouts {
|
|||
pub fn skinned(
|
||||
&self,
|
||||
render_device: &RenderDevice,
|
||||
model: &Buffer,
|
||||
model: &BindingResource,
|
||||
skin: &Buffer,
|
||||
) -> BindGroup {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[entry::model(0, model), entry::skinning(1, skin)],
|
||||
entries: &[entry::model(0, model.clone()), entry::skinning(1, skin)],
|
||||
layout: &self.skinned,
|
||||
label: Some("skinned_mesh_bind_group"),
|
||||
})
|
||||
|
@ -188,13 +196,13 @@ impl MeshLayouts {
|
|||
pub fn morphed(
|
||||
&self,
|
||||
render_device: &RenderDevice,
|
||||
model: &Buffer,
|
||||
model: &BindingResource,
|
||||
weights: &Buffer,
|
||||
targets: &TextureView,
|
||||
) -> BindGroup {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
entry::model(0, model),
|
||||
entry::model(0, model.clone()),
|
||||
entry::weights(2, weights),
|
||||
entry::targets(3, targets),
|
||||
],
|
||||
|
@ -205,14 +213,14 @@ impl MeshLayouts {
|
|||
pub fn morphed_skinned(
|
||||
&self,
|
||||
render_device: &RenderDevice,
|
||||
model: &Buffer,
|
||||
model: &BindingResource,
|
||||
skin: &Buffer,
|
||||
weights: &Buffer,
|
||||
targets: &TextureView,
|
||||
) -> BindGroup {
|
||||
render_device.create_bind_group(&BindGroupDescriptor {
|
||||
entries: &[
|
||||
entry::model(0, model),
|
||||
entry::model(0, model.clone()),
|
||||
entry::skinning(1, skin),
|
||||
entry::weights(2, weights),
|
||||
entry::targets(3, targets),
|
||||
|
|
|
@ -4,12 +4,22 @@
|
|||
|
||||
#ifdef MESH_BINDGROUP_1
|
||||
|
||||
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
@group(1) @binding(0)
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
|
||||
#else
|
||||
@group(1) @binding(0)
|
||||
var<storage> mesh: array<Mesh>;
|
||||
#endif // PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
|
||||
#else // MESH_BINDGROUP_1
|
||||
|
||||
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
@group(2) @binding(0)
|
||||
var<uniform> mesh: Mesh;
|
||||
var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
|
||||
#else
|
||||
@group(2) @binding(0)
|
||||
var<storage> mesh: array<Mesh>;
|
||||
#endif // PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
|
||||
#endif
|
||||
#endif // MESH_BINDGROUP_1
|
||||
|
|
|
@ -20,7 +20,7 @@ fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -
|
|||
return mesh_position_world_to_clip(world_position);
|
||||
}
|
||||
|
||||
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>) -> vec3<f32> {
|
||||
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>, instance_index: u32) -> vec3<f32> {
|
||||
// NOTE: The mikktspace method of normal mapping requires that the world normal is
|
||||
// re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents
|
||||
// and normal maps so that the exact inverse process is applied when shading. Blender, Unity,
|
||||
|
@ -29,23 +29,23 @@ fn mesh_normal_local_to_world(vertex_normal: vec3<f32>) -> vec3<f32> {
|
|||
// http://www.mikktspace.com/
|
||||
return normalize(
|
||||
mat3x3<f32>(
|
||||
mesh.inverse_transpose_model[0].xyz,
|
||||
mesh.inverse_transpose_model[1].xyz,
|
||||
mesh.inverse_transpose_model[2].xyz
|
||||
mesh[instance_index].inverse_transpose_model[0].xyz,
|
||||
mesh[instance_index].inverse_transpose_model[1].xyz,
|
||||
mesh[instance_index].inverse_transpose_model[2].xyz
|
||||
) * vertex_normal
|
||||
);
|
||||
}
|
||||
|
||||
// Calculates the sign of the determinant of the 3x3 model matrix based on a
|
||||
// mesh flag
|
||||
fn sign_determinant_model_3x3m() -> f32 {
|
||||
fn sign_determinant_model_3x3m(instance_index: u32) -> f32 {
|
||||
// bool(u32) is false if 0u else true
|
||||
// f32(bool) is 1.0 if true else 0.0
|
||||
// * 2.0 - 1.0 remaps 0.0 or 1.0 to -1.0 or 1.0 respectively
|
||||
return f32(bool(mesh.flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0;
|
||||
return f32(bool(mesh[instance_index].flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0;
|
||||
}
|
||||
|
||||
fn mesh_tangent_local_to_world(model: mat4x4<f32>, vertex_tangent: vec4<f32>) -> vec4<f32> {
|
||||
fn mesh_tangent_local_to_world(model: mat4x4<f32>, vertex_tangent: vec4<f32>, instance_index: u32) -> vec4<f32> {
|
||||
// NOTE: The mikktspace method of normal mapping requires that the world tangent is
|
||||
// re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents
|
||||
// and normal maps so that the exact inverse process is applied when shading. Blender, Unity,
|
||||
|
@ -62,6 +62,6 @@ fn mesh_tangent_local_to_world(model: mat4x4<f32>, vertex_tangent: vec4<f32>) ->
|
|||
),
|
||||
// NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for
|
||||
// situations such as negative scaling.
|
||||
vertex_tangent.w * sign_determinant_model_3x3m()
|
||||
vertex_tangent.w * sign_determinant_model_3x3m(instance_index)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#define_import_path bevy_pbr::mesh_vertex_output
|
||||
|
||||
struct MeshVertexOutput {
|
||||
// this is `clip position` when the struct is used as a vertex stage output
|
||||
// this is `clip position` when the struct is used as a vertex stage output
|
||||
// and `frag coord` when used as a fragment stage input
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) world_position: vec4<f32>,
|
||||
|
@ -15,4 +15,7 @@ struct MeshVertexOutput {
|
|||
#ifdef VERTEX_COLORS
|
||||
@location(4) color: vec4<f32>,
|
||||
#endif
|
||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||
@location(5) instance_index: u32,
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ fn fragment(
|
|||
pbr_input.V = V;
|
||||
pbr_input.occlusion = occlusion;
|
||||
|
||||
pbr_input.flags = mesh.flags;
|
||||
pbr_input.flags = mesh[in.instance_index].flags;
|
||||
|
||||
output_color = pbr_functions::pbr(pbr_input);
|
||||
} else {
|
||||
|
|
|
@ -223,7 +223,7 @@ fn pbr(
|
|||
for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) {
|
||||
let light_id = clustering::get_light_id(i);
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal);
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ fn pbr(
|
|||
let light_id = clustering::get_light_id(i);
|
||||
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal);
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ fn pbr(
|
|||
let n_directional_lights = view_bindings::lights.n_directional_lights;
|
||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#endif
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
#ifdef SKINNED
|
||||
@location(4) joint_indexes: vec4<u32>,
|
||||
|
@ -22,7 +23,7 @@ fn vertex(vertex: Vertex) -> VertexOutput {
|
|||
#ifdef SKINNED
|
||||
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
|
||||
#else
|
||||
let model = mesh.model;
|
||||
let model = mesh[vertex.instance_index].model;
|
||||
#endif
|
||||
|
||||
var out: VertexOutput;
|
||||
|
|
|
@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
|||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::{Reflect, TypeUuid};
|
||||
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
||||
use bevy_render::render_resource::GpuArrayBufferIndex;
|
||||
use bevy_render::Render;
|
||||
use bevy_render::{
|
||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||
|
@ -117,8 +118,21 @@ fn queue_wireframes(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
msaa: Res<Msaa>,
|
||||
mut material_meshes: ParamSet<(
|
||||
Query<(Entity, &Handle<Mesh>, &MeshUniform)>,
|
||||
Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>,
|
||||
Query<(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshUniform,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)>,
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshUniform,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
),
|
||||
With<Wireframe>,
|
||||
>,
|
||||
)>,
|
||||
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
|
@ -128,32 +142,35 @@ fn queue_wireframes(
|
|||
let rangefinder = view.rangefinder3d();
|
||||
|
||||
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
let add_render_phase =
|
||||
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key = view_key
|
||||
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&wireframe_pipeline,
|
||||
key,
|
||||
&mesh.layout,
|
||||
);
|
||||
let pipeline_id = match pipeline_id {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline: pipeline_id,
|
||||
draw_function: draw_custom,
|
||||
distance: rangefinder.distance(&mesh_uniform.transform),
|
||||
});
|
||||
}
|
||||
};
|
||||
let add_render_phase = |(entity, mesh_handle, mesh_uniform, batch_indices): (
|
||||
Entity,
|
||||
&Handle<Mesh>,
|
||||
&MeshUniform,
|
||||
&GpuArrayBufferIndex<MeshUniform>,
|
||||
)| {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key =
|
||||
view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline_id =
|
||||
pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout);
|
||||
let pipeline_id = match pipeline_id {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline: pipeline_id,
|
||||
draw_function: draw_custom,
|
||||
distance: rangefinder.distance(&mesh_uniform.transform),
|
||||
per_object_binding_dynamic_offset: batch_indices
|
||||
.dynamic_offset
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if wireframe_config.global {
|
||||
let query = material_meshes.p0();
|
||||
|
|
|
@ -18,14 +18,18 @@ pub struct GpuComponentArrayBufferPlugin<C: Component + GpuArrayBufferable>(Phan
|
|||
impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin<C> {
|
||||
fn build(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.insert_resource(GpuArrayBuffer::<C>::new(
|
||||
render_app.world.resource::<RenderDevice>(),
|
||||
))
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
|
||||
);
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.insert_resource(GpuArrayBuffer::<C>::new(
|
||||
render_app.world.resource::<RenderDevice>(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue