mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
bevy_pbr2: Add support for not casting/receiving shadows (#2726)
# Objective Allow marking meshes as not casting / receiving shadows. ## Solution - Added `NotShadowCaster` and `NotShadowReceiver` zero-sized type components. - Extract these components into `bool`s in `ExtractedMesh` - Only generate `DrawShadowMesh` `Drawable`s for meshes _without_ `NotShadowCaster` - Add a `u32` bit `flags` member to `MeshUniform` with one flag indicating whether the mesh is a shadow receiver - If a mesh does _not_ have the `NotShadowReceiver` component, then it is a shadow receiver, and so the bit in the `MeshUniform` is set, otherwise it is not set. - Added an example illustrating the functionality. NOTE: I wanted to have the default state of a mesh as being a shadow caster and shadow receiver, hence the `Not*` components. However, I am on the fence about this. I don't want to have a negative performance impact, nor have people wondering why their custom meshes don't have shadows because they forgot to add `ShadowCaster` and `ShadowReceiver` components, but I also really don't like the double negatives the `Not*` approach incurs. What do you think? Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
f368bf7fc7
commit
f4aa3284a8
6 changed files with 286 additions and 35 deletions
|
@ -199,6 +199,10 @@ path = "examples/3d/render_to_texture.rs"
|
||||||
name = "shadow_biases_pipelined"
|
name = "shadow_biases_pipelined"
|
||||||
path = "examples/3d/shadow_biases_pipelined.rs"
|
path = "examples/3d/shadow_biases_pipelined.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "shadow_caster_receiver_pipelined"
|
||||||
|
path = "examples/3d/shadow_caster_receiver_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "spawner"
|
name = "spawner"
|
||||||
path = "examples/3d/spawner.rs"
|
path = "examples/3d/spawner.rs"
|
||||||
|
|
182
examples/3d/shadow_caster_receiver_pipelined.rs
Normal file
182
examples/3d/shadow_caster_receiver_pipelined.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use bevy::{
|
||||||
|
ecs::prelude::*,
|
||||||
|
input::Input,
|
||||||
|
math::{EulerRot, Mat4, Vec3},
|
||||||
|
pbr2::{
|
||||||
|
DirectionalLight, DirectionalLightBundle, NotShadowCaster, NotShadowReceiver, PbrBundle,
|
||||||
|
PointLight, PointLightBundle, StandardMaterial,
|
||||||
|
},
|
||||||
|
prelude::{App, Assets, Handle, KeyCode, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!(
|
||||||
|
"Controls:
|
||||||
|
C - toggle shadow casters (i.e. casters become not, and not casters become casters)
|
||||||
|
R - toggle shadow receivers (i.e. receivers become not, and not receivers become receivers)
|
||||||
|
L - switch between directional and point lights"
|
||||||
|
);
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(toggle_light)
|
||||||
|
.add_system(toggle_shadows)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a 3D scene to test shadow biases and perspective projections
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
let spawn_plane_depth = 500.0f32;
|
||||||
|
let spawn_height = 2.0;
|
||||||
|
let sphere_radius = 0.25;
|
||||||
|
|
||||||
|
let white_handle = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let sphere_handle = meshes.add(Mesh::from(shape::Icosphere {
|
||||||
|
radius: sphere_radius,
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// sphere - initially a caster
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: sphere_handle.clone(),
|
||||||
|
material: materials.add(Color::RED.into()),
|
||||||
|
transform: Transform::from_xyz(-1.0, spawn_height, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// sphere - initially not a caster
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: sphere_handle,
|
||||||
|
material: materials.add(Color::BLUE.into()),
|
||||||
|
transform: Transform::from_xyz(1.0, spawn_height, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(NotShadowCaster);
|
||||||
|
|
||||||
|
// floating plane - initially not a shadow receiver and not a caster
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 20.0 })),
|
||||||
|
material: materials.add(Color::GREEN.into()),
|
||||||
|
transform: Transform::from_xyz(0.0, 1.0, -10.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert_bundle((NotShadowCaster, NotShadowReceiver));
|
||||||
|
|
||||||
|
// lower ground plane - initially a shadow receiver
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 20.0 })),
|
||||||
|
material: white_handle,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Using DirectionalLight");
|
||||||
|
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_xyz(5.0, 5.0, 0.0),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 0.0,
|
||||||
|
range: spawn_plane_depth,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let theta = std::f32::consts::FRAC_PI_4;
|
||||||
|
let light_transform = Mat4::from_euler(EulerRot::ZYX, 0.0, std::f32::consts::FRAC_PI_2, -theta);
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
illuminance: 100000.0,
|
||||||
|
shadow_projection: OrthographicProjection {
|
||||||
|
left: -10.0,
|
||||||
|
right: 10.0,
|
||||||
|
bottom: -10.0,
|
||||||
|
top: 10.0,
|
||||||
|
near: -50.0,
|
||||||
|
far: 50.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_matrix(light_transform),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-5.0, 5.0, 5.0)
|
||||||
|
.looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_light(
|
||||||
|
input: Res<Input<KeyCode>>,
|
||||||
|
mut point_lights: Query<&mut PointLight>,
|
||||||
|
mut directional_lights: Query<&mut DirectionalLight>,
|
||||||
|
) {
|
||||||
|
if input.just_pressed(KeyCode::L) {
|
||||||
|
for mut light in point_lights.iter_mut() {
|
||||||
|
light.intensity = if light.intensity == 0.0 {
|
||||||
|
println!("Using PointLight");
|
||||||
|
100000000.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for mut light in directional_lights.iter_mut() {
|
||||||
|
light.illuminance = if light.illuminance == 0.0 {
|
||||||
|
println!("Using DirectionalLight");
|
||||||
|
100000.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_shadows(
|
||||||
|
mut commands: Commands,
|
||||||
|
input: Res<Input<KeyCode>>,
|
||||||
|
queries: QuerySet<(
|
||||||
|
Query<Entity, (With<Handle<Mesh>>, With<NotShadowCaster>)>,
|
||||||
|
Query<Entity, (With<Handle<Mesh>>, With<NotShadowReceiver>)>,
|
||||||
|
Query<Entity, (With<Handle<Mesh>>, Without<NotShadowCaster>)>,
|
||||||
|
Query<Entity, (With<Handle<Mesh>>, Without<NotShadowReceiver>)>,
|
||||||
|
)>,
|
||||||
|
) {
|
||||||
|
if input.just_pressed(KeyCode::C) {
|
||||||
|
println!("Toggling casters");
|
||||||
|
for entity in queries.q0().iter() {
|
||||||
|
commands.entity(entity).remove::<NotShadowCaster>();
|
||||||
|
}
|
||||||
|
for entity in queries.q2().iter() {
|
||||||
|
commands.entity(entity).insert(NotShadowCaster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::R) {
|
||||||
|
println!("Toggling receivers");
|
||||||
|
for entity in queries.q1().iter() {
|
||||||
|
commands.entity(entity).remove::<NotShadowReceiver>();
|
||||||
|
}
|
||||||
|
for entity in queries.q3().iter() {
|
||||||
|
commands.entity(entity).insert(NotShadowReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,6 +107,7 @@ Example | File | Description
|
||||||
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
||||||
`pbr_pipelined` | [`3d/pbr_pipelined.rs`](./3d/pbr_pipelined.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
`pbr_pipelined` | [`3d/pbr_pipelined.rs`](./3d/pbr_pipelined.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
||||||
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to texture
|
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to texture
|
||||||
|
`shadow_caster_receiver_pipelined` | [`3d/shadow_caster_receiver_pipelined.rs`](./3d/shadow_caster_receiver_pipelined.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
||||||
`shadow_biases_pipelined` | [`3d/shadow_biases_pipelined.rs`](./3d/shadow_biases_pipelined.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
`shadow_biases_pipelined` | [`3d/shadow_biases_pipelined.rs`](./3d/shadow_biases_pipelined.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
||||||
`spawner` | [`3d/spawner.rs`](./3d/spawner.rs) | Renders a large number of cubes with changing position and material
|
`spawner` | [`3d/spawner.rs`](./3d/spawner.rs) | Renders a large number of cubes with changing position and material
|
||||||
`texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials
|
`texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials
|
||||||
|
|
|
@ -151,3 +151,8 @@ impl Default for AmbientLight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add this component to make a `Mesh` not cast shadows
|
||||||
|
pub struct NotShadowCaster;
|
||||||
|
/// Add this component to make a `Mesh` not receive shadows
|
||||||
|
pub struct NotShadowReceiver;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
mod light;
|
mod light;
|
||||||
pub use light::*;
|
pub use light::*;
|
||||||
|
|
||||||
use crate::{StandardMaterial, StandardMaterialUniformData};
|
use crate::{NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData};
|
||||||
use bevy_asset::{Assets, Handle};
|
use bevy_asset::{Assets, Handle};
|
||||||
use bevy_core_pipeline::Transparent3dPhase;
|
use bevy_core_pipeline::Transparent3dPhase;
|
||||||
use bevy_ecs::{prelude::*, system::SystemState};
|
use bevy_ecs::{prelude::*, system::SystemState};
|
||||||
|
@ -120,11 +120,11 @@ impl FromWorld for PbrShaders {
|
||||||
let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[BindGroupLayoutEntry {
|
entries: &[BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStage::VERTEX,
|
visibility: ShaderStage::VERTEX | ShaderStage::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Uniform,
|
ty: BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: true,
|
has_dynamic_offset: true,
|
||||||
min_binding_size: BufferSize::new(Mat4::std140_size_static() as u64),
|
min_binding_size: BufferSize::new(80),
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}],
|
}],
|
||||||
|
@ -374,6 +374,8 @@ struct ExtractedMesh {
|
||||||
mesh: Handle<Mesh>,
|
mesh: Handle<Mesh>,
|
||||||
transform_binding_offset: u32,
|
transform_binding_offset: u32,
|
||||||
material_handle: Handle<StandardMaterial>,
|
material_handle: Handle<StandardMaterial>,
|
||||||
|
casts_shadows: bool,
|
||||||
|
receives_shadows: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedMeshes {
|
pub struct ExtractedMeshes {
|
||||||
|
@ -385,10 +387,23 @@ pub fn extract_meshes(
|
||||||
meshes: Res<Assets<Mesh>>,
|
meshes: Res<Assets<Mesh>>,
|
||||||
materials: Res<Assets<StandardMaterial>>,
|
materials: Res<Assets<StandardMaterial>>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
query: Query<(&GlobalTransform, &Handle<Mesh>, &Handle<StandardMaterial>)>,
|
query: Query<(
|
||||||
|
&GlobalTransform,
|
||||||
|
&Handle<Mesh>,
|
||||||
|
&Handle<StandardMaterial>,
|
||||||
|
Option<&NotShadowCaster>,
|
||||||
|
Option<&NotShadowReceiver>,
|
||||||
|
)>,
|
||||||
) {
|
) {
|
||||||
let mut extracted_meshes = Vec::new();
|
let mut extracted_meshes = Vec::new();
|
||||||
for (transform, mesh_handle, material_handle) in query.iter() {
|
for (
|
||||||
|
transform,
|
||||||
|
mesh_handle,
|
||||||
|
material_handle,
|
||||||
|
maybe_not_shadow_caster,
|
||||||
|
maybe_not_shadow_receiver,
|
||||||
|
) in query.iter()
|
||||||
|
{
|
||||||
if !meshes.contains(mesh_handle) {
|
if !meshes.contains(mesh_handle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -419,6 +434,10 @@ pub fn extract_meshes(
|
||||||
mesh: mesh_handle.clone_weak(),
|
mesh: mesh_handle.clone_weak(),
|
||||||
transform_binding_offset: 0,
|
transform_binding_offset: 0,
|
||||||
material_handle: material_handle.clone_weak(),
|
material_handle: material_handle.clone_weak(),
|
||||||
|
// NOTE: Double-negative is so that meshes cast and receive shadows by default
|
||||||
|
// Not not shadow caster means that this mesh is a shadow caster
|
||||||
|
casts_shadows: maybe_not_shadow_caster.is_none(),
|
||||||
|
receives_shadows: maybe_not_shadow_receiver.is_none(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -435,9 +454,25 @@ struct MeshDrawInfo {
|
||||||
material_bind_group_key: FrameSlabMapKey<BufferId, BindGroup>,
|
material_bind_group_key: FrameSlabMapKey<BufferId, BindGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, AsStd140)]
|
||||||
|
pub struct MeshUniform {
|
||||||
|
model: Mat4,
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl!
|
||||||
|
bitflags::bitflags! {
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct MeshFlags: u32 {
|
||||||
|
const SHADOW_RECEIVER = (1 << 0);
|
||||||
|
const NONE = 0;
|
||||||
|
const UNINITIALIZED = 0xFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MeshMeta {
|
pub struct MeshMeta {
|
||||||
transform_uniforms: DynamicUniformVec<Mat4>,
|
transform_uniforms: DynamicUniformVec<MeshUniform>,
|
||||||
material_bind_groups: FrameSlabMap<BufferId, BindGroup>,
|
material_bind_groups: FrameSlabMap<BufferId, BindGroup>,
|
||||||
mesh_transform_bind_group: FrameSlabMap<BufferId, BindGroup>,
|
mesh_transform_bind_group: FrameSlabMap<BufferId, BindGroup>,
|
||||||
mesh_transform_bind_group_key: Option<FrameSlabMapKey<BufferId, BindGroup>>,
|
mesh_transform_bind_group_key: Option<FrameSlabMapKey<BufferId, BindGroup>>,
|
||||||
|
@ -453,8 +488,15 @@ pub fn prepare_meshes(
|
||||||
.transform_uniforms
|
.transform_uniforms
|
||||||
.reserve_and_clear(extracted_meshes.meshes.len(), &render_device);
|
.reserve_and_clear(extracted_meshes.meshes.len(), &render_device);
|
||||||
for extracted_mesh in extracted_meshes.meshes.iter_mut() {
|
for extracted_mesh in extracted_meshes.meshes.iter_mut() {
|
||||||
extracted_mesh.transform_binding_offset =
|
let flags = if extracted_mesh.receives_shadows {
|
||||||
mesh_meta.transform_uniforms.push(extracted_mesh.transform);
|
MeshFlags::SHADOW_RECEIVER
|
||||||
|
} else {
|
||||||
|
MeshFlags::NONE
|
||||||
|
};
|
||||||
|
extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform {
|
||||||
|
model: extracted_mesh.transform,
|
||||||
|
flags: flags.bits,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh_meta
|
mesh_meta
|
||||||
|
@ -694,12 +736,14 @@ pub fn queue_meshes(
|
||||||
for view_light_entity in view_lights.lights.iter().copied() {
|
for view_light_entity in view_lights.lights.iter().copied() {
|
||||||
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
|
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
|
||||||
// TODO: this should only queue up meshes that are actually visible by each "light view"
|
// TODO: this should only queue up meshes that are actually visible by each "light view"
|
||||||
for i in 0..extracted_meshes.meshes.len() {
|
for (i, mesh) in extracted_meshes.meshes.iter().enumerate() {
|
||||||
shadow_phase.add(Drawable {
|
if mesh.casts_shadows {
|
||||||
draw_function: draw_shadow_mesh,
|
shadow_phase.add(Drawable {
|
||||||
draw_key: i,
|
draw_function: draw_shadow_mesh,
|
||||||
sort_key: 0, // TODO: sort back-to-front
|
draw_key: i,
|
||||||
})
|
sort_key: 0, // TODO: sort back-to-front
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,19 @@ struct View {
|
||||||
view_proj: mat4x4<f32>;
|
view_proj: mat4x4<f32>;
|
||||||
world_position: vec3<f32>;
|
world_position: vec3<f32>;
|
||||||
};
|
};
|
||||||
[[group(0), binding(0)]]
|
|
||||||
var view: View;
|
|
||||||
|
|
||||||
|
|
||||||
[[block]]
|
[[block]]
|
||||||
struct Mesh {
|
struct Mesh {
|
||||||
transform: mat4x4<f32>;
|
model: mat4x4<f32>;
|
||||||
|
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
||||||
|
flags: u32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u;
|
||||||
|
|
||||||
|
[[group(0), binding(0)]]
|
||||||
|
var view: View;
|
||||||
[[group(1), binding(0)]]
|
[[group(1), binding(0)]]
|
||||||
var mesh: Mesh;
|
var mesh: Mesh;
|
||||||
|
|
||||||
|
@ -30,7 +35,7 @@ struct VertexOutput {
|
||||||
|
|
||||||
[[stage(vertex)]]
|
[[stage(vertex)]]
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
let world_position = mesh.transform * vec4<f32>(vertex.position, 1.0);
|
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.uv = vertex.uv;
|
out.uv = vertex.uv;
|
||||||
|
@ -38,7 +43,7 @@ fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
out.clip_position = view.view_proj * world_position;
|
out.clip_position = view.view_proj * world_position;
|
||||||
// FIXME: The inverse transpose of the model matrix should be used to correctly handle scaling
|
// FIXME: The inverse transpose of the model matrix should be used to correctly handle scaling
|
||||||
// of normals
|
// of normals
|
||||||
out.world_normal = mat3x3<f32>(mesh.transform.x.xyz, mesh.transform.y.xyz, mesh.transform.z.xyz) * vertex.normal;
|
out.world_normal = mat3x3<f32>(mesh.model.x.xyz, mesh.model.y.xyz, mesh.model.z.xyz) * vertex.normal;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,10 +88,17 @@ struct StandardMaterial {
|
||||||
perceptual_roughness: f32;
|
perceptual_roughness: f32;
|
||||||
metallic: f32;
|
metallic: f32;
|
||||||
reflectance: f32;
|
reflectance: f32;
|
||||||
// 'flags' is a bit field indicating various option. uint is 32 bits so we have up to 32 options.
|
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
||||||
flags: u32;
|
flags: u32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
struct PointLight {
|
struct PointLight {
|
||||||
color: vec4<f32>;
|
color: vec4<f32>;
|
||||||
// projection: mat4x4<f32>;
|
// projection: mat4x4<f32>;
|
||||||
|
@ -118,13 +130,6 @@ struct Lights {
|
||||||
n_directional_lights: u32;
|
n_directional_lights: u32;
|
||||||
};
|
};
|
||||||
|
|
||||||
let FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u;
|
|
||||||
let FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u;
|
|
||||||
let FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u;
|
|
||||||
let FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u;
|
|
||||||
let FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
|
|
||||||
let FLAGS_UNLIT_BIT: u32 = 32u;
|
|
||||||
|
|
||||||
|
|
||||||
[[group(0), binding(1)]]
|
[[group(0), binding(1)]]
|
||||||
var lights: Lights;
|
var lights: Lights;
|
||||||
|
@ -463,22 +468,22 @@ struct FragmentInput {
|
||||||
[[stage(fragment)]]
|
[[stage(fragment)]]
|
||||||
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||||
var output_color: vec4<f32> = material.base_color;
|
var output_color: vec4<f32> = material.base_color;
|
||||||
if ((material.flags & FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
|
if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
|
||||||
output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv);
|
output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
// // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||||
if ((material.flags & FLAGS_UNLIT_BIT) == 0u) {
|
if ((material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||||
// TODO use .a for exposure compensation in HDR
|
// TODO use .a for exposure compensation in HDR
|
||||||
var emissive: vec4<f32> = material.emissive;
|
var emissive: vec4<f32> = material.emissive;
|
||||||
if ((material.flags & FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) {
|
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);
|
emissive = vec4<f32>(emissive.rgb * textureSample(emissive_texture, emissive_sampler, in.uv).rgb, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate non-linear roughness from linear perceptualRoughness
|
// calculate non-linear roughness from linear perceptualRoughness
|
||||||
var metallic: f32 = material.metallic;
|
var metallic: f32 = material.metallic;
|
||||||
var perceptual_roughness: f32 = material.perceptual_roughness;
|
var perceptual_roughness: f32 = material.perceptual_roughness;
|
||||||
if ((material.flags & FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
if ((material.flags & STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
||||||
let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, in.uv);
|
let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, in.uv);
|
||||||
// Sampling from GLTF standard channels for now
|
// Sampling from GLTF standard channels for now
|
||||||
metallic = metallic * metallic_roughness.b;
|
metallic = metallic * metallic_roughness.b;
|
||||||
|
@ -487,7 +492,7 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||||
let roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
let roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
||||||
|
|
||||||
var occlusion: f32 = 1.0;
|
var occlusion: f32 = 1.0;
|
||||||
if ((material.flags & FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
|
if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
|
||||||
occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r;
|
occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +505,7 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||||
// vec3 B = cross(N, T) * v_WorldTangent.w;
|
// vec3 B = cross(N, T) * v_WorldTangent.w;
|
||||||
// # endif
|
// # endif
|
||||||
|
|
||||||
if ((material.flags & FLAGS_DOUBLE_SIDED_BIT) != 0u) {
|
if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) {
|
||||||
if (!in.is_front) {
|
if (!in.is_front) {
|
||||||
N = -N;
|
N = -N;
|
||||||
}
|
}
|
||||||
|
@ -543,13 +548,23 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||||
let n_directional_lights = i32(lights.n_directional_lights);
|
let n_directional_lights = i32(lights.n_directional_lights);
|
||||||
for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
|
for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
|
||||||
let light = lights.point_lights[i];
|
let light = lights.point_lights[i];
|
||||||
let shadow = fetch_point_shadow(i, in.world_position, in.world_normal);
|
var shadow: f32;
|
||||||
|
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) {
|
||||||
|
shadow = fetch_point_shadow(i, in.world_position, in.world_normal);
|
||||||
|
} else {
|
||||||
|
shadow = 1.0;
|
||||||
|
}
|
||||||
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
||||||
light_accum = light_accum + light_contrib * shadow;
|
light_accum = light_accum + light_contrib * shadow;
|
||||||
}
|
}
|
||||||
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
|
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
|
||||||
let light = lights.directional_lights[i];
|
let light = lights.directional_lights[i];
|
||||||
let shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
|
var shadow: f32;
|
||||||
|
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) {
|
||||||
|
shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
|
||||||
|
} else {
|
||||||
|
shadow = 1.0;
|
||||||
|
}
|
||||||
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
||||||
light_accum = light_accum + light_contrib * shadow;
|
light_accum = light_accum + light_contrib * shadow;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue