mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +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;
|
var<uniform> material: CustomMaterial;
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) blend_color: vec4<f32>,
|
@location(1) blend_color: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
@ -21,8 +22,8 @@ struct VertexOutput {
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_position = mesh_position_local_to_clip(
|
out.clip_position = mesh_position_local_to_clip(
|
||||||
mesh.model,
|
mesh[vertex.instance_index].model,
|
||||||
vec4<f32>(vertex.position, 1.0)
|
vec4<f32>(vertex.position, 1.0),
|
||||||
);
|
);
|
||||||
out.blend_color = vertex.blend_color;
|
out.blend_color = vertex.blend_color;
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -19,8 +19,12 @@ struct VertexOutput {
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz;
|
let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz;
|
||||||
var out: VertexOutput;
|
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(
|
out.clip_position = mesh_position_local_to_clip(
|
||||||
mesh.model,
|
mesh[0].model,
|
||||||
vec4<f32>(position, 1.0)
|
vec4<f32>(position, 1.0)
|
||||||
);
|
);
|
||||||
out.color = vertex.i_color;
|
out.color = vertex.i_color;
|
||||||
|
|
|
@ -136,14 +136,18 @@ impl Plugin for Core3dPlugin {
|
||||||
|
|
||||||
pub struct Opaque3d {
|
pub struct Opaque3d {
|
||||||
pub distance: f32,
|
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 pipeline: CachedRenderPipelineId,
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for Opaque3d {
|
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.
|
// 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]
|
#[inline]
|
||||||
fn entity(&self) -> Entity {
|
fn entity(&self) -> Entity {
|
||||||
|
@ -152,7 +156,10 @@ impl PhaseItem for Opaque3d {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
Reverse(FloatOrd(self.distance))
|
(
|
||||||
|
self.per_object_binding_dynamic_offset,
|
||||||
|
Reverse(FloatOrd(self.distance)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -163,7 +170,9 @@ impl PhaseItem for Opaque3d {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort(items: &mut [Self]) {
|
fn sort(items: &mut [Self]) {
|
||||||
// Key negated to match reversed SortKey ordering
|
// 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 struct AlphaMask3d {
|
||||||
pub distance: f32,
|
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 pipeline: CachedRenderPipelineId,
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for AlphaMask3d {
|
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.
|
// 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]
|
#[inline]
|
||||||
fn entity(&self) -> Entity {
|
fn entity(&self) -> Entity {
|
||||||
|
@ -192,7 +205,10 @@ impl PhaseItem for AlphaMask3d {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
Reverse(FloatOrd(self.distance))
|
(
|
||||||
|
self.per_object_binding_dynamic_offset,
|
||||||
|
Reverse(FloatOrd(self.distance)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -203,7 +219,9 @@ impl PhaseItem for AlphaMask3d {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort(items: &mut [Self]) {
|
fn sort(items: &mut [Self]) {
|
||||||
// Key negated to match reversed SortKey ordering
|
// 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.
|
/// Used to render all 3D meshes with materials that have no transparency.
|
||||||
pub struct Opaque3dPrepass {
|
pub struct Opaque3dPrepass {
|
||||||
pub distance: f32,
|
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 entity: Entity,
|
||||||
pub pipeline_id: CachedRenderPipelineId,
|
pub pipeline_id: CachedRenderPipelineId,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for Opaque3dPrepass {
|
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.
|
// 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]
|
#[inline]
|
||||||
fn entity(&self) -> Entity {
|
fn entity(&self) -> Entity {
|
||||||
|
@ -96,7 +100,10 @@ impl PhaseItem for Opaque3dPrepass {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
Reverse(FloatOrd(self.distance))
|
(
|
||||||
|
self.per_object_binding_dynamic_offset,
|
||||||
|
Reverse(FloatOrd(self.distance)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -107,7 +114,9 @@ impl PhaseItem for Opaque3dPrepass {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort(items: &mut [Self]) {
|
fn sort(items: &mut [Self]) {
|
||||||
// Key negated to match reversed SortKey ordering
|
// 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.
|
/// Used to render all meshes with a material with an alpha mask.
|
||||||
pub struct AlphaMask3dPrepass {
|
pub struct AlphaMask3dPrepass {
|
||||||
pub distance: f32,
|
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 entity: Entity,
|
||||||
pub pipeline_id: CachedRenderPipelineId,
|
pub pipeline_id: CachedRenderPipelineId,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for AlphaMask3dPrepass {
|
impl PhaseItem for AlphaMask3dPrepass {
|
||||||
// NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort.
|
// NOTE: (dynamic offset, -distance)
|
||||||
type SortKey = Reverse<FloatOrd>;
|
// NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort.
|
||||||
|
type SortKey = (u32, Reverse<FloatOrd>);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn entity(&self) -> Entity {
|
fn entity(&self) -> Entity {
|
||||||
|
@ -141,7 +154,10 @@ impl PhaseItem for AlphaMask3dPrepass {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
Reverse(FloatOrd(self.distance))
|
(
|
||||||
|
self.per_object_binding_dynamic_offset,
|
||||||
|
Reverse(FloatOrd(self.distance)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -152,7 +168,9 @@ impl PhaseItem for AlphaMask3dPrepass {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort(items: &mut [Self]) {
|
fn sort(items: &mut [Self]) {
|
||||||
// Key negated to match reversed SortKey ordering
|
// 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"
|
bitflags = "2.3"
|
||||||
# direct dependency required for derive macro
|
# direct dependency required for derive macro
|
||||||
bytemuck = { version = "1", features = ["derive"] }
|
bytemuck = { version = "1", features = ["derive"] }
|
||||||
radsort = "0.1"
|
|
||||||
naga_oil = "0.8"
|
naga_oil = "0.8"
|
||||||
|
radsort = "0.1"
|
||||||
|
smallvec = "1.6"
|
||||||
|
|
|
@ -30,9 +30,9 @@ use bevy_render::{
|
||||||
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
RenderPhase, SetItemPipeline, TrackedRenderPass,
|
||||||
},
|
},
|
||||||
render_resource::{
|
render_resource::{
|
||||||
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource,
|
AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, GpuArrayBufferIndex,
|
||||||
PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline,
|
OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
texture::FallbackImage,
|
texture::FallbackImage,
|
||||||
|
@ -379,7 +379,12 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
msaa: Res<Msaa>,
|
msaa: Res<Msaa>,
|
||||||
render_meshes: Res<RenderAssets<Mesh>>,
|
render_meshes: Res<RenderAssets<Mesh>>,
|
||||||
render_materials: Res<RenderMaterials<M>>,
|
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>>,
|
images: Res<RenderAssets<Image>>,
|
||||||
mut views: Query<(
|
mut views: Query<(
|
||||||
&ExtractedView,
|
&ExtractedView,
|
||||||
|
@ -463,7 +468,7 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
|
|
||||||
let rangefinder = view.rangefinder3d();
|
let rangefinder = view.rangefinder3d();
|
||||||
for visible_entity in &visible_entities.entities {
|
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)
|
material_meshes.get(*visible_entity)
|
||||||
{
|
{
|
||||||
if let (Some(mesh), Some(material)) = (
|
if let (Some(mesh), Some(material)) = (
|
||||||
|
@ -520,6 +525,9 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
draw_function: draw_opaque_pbr,
|
draw_function: draw_opaque_pbr,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
|
per_object_binding_dynamic_offset: batch_indices
|
||||||
|
.dynamic_offset
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AlphaMode::Mask(_) => {
|
AlphaMode::Mask(_) => {
|
||||||
|
@ -528,6 +536,9 @@ pub fn queue_material_meshes<M: Material>(
|
||||||
draw_function: draw_alpha_mask_pbr,
|
draw_function: draw_alpha_mask_pbr,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
|
per_object_binding_dynamic_offset: batch_indices
|
||||||
|
.dynamic_offset
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AlphaMode::Blend
|
AlphaMode::Blend
|
||||||
|
|
|
@ -30,9 +30,9 @@ use bevy_render::{
|
||||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||||
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
|
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
|
||||||
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
|
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
|
||||||
DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache,
|
DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState,
|
||||||
PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages,
|
PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||||
ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
|
||||||
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
|
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
|
||||||
TextureViewDimension, VertexState,
|
TextureViewDimension, VertexState,
|
||||||
},
|
},
|
||||||
|
@ -751,7 +751,12 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
msaa: Res<Msaa>,
|
msaa: Res<Msaa>,
|
||||||
render_meshes: Res<RenderAssets<Mesh>>,
|
render_meshes: Res<RenderAssets<Mesh>>,
|
||||||
render_materials: Res<RenderMaterials<M>>,
|
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<(
|
mut views: Query<(
|
||||||
&ExtractedView,
|
&ExtractedView,
|
||||||
&VisibleEntities,
|
&VisibleEntities,
|
||||||
|
@ -796,7 +801,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
let rangefinder = view.rangefinder3d();
|
let rangefinder = view.rangefinder3d();
|
||||||
|
|
||||||
for visible_entity in &visible_entities.entities {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -848,6 +853,9 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
draw_function: opaque_draw_prepass,
|
draw_function: opaque_draw_prepass,
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
|
per_object_binding_dynamic_offset: batch_indices
|
||||||
|
.dynamic_offset
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AlphaMode::Mask(_) => {
|
AlphaMode::Mask(_) => {
|
||||||
|
@ -856,6 +864,9 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||||
draw_function: alpha_mask_draw_prepass,
|
draw_function: alpha_mask_draw_prepass,
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
distance,
|
distance,
|
||||||
|
per_object_binding_dynamic_offset: batch_indices
|
||||||
|
.dynamic_offset
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AlphaMode::Blend
|
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
|
// 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.
|
// pass them to custom prepass shaders like pbr_prepass.wgsl.
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
|
|
||||||
#ifdef VERTEX_UVS
|
#ifdef VERTEX_UVS
|
||||||
|
@ -88,7 +89,9 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||||
#else // SKINNED
|
#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
|
#endif // SKINNED
|
||||||
|
|
||||||
out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0));
|
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
|
#ifdef SKINNED
|
||||||
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
||||||
#else // SKINNED
|
#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
|
#endif // SKINNED
|
||||||
|
|
||||||
#ifdef VERTEX_TANGENTS
|
#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 // VERTEX_TANGENTS
|
||||||
#endif // NORMAL_PREPASS
|
#endif // NORMAL_PREPASS
|
||||||
|
|
||||||
#ifdef MOTION_VECTOR_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.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
|
#endif // MOTION_VECTOR_PREPASS
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -15,5 +15,4 @@ var<uniform> previous_view_proj: mat4x4<f32>;
|
||||||
|
|
||||||
// Material bindings will be in @group(1)
|
// Material bindings will be in @group(1)
|
||||||
|
|
||||||
@group(2) @binding(0)
|
#import bevy_pbr::mesh_bindings mesh
|
||||||
var<uniform> mesh: bevy_pbr::mesh_types::Mesh;
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
||||||
CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities,
|
CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities,
|
||||||
DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight,
|
DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight,
|
||||||
GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey,
|
GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey,
|
||||||
NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight,
|
MeshUniform, NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline,
|
||||||
VisiblePointLights,
|
RenderMaterials, SpotLight, VisiblePointLights,
|
||||||
};
|
};
|
||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_core_pipeline::core_3d::Transparent3d;
|
use bevy_core_pipeline::core_3d::Transparent3d;
|
||||||
|
@ -1556,7 +1556,10 @@ pub fn prepare_clusters(
|
||||||
pub fn queue_shadows<M: Material>(
|
pub fn queue_shadows<M: Material>(
|
||||||
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
|
||||||
prepass_pipeline: Res<PrepassPipeline<M>>,
|
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_meshes: Res<RenderAssets<Mesh>>,
|
||||||
render_materials: Res<RenderMaterials<M>>,
|
render_materials: Res<RenderMaterials<M>>,
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<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
|
// NOTE: Lights with shadow mapping disabled will have no visible entities
|
||||||
// so no meshes will be queued
|
// so no meshes will be queued
|
||||||
for entity in visible_entities.iter().copied() {
|
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)) = (
|
if let (Some(mesh), Some(material)) = (
|
||||||
render_meshes.get(mesh_handle),
|
render_meshes.get(mesh_handle),
|
||||||
render_materials.get(material_handle),
|
render_materials.get(material_handle),
|
||||||
|
@ -1647,7 +1652,10 @@ pub fn queue_shadows<M: Material>(
|
||||||
draw_function: draw_shadow_mesh,
|
draw_function: draw_shadow_mesh,
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
entity,
|
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 struct Shadow {
|
||||||
pub distance: f32,
|
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 entity: Entity,
|
||||||
pub pipeline: CachedRenderPipelineId,
|
pub pipeline: CachedRenderPipelineId,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for Shadow {
|
impl PhaseItem for Shadow {
|
||||||
type SortKey = usize;
|
type SortKey = (usize, u32);
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn entity(&self) -> Entity {
|
fn entity(&self) -> Entity {
|
||||||
|
@ -1673,7 +1684,7 @@ impl PhaseItem for Shadow {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
self.pipeline.id()
|
(self.pipeline.id(), self.per_object_binding_dynamic_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1686,7 +1697,7 @@ impl PhaseItem for Shadow {
|
||||||
// The shadow phase is sorted by pipeline id for performance reasons.
|
// The shadow phase is sorted by pipeline id for performance reasons.
|
||||||
// Grouping all draw commands using the same pipeline together performs
|
// Grouping all draw commands using the same pipeline together performs
|
||||||
// better than rebinding everything at a high rate.
|
// 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_math::{Mat3A, Mat4, Vec2};
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
|
||||||
globals::{GlobalsBuffer, GlobalsUniform},
|
globals::{GlobalsBuffer, GlobalsUniform},
|
||||||
|
gpu_component_array_buffer::GpuComponentArrayBufferPlugin,
|
||||||
mesh::{
|
mesh::{
|
||||||
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||||
GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout,
|
GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout,
|
||||||
|
@ -105,12 +105,6 @@ impl Plugin for MeshRenderPlugin {
|
||||||
Shader::from_wgsl
|
Shader::from_wgsl
|
||||||
);
|
);
|
||||||
load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", 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!(
|
load_internal_asset!(
|
||||||
app,
|
app,
|
||||||
MESH_FUNCTIONS_HANDLE,
|
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, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl);
|
||||||
load_internal_asset!(app, MORPH_HANDLE, "morph.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) {
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
|
@ -145,9 +139,30 @@ impl Plugin for MeshRenderPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut bevy_app::App) {
|
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 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>();
|
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
|
// This dummy white texture is to be used in place of optional StandardMaterial textures
|
||||||
pub dummy_white_gpu_image: GpuImage,
|
pub dummy_white_gpu_image: GpuImage,
|
||||||
pub clustered_forward_buffer_binding_type: BufferBindingType,
|
pub clustered_forward_buffer_binding_type: BufferBindingType,
|
||||||
|
|
||||||
pub mesh_layouts: MeshLayouts,
|
pub mesh_layouts: MeshLayouts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,6 +723,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
let mut shader_defs = Vec::new();
|
let mut shader_defs = Vec::new();
|
||||||
let mut vertex_attributes = Vec::new();
|
let mut vertex_attributes = Vec::new();
|
||||||
|
|
||||||
|
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
|
||||||
|
|
||||||
if layout.contains(Mesh::ATTRIBUTE_POSITION) {
|
if layout.contains(Mesh::ATTRIBUTE_POSITION) {
|
||||||
shader_defs.push("VERTEX_POSITIONS".into());
|
shader_defs.push("VERTEX_POSITIONS".into());
|
||||||
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
|
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
|
||||||
|
@ -932,29 +948,29 @@ pub fn queue_mesh_bind_group(
|
||||||
mut groups: ResMut<MeshBindGroups>,
|
mut groups: ResMut<MeshBindGroups>,
|
||||||
mesh_pipeline: Res<MeshPipeline>,
|
mesh_pipeline: Res<MeshPipeline>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
mesh_uniforms: Res<ComponentUniforms<MeshUniform>>,
|
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
|
||||||
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
|
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
|
||||||
weights_uniform: Res<MorphUniform>,
|
weights_uniform: Res<MorphUniform>,
|
||||||
) {
|
) {
|
||||||
groups.reset();
|
groups.reset();
|
||||||
let layouts = &mesh_pipeline.mesh_layouts;
|
let layouts = &mesh_pipeline.mesh_layouts;
|
||||||
let Some(model) = mesh_uniforms.buffer() else {
|
let Some(model) = mesh_uniforms.binding() else {
|
||||||
return;
|
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();
|
let skin = skinned_mesh_uniform.buffer.buffer();
|
||||||
if let Some(skin) = skin {
|
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() {
|
if let Some(weights) = weights_uniform.buffer.buffer() {
|
||||||
for (id, gpu_mesh) in meshes.iter() {
|
for (id, gpu_mesh) in meshes.iter() {
|
||||||
if let Some(targets) = gpu_mesh.morph_targets.as_ref() {
|
if let Some(targets) = gpu_mesh.morph_targets.as_ref() {
|
||||||
let group = if let Some(skin) = skin.filter(|_| is_skinned(&gpu_mesh.layout)) {
|
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 {
|
} else {
|
||||||
layouts.morphed(&render_device, model, weights, targets)
|
layouts.morphed(&render_device, &model, weights, targets)
|
||||||
};
|
};
|
||||||
groups.morph_targets.insert(id.id(), group);
|
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 ViewWorldQuery = ();
|
||||||
type ItemWorldQuery = (
|
type ItemWorldQuery = (
|
||||||
Read<Handle<Mesh>>,
|
Read<Handle<Mesh>>,
|
||||||
Read<DynamicUniformIndex<MeshUniform>>,
|
Read<GpuArrayBufferIndex<MeshUniform>>,
|
||||||
Option<Read<SkinnedMeshJoints>>,
|
Option<Read<SkinnedMeshJoints>>,
|
||||||
Option<Read<MorphIndex>>,
|
Option<Read<MorphIndex>>,
|
||||||
);
|
);
|
||||||
|
@ -1207,7 +1223,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &P,
|
_item: &P,
|
||||||
_view: (),
|
_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>,
|
bind_groups: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
|
@ -1223,14 +1239,23 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
|
||||||
);
|
);
|
||||||
return RenderCommandResult::Failure;
|
return RenderCommandResult::Failure;
|
||||||
};
|
};
|
||||||
let mut set_bind_group = |indices: &[u32]| pass.set_bind_group(I, bind_group, indices);
|
|
||||||
let mesh_index = mesh_index.index();
|
let mut dynamic_offsets: [u32; 3] = Default::default();
|
||||||
match (skin_index, morph_index) {
|
let mut index_count = 0;
|
||||||
(None, None) => set_bind_group(&[mesh_index]),
|
if let Some(mesh_index) = batch_indices.dynamic_offset {
|
||||||
(Some(skin), None) => set_bind_group(&[mesh_index, skin.index]),
|
dynamic_offsets[index_count] = mesh_index;
|
||||||
(None, Some(morph)) => set_bind_group(&[mesh_index, morph.index]),
|
index_count += 1;
|
||||||
(Some(skin), Some(morph)) => set_bind_group(&[mesh_index, skin.index, morph.index]),
|
}
|
||||||
};
|
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
|
RenderCommandResult::Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1239,12 +1264,12 @@ pub struct DrawMesh;
|
||||||
impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
|
impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
|
||||||
type Param = SRes<RenderAssets<Mesh>>;
|
type Param = SRes<RenderAssets<Mesh>>;
|
||||||
type ViewWorldQuery = ();
|
type ViewWorldQuery = ();
|
||||||
type ItemWorldQuery = Read<Handle<Mesh>>;
|
type ItemWorldQuery = (Read<GpuArrayBufferIndex<MeshUniform>>, Read<Handle<Mesh>>);
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &P,
|
_item: &P,
|
||||||
_view: (),
|
_view: (),
|
||||||
mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>,
|
(batch_indices, mesh_handle): ROQueryItem<'_, Self::ItemWorldQuery>,
|
||||||
meshes: SystemParamItem<'w, '_, Self::Param>,
|
meshes: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
|
@ -1257,10 +1282,13 @@ impl<P: PhaseItem> RenderCommand<P> for DrawMesh {
|
||||||
count,
|
count,
|
||||||
} => {
|
} => {
|
||||||
pass.set_index_buffer(buffer.slice(..), 0, *index_format);
|
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 => {
|
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
|
RenderCommandResult::Success
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
|
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
#ifdef VERTEX_POSITIONS
|
#ifdef VERTEX_POSITIONS
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
@ -63,14 +64,21 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput {
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
||||||
#else
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_NORMALS
|
#ifdef VERTEX_NORMALS
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
||||||
#else
|
#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
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -84,13 +92,25 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_TANGENTS
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
out.color = vertex.color;
|
out.color = vertex.color;
|
||||||
#endif
|
#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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
mesh::morph::MAX_MORPH_WEIGHTS,
|
mesh::morph::MAX_MORPH_WEIGHTS,
|
||||||
render_resource::{
|
render_resource::{
|
||||||
BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, Buffer,
|
BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor,
|
||||||
TextureView,
|
BindingResource, Buffer, TextureView,
|
||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,12 @@ mod layout_entry {
|
||||||
use super::MORPH_BUFFER_SIZE;
|
use super::MORPH_BUFFER_SIZE;
|
||||||
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
||||||
use crate::MeshUniform;
|
use crate::MeshUniform;
|
||||||
use bevy_render::render_resource::{
|
use bevy_render::{
|
||||||
BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, ShaderStages, ShaderType,
|
render_resource::{
|
||||||
TextureSampleType, TextureViewDimension,
|
BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, GpuArrayBuffer,
|
||||||
|
ShaderStages, TextureSampleType, TextureViewDimension,
|
||||||
|
},
|
||||||
|
renderer::RenderDevice,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry {
|
fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry {
|
||||||
|
@ -34,9 +37,12 @@ mod layout_entry {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) fn model(binding: u32) -> BindGroupLayoutEntry {
|
pub(super) fn model(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry {
|
||||||
let size = MeshUniform::min_size().get();
|
GpuArrayBuffer::<MeshUniform>::binding_layout(
|
||||||
buffer(binding, size, ShaderStages::VERTEX | ShaderStages::FRAGMENT)
|
binding,
|
||||||
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
render_device,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry {
|
pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry {
|
||||||
buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX)
|
buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX)
|
||||||
|
@ -62,9 +68,8 @@ mod layout_entry {
|
||||||
mod entry {
|
mod entry {
|
||||||
use super::MORPH_BUFFER_SIZE;
|
use super::MORPH_BUFFER_SIZE;
|
||||||
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
||||||
use crate::MeshUniform;
|
|
||||||
use bevy_render::render_resource::{
|
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 {
|
fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry {
|
||||||
|
@ -77,8 +82,8 @@ mod entry {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) fn model(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry {
|
||||||
entry(binding, MeshUniform::min_size().get(), buffer)
|
BindGroupEntry { binding, resource }
|
||||||
}
|
}
|
||||||
pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
||||||
entry(binding, JOINT_BUFFER_SIZE as u64, buffer)
|
entry(binding, JOINT_BUFFER_SIZE as u64, buffer)
|
||||||
|
@ -132,20 +137,23 @@ impl MeshLayouts {
|
||||||
|
|
||||||
fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[layout_entry::model(0)],
|
entries: &[layout_entry::model(render_device, 0)],
|
||||||
label: Some("mesh_layout"),
|
label: Some("mesh_layout"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
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"),
|
label: Some("skinned_mesh_layout"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
layout_entry::model(0),
|
layout_entry::model(render_device, 0),
|
||||||
layout_entry::weights(2),
|
layout_entry::weights(2),
|
||||||
layout_entry::targets(3),
|
layout_entry::targets(3),
|
||||||
],
|
],
|
||||||
|
@ -155,7 +163,7 @@ impl MeshLayouts {
|
||||||
fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
layout_entry::model(0),
|
layout_entry::model(render_device, 0),
|
||||||
layout_entry::skinning(1),
|
layout_entry::skinning(1),
|
||||||
layout_entry::weights(2),
|
layout_entry::weights(2),
|
||||||
layout_entry::targets(3),
|
layout_entry::targets(3),
|
||||||
|
@ -166,9 +174,9 @@ impl MeshLayouts {
|
||||||
|
|
||||||
// ---------- BindGroup methods ----------
|
// ---------- 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 {
|
render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
entries: &[entry::model(0, model)],
|
entries: &[entry::model(0, model.clone())],
|
||||||
layout: &self.model_only,
|
layout: &self.model_only,
|
||||||
label: Some("model_only_mesh_bind_group"),
|
label: Some("model_only_mesh_bind_group"),
|
||||||
})
|
})
|
||||||
|
@ -176,11 +184,11 @@ impl MeshLayouts {
|
||||||
pub fn skinned(
|
pub fn skinned(
|
||||||
&self,
|
&self,
|
||||||
render_device: &RenderDevice,
|
render_device: &RenderDevice,
|
||||||
model: &Buffer,
|
model: &BindingResource,
|
||||||
skin: &Buffer,
|
skin: &Buffer,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
render_device.create_bind_group(&BindGroupDescriptor {
|
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,
|
layout: &self.skinned,
|
||||||
label: Some("skinned_mesh_bind_group"),
|
label: Some("skinned_mesh_bind_group"),
|
||||||
})
|
})
|
||||||
|
@ -188,13 +196,13 @@ impl MeshLayouts {
|
||||||
pub fn morphed(
|
pub fn morphed(
|
||||||
&self,
|
&self,
|
||||||
render_device: &RenderDevice,
|
render_device: &RenderDevice,
|
||||||
model: &Buffer,
|
model: &BindingResource,
|
||||||
weights: &Buffer,
|
weights: &Buffer,
|
||||||
targets: &TextureView,
|
targets: &TextureView,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
render_device.create_bind_group(&BindGroupDescriptor {
|
render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
entry::model(0, model),
|
entry::model(0, model.clone()),
|
||||||
entry::weights(2, weights),
|
entry::weights(2, weights),
|
||||||
entry::targets(3, targets),
|
entry::targets(3, targets),
|
||||||
],
|
],
|
||||||
|
@ -205,14 +213,14 @@ impl MeshLayouts {
|
||||||
pub fn morphed_skinned(
|
pub fn morphed_skinned(
|
||||||
&self,
|
&self,
|
||||||
render_device: &RenderDevice,
|
render_device: &RenderDevice,
|
||||||
model: &Buffer,
|
model: &BindingResource,
|
||||||
skin: &Buffer,
|
skin: &Buffer,
|
||||||
weights: &Buffer,
|
weights: &Buffer,
|
||||||
targets: &TextureView,
|
targets: &TextureView,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
render_device.create_bind_group(&BindGroupDescriptor {
|
render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
entry::model(0, model),
|
entry::model(0, model.clone()),
|
||||||
entry::skinning(1, skin),
|
entry::skinning(1, skin),
|
||||||
entry::weights(2, weights),
|
entry::weights(2, weights),
|
||||||
entry::targets(3, targets),
|
entry::targets(3, targets),
|
||||||
|
|
|
@ -4,12 +4,22 @@
|
||||||
|
|
||||||
#ifdef MESH_BINDGROUP_1
|
#ifdef MESH_BINDGROUP_1
|
||||||
|
|
||||||
|
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
|
||||||
@group(1) @binding(0)
|
@group(1) @binding(0)
|
||||||
var<uniform> mesh: Mesh;
|
var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
|
||||||
|
|
||||||
#else
|
#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)
|
@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);
|
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
|
// 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
|
// 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,
|
// 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/
|
// http://www.mikktspace.com/
|
||||||
return normalize(
|
return normalize(
|
||||||
mat3x3<f32>(
|
mat3x3<f32>(
|
||||||
mesh.inverse_transpose_model[0].xyz,
|
mesh[instance_index].inverse_transpose_model[0].xyz,
|
||||||
mesh.inverse_transpose_model[1].xyz,
|
mesh[instance_index].inverse_transpose_model[1].xyz,
|
||||||
mesh.inverse_transpose_model[2].xyz
|
mesh[instance_index].inverse_transpose_model[2].xyz
|
||||||
) * vertex_normal
|
) * vertex_normal
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates the sign of the determinant of the 3x3 model matrix based on a
|
// Calculates the sign of the determinant of the 3x3 model matrix based on a
|
||||||
// mesh flag
|
// 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
|
// bool(u32) is false if 0u else true
|
||||||
// f32(bool) is 1.0 if true else 0.0
|
// 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
|
// * 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
|
// 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
|
// 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,
|
// 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
|
// NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for
|
||||||
// situations such as negative scaling.
|
// situations such as negative scaling.
|
||||||
vertex_tangent.w * sign_determinant_model_3x3m()
|
vertex_tangent.w * sign_determinant_model_3x3m(instance_index)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,7 @@ struct MeshVertexOutput {
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
@location(4) color: vec4<f32>,
|
@location(4) color: vec4<f32>,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
||||||
|
@location(5) instance_index: u32,
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ fn fragment(
|
||||||
pbr_input.V = V;
|
pbr_input.V = V;
|
||||||
pbr_input.occlusion = occlusion;
|
pbr_input.occlusion = occlusion;
|
||||||
|
|
||||||
pbr_input.flags = mesh.flags;
|
pbr_input.flags = mesh[in.instance_index].flags;
|
||||||
|
|
||||||
output_color = pbr_functions::pbr(pbr_input);
|
output_color = pbr_functions::pbr(pbr_input);
|
||||||
} else {
|
} 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) {
|
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);
|
let light_id = clustering::get_light_id(i);
|
||||||
var shadow: f32 = 1.0;
|
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) {
|
&& (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);
|
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);
|
let light_id = clustering::get_light_id(i);
|
||||||
|
|
||||||
var shadow: f32 = 1.0;
|
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) {
|
&& (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);
|
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;
|
let n_directional_lights = view_bindings::lights.n_directional_lights;
|
||||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||||
var shadow: f32 = 1.0;
|
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) {
|
&& (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);
|
shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
|
@builtin(instance_index) instance_index: u32,
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
@location(4) joint_indexes: vec4<u32>,
|
@location(4) joint_indexes: vec4<u32>,
|
||||||
|
@ -22,7 +23,7 @@ fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
#ifdef SKINNED
|
#ifdef SKINNED
|
||||||
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
|
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
|
||||||
#else
|
#else
|
||||||
let model = mesh.model;
|
let model = mesh[vertex.instance_index].model;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
|
|
|
@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
||||||
use bevy_reflect::std_traits::ReflectDefault;
|
use bevy_reflect::std_traits::ReflectDefault;
|
||||||
use bevy_reflect::{Reflect, TypeUuid};
|
use bevy_reflect::{Reflect, TypeUuid};
|
||||||
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
||||||
|
use bevy_render::render_resource::GpuArrayBufferIndex;
|
||||||
use bevy_render::Render;
|
use bevy_render::Render;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||||
|
@ -117,8 +118,21 @@ fn queue_wireframes(
|
||||||
pipeline_cache: Res<PipelineCache>,
|
pipeline_cache: Res<PipelineCache>,
|
||||||
msaa: Res<Msaa>,
|
msaa: Res<Msaa>,
|
||||||
mut material_meshes: ParamSet<(
|
mut material_meshes: ParamSet<(
|
||||||
Query<(Entity, &Handle<Mesh>, &MeshUniform)>,
|
Query<(
|
||||||
Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>,
|
Entity,
|
||||||
|
&Handle<Mesh>,
|
||||||
|
&MeshUniform,
|
||||||
|
&GpuArrayBufferIndex<MeshUniform>,
|
||||||
|
)>,
|
||||||
|
Query<
|
||||||
|
(
|
||||||
|
Entity,
|
||||||
|
&Handle<Mesh>,
|
||||||
|
&MeshUniform,
|
||||||
|
&GpuArrayBufferIndex<MeshUniform>,
|
||||||
|
),
|
||||||
|
With<Wireframe>,
|
||||||
|
>,
|
||||||
)>,
|
)>,
|
||||||
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
|
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
|
||||||
) {
|
) {
|
||||||
|
@ -128,17 +142,17 @@ fn queue_wireframes(
|
||||||
let rangefinder = view.rangefinder3d();
|
let rangefinder = view.rangefinder3d();
|
||||||
|
|
||||||
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
|
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
|
||||||
let add_render_phase =
|
let add_render_phase = |(entity, mesh_handle, mesh_uniform, batch_indices): (
|
||||||
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
|
Entity,
|
||||||
|
&Handle<Mesh>,
|
||||||
|
&MeshUniform,
|
||||||
|
&GpuArrayBufferIndex<MeshUniform>,
|
||||||
|
)| {
|
||||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||||
let key = view_key
|
let key =
|
||||||
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||||
let pipeline_id = pipelines.specialize(
|
let pipeline_id =
|
||||||
&pipeline_cache,
|
pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout);
|
||||||
&wireframe_pipeline,
|
|
||||||
key,
|
|
||||||
&mesh.layout,
|
|
||||||
);
|
|
||||||
let pipeline_id = match pipeline_id {
|
let pipeline_id = match pipeline_id {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -151,6 +165,9 @@ fn queue_wireframes(
|
||||||
pipeline: pipeline_id,
|
pipeline: pipeline_id,
|
||||||
draw_function: draw_custom,
|
draw_function: draw_custom,
|
||||||
distance: rangefinder.distance(&mesh_uniform.transform),
|
distance: rangefinder.distance(&mesh_uniform.transform),
|
||||||
|
per_object_binding_dynamic_offset: batch_indices
|
||||||
|
.dynamic_offset
|
||||||
|
.unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,16 +18,20 @@ pub struct GpuComponentArrayBufferPlugin<C: Component + GpuArrayBufferable>(Phan
|
||||||
impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin<C> {
|
impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin<C> {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app.add_systems(
|
||||||
.insert_resource(GpuArrayBuffer::<C>::new(
|
|
||||||
render_app.world.resource::<RenderDevice>(),
|
|
||||||
))
|
|
||||||
.add_systems(
|
|
||||||
Render,
|
Render,
|
||||||
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
|
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>(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Component + GpuArrayBufferable> Default for GpuComponentArrayBufferPlugin<C> {
|
impl<C: Component + GpuArrayBufferable> Default for GpuComponentArrayBufferPlugin<C> {
|
||||||
|
|
Loading…
Reference in a new issue