Created
January 18, 2025 23:57
-
-
Save ChristopherBiscardi/c0511e6205b0d60cb2271ea11e3cc942 to your computer and use it in GitHub Desktop.
bevy 0.15 Custom Attribute StandardMaterial Extension Shader 360 flip
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import bevy_pbr::{ | |
pbr_fragment::pbr_input_from_standard_material, | |
pbr_functions::alpha_discard, | |
} | |
#ifdef PREPASS_PIPELINE | |
#import bevy_pbr::{ | |
prepass_io::{VertexOutput, FragmentOutput}, | |
pbr_deferred_functions::deferred_output, | |
} | |
#else | |
#import bevy_pbr::{ | |
forward_io::{VertexOutput, FragmentOutput}, | |
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing}, | |
} | |
#endif | |
struct MyExtendedMaterial { | |
quantize_steps: u32, | |
} | |
@group(2) @binding(100) | |
var<uniform> my_extended_material: MyExtendedMaterial; | |
@fragment | |
fn fragment( | |
in: VertexOutput, | |
@location(8) blend: vec4f, | |
@builtin(front_facing) is_front: bool, | |
) -> FragmentOutput { | |
// generate a PbrInput struct from the StandardMaterial bindings | |
var pbr_input = pbr_input_from_standard_material(in, is_front); | |
// we can optionally modify the input before lighting and alpha_discard is applied | |
// pbr_input.material.base_color.b = pbr_input.material.base_color.r; | |
pbr_input.material.base_color = blend; | |
// alpha discard | |
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color); | |
#ifdef PREPASS_PIPELINE | |
// in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader. | |
let out = deferred_output(in, pbr_input); | |
#else | |
var out: FragmentOutput; | |
// apply lighting | |
out.color = apply_pbr_lighting(pbr_input); | |
// we can optionally modify the lit color before post-processing is applied | |
out.color = vec4<f32>(vec4<u32>(out.color * f32(my_extended_material.quantize_steps))) / f32(my_extended_material.quantize_steps); | |
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr) | |
// note this does not include fullscreen postprocessing effects like bloom. | |
out.color = main_pass_post_lighting_processing(pbr_input, out.color); | |
// we can optionally modify the final result here | |
out.color = out.color * 2.0; | |
#endif | |
return out; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Demonstrates using a custom extension to the `StandardMaterial` to modify the results of the builtin pbr shader. | |
use bevy::{ | |
color::palettes::basic::RED, | |
pbr::{ | |
ExtendedMaterial, MaterialExtension, | |
OpaqueRendererMethod, | |
}, | |
prelude::*, | |
render::{ | |
mesh::{ | |
MeshVertexAttribute, MeshVertexBufferLayoutRef, | |
}, | |
render_resource::*, | |
}, | |
}; | |
fn main() { | |
App::new() | |
.add_plugins(DefaultPlugins) | |
.add_plugins(MaterialPlugin::< | |
ExtendedMaterial<StandardMaterial, MyExtension>, | |
>::default()) | |
.add_systems(Startup, setup) | |
.add_systems(Update, rotate_things) | |
.run(); | |
} | |
// A "high" random id should be used for custom attributes to ensure consistent sorting and avoid collisions with other attributes. | |
// See the MeshVertexAttribute docs for more info. | |
const ATTRIBUTE_BLEND_COLOR: MeshVertexAttribute = | |
MeshVertexAttribute::new( | |
"BlendColor", | |
988540917, | |
VertexFormat::Float32x4, | |
); | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut< | |
Assets< | |
ExtendedMaterial<StandardMaterial, MyExtension>, | |
>, | |
>, | |
) { | |
let mesh = Mesh::from(Cuboid::default()) | |
// Sets the custom attribute | |
.with_inserted_attribute( | |
ATTRIBUTE_BLEND_COLOR, | |
// The cube mesh has 24 vertices (6 faces, 4 vertices per face), so we insert one BlendColor for each | |
vec![[0.0, 1.0, 0.0, 1.0]; 24], | |
); | |
// sphere | |
commands.spawn(( | |
Mesh3d(meshes.add(mesh)), | |
MeshMaterial3d(materials.add(ExtendedMaterial { | |
base: StandardMaterial { | |
base_color: RED.into(), | |
// can be used in forward or deferred mode | |
opaque_render_method: | |
OpaqueRendererMethod::Auto, | |
// in deferred mode, only the PbrInput can be modified (uvs, color and other material properties), | |
// in forward mode, the output can also be modified after lighting is applied. | |
// see the fragment shader `extended_material.wgsl` for more info. | |
// Note: to run in deferred mode, you must also add a `DeferredPrepass` component to the camera and either | |
// change the above to `OpaqueRendererMethod::Deferred` or add the `DefaultOpaqueRendererMethod` resource. | |
..Default::default() | |
}, | |
extension: MyExtension { quantize_steps: 3 }, | |
})), | |
Transform::from_xyz(0.0, 0.5, 0.0), | |
)); | |
// light | |
commands.spawn(( | |
DirectionalLight::default(), | |
Transform::from_xyz(1.0, 1.0, 1.0) | |
.looking_at(Vec3::ZERO, Vec3::Y), | |
Rotate, | |
)); | |
// camera | |
commands.spawn(( | |
Camera3d::default(), | |
Transform::from_xyz(-2.0, 2.5, 5.0) | |
.looking_at(Vec3::ZERO, Vec3::Y), | |
)); | |
} | |
#[derive(Component)] | |
struct Rotate; | |
fn rotate_things( | |
mut q: Query<&mut Transform, With<Rotate>>, | |
time: Res<Time>, | |
) { | |
for mut t in &mut q { | |
t.rotate_y(time.delta_secs()); | |
} | |
} | |
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] | |
struct MyExtension { | |
// We need to ensure that the bindings of the base material and the extension do not conflict, | |
// so we start from binding slot 100, leaving slots 0-99 for the base material. | |
#[uniform(100)] | |
quantize_steps: u32, | |
} | |
impl MaterialExtension for MyExtension { | |
fn vertex_shader() -> ShaderRef { | |
"vertex.wgsl".into() | |
} | |
fn fragment_shader() -> ShaderRef { | |
"fragment.wgsl".into() | |
} | |
fn deferred_fragment_shader() -> ShaderRef { | |
"fragment.wgsl".into() | |
} | |
fn specialize( | |
_pipeline: &bevy::pbr::MaterialExtensionPipeline, | |
descriptor: &mut RenderPipelineDescriptor, | |
layout: &MeshVertexBufferLayoutRef, | |
_key: bevy::pbr::MaterialExtensionKey<Self>, | |
) -> Result<(), SpecializedMeshPipelineError> { | |
// layout.0.get_layout(attribute_descriptors); | |
let vertex_layout = layout.0.get_layout(&[ | |
Mesh::ATTRIBUTE_POSITION.at_shader_location(0), | |
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), | |
Mesh::ATTRIBUTE_UV_0.at_shader_location(2), | |
// Mesh::ATTRIBUTE_TANGENT.at_shader_location(3), | |
// Mesh::ATTRIBUTE_COLOR.at_shader_location(4), | |
ATTRIBUTE_BLEND_COLOR.at_shader_location(8), | |
])?; | |
descriptor.vertex.buffers = vec![vertex_layout]; | |
Ok(()) | |
} | |
} | |
// struct VertexOutput { | |
// // 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>, | |
// @location(1) world_normal: vec3<f32>, | |
// #ifdef VERTEX_UVS_A | |
// @location(2) uv: vec2<f32>, | |
// #endif | |
// #ifdef VERTEX_UVS_B | |
// @location(3) uv_b: vec2<f32>, | |
// #endif | |
// #ifdef VERTEX_TANGENTS | |
// @location(4) world_tangent: vec4<f32>, | |
// #endif | |
// #ifdef VERTEX_COLORS | |
// @location(5) color: vec4<f32>, | |
// #endif | |
// #ifdef VERTEX_OUTPUT_INSTANCE_INDEX | |
// @location(6) @interpolate(flat) instance_index: u32, | |
// #endif | |
// #ifdef VISIBILITY_RANGE_DITHER | |
// @location(7) @interpolate(flat) visibility_range_dither: i32, | |
// #endif | |
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import bevy_pbr::{ | |
mesh_bindings::mesh, | |
mesh_functions, | |
skinning, | |
morph::morph, | |
forward_io::{Vertex, VertexOutput}, | |
view_transformations::position_world_to_clip, | |
} | |
#ifdef MORPH_TARGETS | |
fn morph_vertex(vertex_in: Vertex) -> Vertex { | |
var vertex = vertex_in; | |
let first_vertex = mesh[vertex.instance_index].first_vertex_index; | |
let vertex_index = vertex.index - first_vertex; | |
let weight_count = bevy_pbr::morph::layer_count(); | |
for (var i: u32 = 0u; i < weight_count; i ++) { | |
let weight = bevy_pbr::morph::weight_at(i); | |
if weight == 0.0 { | |
continue; | |
} | |
vertex.position += weight * morph(vertex_index, bevy_pbr::morph::position_offset, i); | |
#ifdef VERTEX_NORMALS | |
vertex.normal += weight * morph(vertex_index, bevy_pbr::morph::normal_offset, i); | |
#endif | |
#ifdef VERTEX_TANGENTS | |
vertex.tangent += vec4(weight * morph(vertex_index, bevy_pbr::morph::tangent_offset, i), 0.0); | |
#endif | |
} | |
return vertex; | |
} | |
#endif | |
struct MyOutput { | |
// 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>, | |
@location(1) world_normal: vec3<f32>, | |
#ifdef VERTEX_UVS_A | |
@location(2) uv: vec2<f32>, | |
#endif | |
#ifdef VERTEX_UVS_B | |
@location(3) uv_b: vec2<f32>, | |
#endif | |
#ifdef VERTEX_TANGENTS | |
@location(4) world_tangent: vec4<f32>, | |
#endif | |
#ifdef VERTEX_COLORS | |
@location(5) color: vec4<f32>, | |
#endif | |
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX | |
@location(6) @interpolate(flat) instance_index: u32, | |
#endif | |
#ifdef VISIBILITY_RANGE_DITHER | |
@location(7) @interpolate(flat) visibility_range_dither: i32, | |
#endif | |
@location(8) blend: vec4f | |
} | |
@vertex | |
fn vertex( | |
vertex_no_morph: Vertex, | |
@location(8) blend: vec4f | |
) -> MyOutput { | |
var out: MyOutput; | |
#ifdef MORPH_TARGETS | |
var vertex = morph_vertex(vertex_no_morph); | |
#else | |
var vertex = vertex_no_morph; | |
#endif | |
let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); | |
#ifdef SKINNED | |
var world_from_local = skinning::skin_model( | |
vertex.joint_indices, | |
vertex.joint_weights, | |
vertex_no_morph.instance_index | |
); | |
#else | |
// 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 world_from_local = mesh_world_from_local; | |
#endif | |
#ifdef VERTEX_NORMALS | |
#ifdef SKINNED | |
out.world_normal = skinning::skin_normals(world_from_local, vertex.normal); | |
#else | |
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 | |
#ifdef VERTEX_POSITIONS | |
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0)); | |
out.position = position_world_to_clip(out.world_position.xyz); | |
#endif | |
#ifdef VERTEX_UVS_A | |
out.uv = vertex.uv; | |
#endif | |
#ifdef VERTEX_UVS_B | |
out.uv_b = vertex.uv_b; | |
#endif | |
#ifdef VERTEX_TANGENTS | |
out.world_tangent = mesh_functions::mesh_tangent_local_to_world( | |
world_from_local, | |
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 | |
#ifdef VISIBILITY_RANGE_DITHER | |
out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level( | |
vertex_no_morph.instance_index, mesh_world_from_local[3]); | |
#endif | |
out.blend = blend; | |
return out; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment