
# Objective Closes #18075 In order to enable a number of patterns for dynamic materials in the engine, it's necessary to decouple the renderer from the `Material` trait. This opens the possibility for: - Materials that aren't coupled to `AsBindGroup`. - 2d using the underlying 3d bindless infrastructure. - Dynamic materials that can change their layout at runtime. - Materials that aren't even backed by a Rust struct at all. ## Solution In short, remove all trait bounds from render world material systems and resources. This means moving a bunch of stuff onto `MaterialProperties` and engaging in some hacks to make specialization work. Rather than storing the bind group data in `MaterialBindGroupAllocator`, right now we're storing it in a closure on `MaterialProperties`. TBD if this has bad performance characteristics. ## Benchmarks - `many_cubes`: `cargo run --example many_cubes --release --features=bevy/trace_tracy -- --vary-material-data-per-instance`:  - @DGriffin91's Caldera `cargo run --release --features=bevy/trace_tracy -- --random-materials`  - @DGriffin91's Caldera with 20 unique material types (i.e. `MaterialPlugin<M>`) and random materials per mesh `cargo run --release --features=bevy/trace_tracy -- --random-materials`  ### TODO - We almost certainly lost some parallelization from removing the type params that could be gained back from smarter iteration. - Test all the things that could have broken. - ~Fix meshlets~ ## Showcase See [the example](https://github.com/bevyengine/bevy/pull/19667/files#diff-9d768cfe1c3aa81eff365d250d3cbe5a63e8df63e81dd85f64c3c3cd993f6d94) for a custom material implemented without the use of the `Material` trait and thus `AsBindGroup`.  --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com> Co-authored-by: IceSentry <c.giguere42@gmail.com>
137 lines
3.7 KiB
Rust
137 lines
3.7 KiB
Rust
//! A shader that uses the WESL shading language.
|
|
|
|
use bevy::{
|
|
pbr::{MaterialPipeline, MaterialPipelineKey},
|
|
prelude::*,
|
|
reflect::TypePath,
|
|
render::{
|
|
mesh::MeshVertexBufferLayoutRef,
|
|
render_resource::{
|
|
AsBindGroup, RenderPipelineDescriptor, ShaderDefVal, ShaderRef,
|
|
SpecializedMeshPipelineError,
|
|
},
|
|
},
|
|
};
|
|
|
|
/// This example uses shader source files from the assets subdirectory
|
|
const FRAGMENT_SHADER_ASSET_PATH: &str = "shaders/custom_material.wesl";
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins((
|
|
DefaultPlugins,
|
|
MaterialPlugin::<CustomMaterial>::default(),
|
|
CustomMaterialPlugin,
|
|
))
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, update)
|
|
.run();
|
|
}
|
|
|
|
/// A plugin that loads the custom material shader
|
|
pub struct CustomMaterialPlugin;
|
|
|
|
/// An example utility shader that is used by the custom material
|
|
#[expect(
|
|
dead_code,
|
|
reason = "used to kept a strong handle, shader is referenced by the material"
|
|
)]
|
|
#[derive(Resource)]
|
|
struct UtilityShader(Handle<Shader>);
|
|
|
|
impl Plugin for CustomMaterialPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
let handle = app
|
|
.world_mut()
|
|
.resource_mut::<AssetServer>()
|
|
.load::<Shader>("shaders/util.wesl");
|
|
app.insert_resource(UtilityShader(handle));
|
|
}
|
|
}
|
|
|
|
/// set up a simple 3D scene
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<CustomMaterial>>,
|
|
) {
|
|
// cube
|
|
commands.spawn((
|
|
Mesh3d(meshes.add(Cuboid::default())),
|
|
MeshMaterial3d(materials.add(CustomMaterial {
|
|
time: Vec4::ZERO,
|
|
party_mode: false,
|
|
})),
|
|
Transform::from_xyz(0.0, 0.5, 0.0),
|
|
));
|
|
|
|
// camera
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
));
|
|
}
|
|
|
|
fn update(
|
|
time: Res<Time>,
|
|
mut query: Query<(&MeshMaterial3d<CustomMaterial>, &mut Transform)>,
|
|
mut materials: ResMut<Assets<CustomMaterial>>,
|
|
keys: Res<ButtonInput<KeyCode>>,
|
|
) {
|
|
for (material, mut transform) in query.iter_mut() {
|
|
let material = materials.get_mut(material).unwrap();
|
|
material.time.x = time.elapsed_secs();
|
|
if keys.just_pressed(KeyCode::Space) {
|
|
material.party_mode = !material.party_mode;
|
|
}
|
|
|
|
if material.party_mode {
|
|
transform.rotate(Quat::from_rotation_y(0.005));
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is the struct that will be passed to your shader
|
|
#[derive(Asset, TypePath, AsBindGroup, Clone)]
|
|
#[bind_group_data(CustomMaterialKey)]
|
|
struct CustomMaterial {
|
|
// Needed for 16 bit alignment in WebGL2
|
|
#[uniform(0)]
|
|
time: Vec4,
|
|
party_mode: bool,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Eq, PartialEq, Hash, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
struct CustomMaterialKey {
|
|
party_mode: u32,
|
|
}
|
|
|
|
impl From<&CustomMaterial> for CustomMaterialKey {
|
|
fn from(material: &CustomMaterial) -> Self {
|
|
Self {
|
|
party_mode: material.party_mode as u32,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Material for CustomMaterial {
|
|
fn fragment_shader() -> ShaderRef {
|
|
FRAGMENT_SHADER_ASSET_PATH.into()
|
|
}
|
|
|
|
fn specialize(
|
|
_pipeline: &MaterialPipeline,
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
_layout: &MeshVertexBufferLayoutRef,
|
|
key: MaterialPipelineKey<Self>,
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
let fragment = descriptor.fragment.as_mut().unwrap();
|
|
fragment.shader_defs.push(ShaderDefVal::Bool(
|
|
"PARTY_MODE".to_string(),
|
|
key.bind_group_data.party_mode == 1,
|
|
));
|
|
Ok(())
|
|
}
|
|
}
|