Last active
July 15, 2022 11:06
-
-
Save ChristopherBiscardi/36582828800a03e0ed4b56e0b3d3a7d1 to your computer and use it in GitHub Desktop.
Bevy: Extending StandardMaterial
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
use bevy::{ | |
asset::AssetServerSettings, | |
pbr::{ | |
MaterialPipeline, MaterialPipelineKey, | |
StandardMaterialKey, StandardMaterialUniform, | |
}, | |
prelude::*, | |
reflect::TypeUuid, | |
render::{ | |
mesh::{ | |
MeshVertexBufferLayout, VertexAttributeValues, | |
}, | |
primitives::Frustum, | |
render_asset::RenderAssets, | |
render_resource::{ | |
AsBindGroup, AsBindGroupShaderType, Face, | |
RenderPipelineDescriptor, ShaderRef, | |
ShaderType, SpecializedMeshPipelineError, | |
TextureFormat, | |
}, | |
}, | |
}; | |
use bevy_shader_utils::ShaderUtilsPlugin; | |
fn main() { | |
App::new() | |
.insert_resource(ClearColor( | |
Color::hex("071f3c").unwrap(), | |
)) | |
.insert_resource(AssetServerSettings { | |
watch_for_changes: true, | |
..default() | |
}) | |
.add_plugins(DefaultPlugins) | |
.add_plugin(ShaderUtilsPlugin) | |
.add_plugin( | |
MaterialPlugin::<CustomMaterial>::default(), | |
) | |
.add_startup_system(setup) | |
.add_system(change_color) | |
.add_system(animate_light_direction) | |
.run(); | |
} | |
#[derive(Component)] | |
struct Cube; | |
/// set up a simple 3D scene | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut custom_materials: ResMut<Assets<CustomMaterial>>, | |
mut materials: ResMut<Assets<StandardMaterial>>, | |
asset_server: Res<AssetServer>, | |
) { | |
let mut mesh = Mesh::from(shape::UVSphere { | |
radius: 1.0, | |
..default() | |
}); | |
if let Some(VertexAttributeValues::Float32x3( | |
positions, | |
)) = mesh.attribute(Mesh::ATTRIBUTE_POSITION) | |
{ | |
let colors: Vec<[f32; 4]> = positions | |
.iter() | |
.map(|[r, g, b]| { | |
[ | |
(1. - *r) / 2., | |
(1. - *g) / 2., | |
(1. - *b) / 2., | |
1., | |
] | |
}) | |
.collect(); | |
mesh.insert_attribute( | |
Mesh::ATTRIBUTE_COLOR, | |
colors, | |
); | |
} | |
commands.spawn().insert_bundle(MaterialMeshBundle { | |
mesh: meshes.add(mesh), | |
transform: Transform::from_xyz(0.0, 0.5, 0.0), | |
material: custom_materials.add(CustomMaterial { | |
time: 0., | |
..default() | |
}), | |
..default() | |
}); | |
// camera | |
commands.spawn_bundle(Camera3dBundle { | |
transform: Transform::from_xyz(-2.0, 2.5, 5.0) | |
.looking_at(Vec3::ZERO, Vec3::Y), | |
..default() | |
}); | |
// directional 'sun' light | |
const HALF_SIZE: f32 = 10.0; | |
commands.spawn_bundle(DirectionalLightBundle { | |
directional_light: DirectionalLight { | |
// Configure the projection to better fit the scene | |
shadow_projection: OrthographicProjection { | |
left: -HALF_SIZE, | |
right: HALF_SIZE, | |
bottom: -HALF_SIZE, | |
top: HALF_SIZE, | |
near: -10.0 * HALF_SIZE, | |
far: 10.0 * HALF_SIZE, | |
..default() | |
}, | |
shadows_enabled: false, | |
..default() | |
}, | |
transform: Transform { | |
translation: Vec3::new(0.0, 2.0, 0.0), | |
rotation: Quat::from_rotation_x( | |
-std::f32::consts::FRAC_PI_4, | |
), | |
..default() | |
}, | |
..default() | |
}); | |
} | |
fn animate_light_direction( | |
time: Res<Time>, | |
mut query: Query< | |
&mut Transform, | |
With<DirectionalLight>, | |
>, | |
) { | |
for mut transform in query.iter_mut() { | |
transform.rotate_y(time.delta_seconds() * 0.5); | |
} | |
} | |
fn change_color( | |
mut materials: ResMut<Assets<CustomMaterial>>, | |
time: Res<Time>, | |
) { | |
for material in materials.iter_mut() { | |
material.1.time = | |
time.seconds_since_startup() as f32; | |
} | |
} | |
// The Material trait is very configurable, but comes with sensible defaults for all methods. | |
// You only need to implement functions for features that need non-default behavior. See the Material api docs for details! | |
impl Material for CustomMaterial { | |
fn vertex_shader() -> ShaderRef { | |
"shaders/vertex_shader.wgsl".into() | |
} | |
fn fragment_shader() -> ShaderRef { | |
"shaders/pbr_extension.wgsl".into() | |
} | |
fn alpha_mode(&self) -> AlphaMode { | |
self.sm.alpha_mode | |
} | |
fn specialize( | |
_pipeline: &MaterialPipeline<Self>, | |
descriptor: &mut RenderPipelineDescriptor, | |
_layout: &MeshVertexBufferLayout, | |
key: MaterialPipelineKey<Self>, | |
) -> Result<(), SpecializedMeshPipelineError> { | |
if key.bind_group_data.normal_map { | |
descriptor | |
.fragment | |
.as_mut() | |
.unwrap() | |
.shader_defs | |
.push(String::from( | |
"STANDARDMATERIAL_NORMAL_MAP", | |
)); | |
} | |
descriptor.primitive.cull_mode = None; | |
if let Some(label) = &mut descriptor.label { | |
*label = | |
format!("custom_pbr_{}", *label).into(); | |
} | |
Ok(()) | |
} | |
} | |
// This is the struct that will be passed to your shader | |
#[derive(AsBindGroup, TypeUuid, Debug, Clone, Default)] | |
#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] | |
#[bind_group_data(CustomMaterialKey)] | |
#[uniform(0, CustomMaterialUniform)] | |
pub struct CustomMaterial { | |
#[uniform(12)] | |
time: f32, | |
sm: StandardMaterial, | |
} | |
// Standard stuff for CustomMaterial | |
/// The GPU representation of the uniform data of a [`StandardMaterial`]. | |
#[derive(Clone, Default, ShaderType)] | |
pub struct CustomMaterialUniform { | |
pub time: f32, | |
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything | |
/// in between. | |
pub base_color: Vec4, | |
// Use a color for user friendliness even though we technically don't use the alpha channel | |
// Might be used in the future for exposure correction in HDR | |
pub emissive: Vec4, | |
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader | |
/// Defaults to minimum of 0.089 | |
pub roughness: f32, | |
/// From [0.0, 1.0], dielectric to pure metallic | |
pub metallic: f32, | |
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0] | |
/// defaults to 0.5 which is mapped to 4% reflectance in the shader | |
pub reflectance: f32, | |
pub flags: u32, | |
/// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque, | |
/// and any below means fully transparent. | |
pub alpha_cutoff: f32, | |
} | |
impl AsBindGroupShaderType<CustomMaterialUniform> | |
for CustomMaterial | |
{ | |
fn as_bind_group_shader_type( | |
&self, | |
images: &RenderAssets<Image>, | |
) -> CustomMaterialUniform { | |
let sm_uniform: StandardMaterialUniform = | |
self.sm.as_bind_group_shader_type(images); | |
CustomMaterialUniform { | |
time: self.time, | |
base_color: sm_uniform.base_color, | |
emissive: sm_uniform.emissive.into(), | |
roughness: sm_uniform.roughness, | |
metallic: sm_uniform.metallic, | |
reflectance: sm_uniform.reflectance, | |
flags: sm_uniform.flags, | |
alpha_cutoff: sm_uniform.alpha_cutoff, | |
} | |
} | |
} | |
#[derive(Clone, PartialEq, Eq, Hash)] | |
pub struct CustomMaterialKey { | |
normal_map: bool, | |
cull_mode: Option<Face>, | |
} | |
impl From<&CustomMaterial> for CustomMaterialKey { | |
fn from(material: &CustomMaterial) -> Self { | |
CustomMaterialKey { | |
normal_map: material | |
.sm | |
.normal_map_texture | |
.is_some(), | |
cull_mode: material.sm.cull_mode, | |
} | |
} | |
} |
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
2022-07-15T10:53:42.906489Z ERROR wgpu::backend::direct: Handling wgpu errors as fatal by default | |
thread 'main' panicked at 'wgpu error: Validation Error | |
Caused by: | |
In Device::create_render_pipeline | |
note: label = `custom_pbr_opaque_mesh_pipeline` | |
error matching FRAGMENT shader requirements against the pipeline | |
shader global ResourceBinding { group: 1, binding: 1 } is not available in the layout pipeline layout | |
binding is missing from the pipeline layout | |
', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/wgpu-0.12.0/src/backend/direct.rs:2273:5 | |
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace |
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_view_bindings | |
// #import bevy_pbr::pbr_bindings | |
// #import bevy_pbr::pbr_types | |
struct StandardMaterial { | |
time: f32; | |
base_color: vec4<f32>; | |
emissive: vec4<f32>; | |
perceptual_roughness: f32; | |
metallic: f32; | |
reflectance: f32; | |
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. | |
flags: u32; | |
alpha_cutoff: f32; | |
}; | |
let STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u; | |
let STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u; | |
let STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; | |
let STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u; | |
let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; | |
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; | |
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u; | |
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u; | |
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u; | |
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; | |
let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; | |
// Creates a StandardMaterial with default values | |
fn standard_material_new() -> StandardMaterial { | |
var material: StandardMaterial; | |
// NOTE: Keep in-sync with src/pbr_material.rs! | |
material.base_color = vec4<f32>(1.0, 1.0, 1.0, 1.0); | |
material.emissive = vec4<f32>(0.0, 0.0, 0.0, 1.0); | |
material.perceptual_roughness = 0.089; | |
material.metallic = 0.01; | |
material.reflectance = 0.5; | |
material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE; | |
material.alpha_cutoff = 0.5; | |
material.time = 0.0; | |
return material; | |
} | |
#import bevy_pbr::mesh_bindings | |
#import bevy_pbr::utils | |
#import bevy_pbr::clustered_forward | |
#import bevy_pbr::lighting | |
#import bevy_pbr::shadows | |
#import bevy_pbr::pbr_functions | |
#import bevy_shader_utils::simplex_noise_3d | |
#import bevy_shader_utils::simplex_noise_2d | |
[[group(1), binding(0)]] | |
var<uniform> material: StandardMaterial; | |
[[group(1), binding(1)]] | |
var base_color_texture: texture_2d<f32>; | |
[[group(1), binding(2)]] | |
var base_color_sampler: sampler; | |
[[group(1), binding(3)]] | |
var emissive_texture: texture_2d<f32>; | |
[[group(1), binding(4)]] | |
var emissive_sampler: sampler; | |
[[group(1), binding(5)]] | |
var metallic_roughness_texture: texture_2d<f32>; | |
[[group(1), binding(6)]] | |
var metallic_roughness_sampler: sampler; | |
[[group(1), binding(7)]] | |
var occlusion_texture: texture_2d<f32>; | |
[[group(1), binding(8)]] | |
var occlusion_sampler: sampler; | |
[[group(1), binding(9)]] | |
var normal_map_texture: texture_2d<f32>; | |
[[group(1), binding(10)]] | |
var normal_map_sampler: sampler; | |
struct FragmentInput { | |
[[builtin(front_facing)]] is_front: bool; | |
[[builtin(position)]] frag_coord: vec4<f32>; | |
[[location(0)]] world_position: vec4<f32>; | |
[[location(1)]] world_normal: vec3<f32>; | |
#ifdef VERTEX_UVS | |
[[location(2)]] uv: vec2<f32>; | |
#endif | |
#ifdef VERTEX_TANGENTS | |
[[location(3)]] world_tangent: vec4<f32>; | |
#endif | |
#ifdef VERTEX_COLORS | |
[[location(4)]] color: vec4<f32>; | |
#endif | |
}; | |
[[stage(fragment)]] | |
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> { | |
// var output_color = vec4<f32>(0.533, 0.533, 0.80, 1.0); | |
var output_color: vec4<f32> = material.base_color; | |
// #ifdef VERTEX_COLORS | |
// output_color = output_color * in.color; | |
// #endif | |
#ifdef VERTEX_UVS | |
if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { | |
output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv); | |
} | |
#endif | |
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit | |
if ((material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { | |
// Prepare a 'processed' StandardMaterial by sampling all textures to resolve | |
// the material members | |
var pbr_input: PbrInput; | |
pbr_input.material.base_color = output_color; | |
pbr_input.material.reflectance = material.reflectance; | |
pbr_input.material.flags = material.flags; | |
pbr_input.material.alpha_cutoff = material.alpha_cutoff; | |
// TODO use .a for exposure compensation in HDR | |
var emissive: vec4<f32> = material.emissive; | |
#ifdef VERTEX_UVS | |
if ((material.flags & STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { | |
emissive = vec4<f32>(emissive.rgb * textureSample(emissive_texture, emissive_sampler, in.uv).rgb, 1.0); | |
} | |
#endif | |
pbr_input.material.emissive = emissive; | |
var metallic: f32 = material.metallic; | |
var perceptual_roughness: f32 = material.perceptual_roughness; | |
#ifdef VERTEX_UVS | |
if ((material.flags & STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { | |
let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, in.uv); | |
// Sampling from GLTF standard channels for now | |
metallic = metallic * metallic_roughness.b; | |
perceptual_roughness = perceptual_roughness * metallic_roughness.g; | |
} | |
#endif | |
pbr_input.material.metallic = metallic; | |
pbr_input.material.perceptual_roughness = perceptual_roughness; | |
var occlusion: f32 = 1.0; | |
#ifdef VERTEX_UVS | |
if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { | |
occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r; | |
} | |
#endif | |
pbr_input.occlusion = occlusion; | |
pbr_input.frag_coord = in.frag_coord; | |
pbr_input.world_position = in.world_position; | |
pbr_input.world_normal = in.world_normal; | |
pbr_input.is_orthographic = view.projection[3].w == 1.0; | |
pbr_input.N = prepare_normal( | |
material.flags, | |
in.world_normal, | |
#ifdef VERTEX_TANGENTS | |
#ifdef STANDARDMATERIAL_NORMAL_MAP | |
in.world_tangent, | |
#endif | |
#endif | |
#ifdef VERTEX_UVS | |
in.uv, | |
#endif | |
in.is_front, | |
); | |
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); | |
output_color = tone_mapping(pbr(pbr_input)); | |
} | |
// return output_color; | |
// custom code | |
var base_color = output_color; | |
// var base_color = vec4<f32>(1.0,0.4,1.0,1.0); | |
var noise_step = 5.0; | |
// var base_color = vec3<f32>(0.533, 0.533, 0.80); | |
// var noise = simplexNoise3(vec3<f32>(in.frag_coord.x * noise_step, in.frag_coord.y * noise_step, in.frag_coord.z * noise_step)); | |
var noise = simplexNoise3(vec3<f32>(in.color.x * noise_step, in.color.y * noise_step, in.color.z * noise_step)); | |
var threshold = sin(material.time); | |
var alpha = step(noise, threshold); | |
// var edge_color = vec3<f32>(0.0, 1.0, 0.8); | |
var edge_color = output_color * 3.0; | |
var border_step = smoothStep(threshold - 0.2, threshold + 0.2, noise); | |
var dissolve_border = vec3<f32>(edge_color.x * border_step, edge_color.y * border_step, edge_color.z * border_step); | |
var output_color = vec4<f32>( | |
base_color.x + dissolve_border.x, | |
base_color.y + dissolve_border.y, | |
base_color.z + dissolve_border.z, | |
alpha | |
); | |
if (output_color.a == 0.0) { discard; } else { | |
return output_color; | |
} | |
} |
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_view_bindings | |
#import bevy_pbr::mesh_bindings | |
// NOTE: Bindings must come before functions that use them! | |
#import bevy_pbr::mesh_functions | |
#import bevy_shader_utils::simplex_noise_3d | |
#import bevy_shader_utils::simplex_noise_2d | |
struct StandardMaterial { | |
time: f32; | |
// base_color: vec4<f32>; | |
// emissive: vec4<f32>; | |
// perceptual_roughness: f32; | |
// metallic: f32; | |
// reflectance: f32; | |
// // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. | |
// flags: u32; | |
// alpha_cutoff: f32; | |
}; | |
[[group(1), binding(0)]] | |
var<uniform> material: StandardMaterial; | |
struct Vertex { | |
[[location(0)]] position: vec3<f32>; | |
[[location(1)]] normal: vec3<f32>; | |
#ifdef VERTEX_UVS | |
[[location(2)]] uv: vec2<f32>; | |
#endif | |
#ifdef VERTEX_TANGENTS | |
[[location(3)]] tangent: vec4<f32>; | |
#endif | |
#ifdef VERTEX_COLORS | |
[[location(4)]] color: vec4<f32>; | |
#endif | |
#ifdef SKINNED | |
[[location(5)]] joint_indices: vec4<u32>; | |
[[location(6)]] joint_weights: vec4<f32>; | |
#endif | |
}; | |
struct VertexOutput { | |
[[builtin(position)]] clip_position: vec4<f32>; | |
[[location(0)]] world_position: vec4<f32>; | |
[[location(1)]] world_normal: vec3<f32>; | |
#ifdef VERTEX_UVS | |
[[location(2)]] uv: vec2<f32>; | |
#endif | |
#ifdef VERTEX_TANGENTS | |
[[location(3)]] world_tangent: vec4<f32>; | |
#endif | |
#ifdef VERTEX_COLORS | |
[[location(4)]] color: vec4<f32>; | |
#endif | |
}; | |
[[stage(vertex)]] | |
fn vertex(vertex: Vertex) -> VertexOutput { | |
var out: VertexOutput; | |
#ifdef SKINNED | |
var model = skin_model(vertex.joint_indices, vertex.joint_weights); | |
out.world_normal = skin_normals(model, vertex.normal); | |
#else | |
var model = mesh.model; | |
out.world_normal = mesh_normal_local_to_world(vertex.normal); | |
#endif | |
out.world_position = mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0)); | |
#ifdef VERTEX_UVS | |
out.uv = vertex.uv; | |
#endif | |
#ifdef VERTEX_TANGENTS | |
out.world_tangent = mesh_tangent_local_to_world(model, vertex.tangent); | |
#endif | |
#ifdef VERTEX_COLORS | |
out.color = vertex.color; | |
#endif | |
out.clip_position = mesh_position_world_to_clip(out.world_position); | |
out.color = vec4<f32>(vertex.position.x, vertex.position.y, vertex.position.z, 1.0); | |
return out; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment