bevy/pipelined/bevy_pbr2/src/render/light.rs

706 lines
26 KiB
Rust
Raw Normal View History

use crate::{AmbientLight, DirectionalLight, ExtractedMeshes, MeshMeta, PbrShaders, PointLight};
2021-06-02 02:59:17 +00:00
use bevy_ecs::{prelude::*, system::SystemState};
2021-07-01 23:48:55 +00:00
use bevy_math::{const_vec3, Mat4, Vec3, Vec4};
2021-06-02 02:59:17 +00:00
use bevy_render2::{
camera::CameraProjection,
2021-06-02 02:59:17 +00:00
color::Color,
core_pipeline::Transparent3dPhase,
mesh::Mesh,
render_asset::RenderAssets,
2021-06-02 02:59:17 +00:00
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
2021-06-21 23:28:52 +00:00
render_resource::*,
renderer::{RenderContext, RenderDevice},
2021-06-02 02:59:17 +00:00
texture::*,
2021-07-01 23:54:58 +00:00
view::{ExtractedView, ViewUniformOffset},
2021-06-02 02:59:17 +00:00
};
use bevy_transform::components::GlobalTransform;
use crevice::std140::AsStd140;
use std::num::NonZeroU32;
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
pub struct ExtractedAmbientLight {
color: Color,
brightness: f32,
}
2021-06-02 02:59:17 +00:00
pub struct ExtractedPointLight {
color: Color,
intensity: f32,
range: f32,
radius: f32,
transform: GlobalTransform,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
pub struct ExtractedDirectionalLight {
color: Color,
illuminance: f32,
direction: Vec3,
projection: Mat4,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
2021-06-02 02:59:17 +00:00
}
#[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuPointLight {
2021-06-02 02:59:17 +00:00
color: Vec4,
2021-07-01 23:48:55 +00:00
// proj: Mat4,
2021-06-02 02:59:17 +00:00
position: Vec3,
2021-07-01 23:48:55 +00:00
inverse_square_range: f32,
radius: f32,
near: f32,
far: f32,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
#[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuDirectionalLight {
view_projection: Mat4,
color: Vec4,
dir_to_light: Vec3,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
2021-06-02 02:59:17 +00:00
}
#[repr(C)]
#[derive(Copy, Clone, Debug, AsStd140)]
pub struct GpuLights {
// TODO: this comes first to work around a WGSL alignment issue. We need to solve this issue before releasing the renderer rework
point_lights: [GpuPointLight; MAX_POINT_LIGHTS],
directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
ambient_color: Vec4,
n_point_lights: u32,
n_directional_lights: u32,
2021-06-02 02:59:17 +00:00
}
// NOTE: this must be kept in sync with the same constants in pbr.frag
2021-07-01 23:54:58 +00:00
pub const MAX_POINT_LIGHTS: usize = 10;
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
pub const POINT_SHADOW_SIZE: Extent3d = Extent3d {
2021-06-02 02:59:17 +00:00
width: 1024,
height: 1024,
depth_or_array_layers: (6 * MAX_POINT_LIGHTS) as u32,
};
pub const DIRECTIONAL_SHADOW_SIZE: Extent3d = Extent3d {
width: 4096,
height: 4096,
depth_or_array_layers: MAX_DIRECTIONAL_LIGHTS as u32,
2021-06-02 02:59:17 +00:00
};
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
pub struct ShadowShaders {
2021-06-21 23:28:52 +00:00
pub pipeline: RenderPipeline,
pub view_layout: BindGroupLayout,
pub point_light_sampler: Sampler,
pub directional_light_sampler: Sampler,
2021-06-02 02:59:17 +00:00
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
impl FromWorld for ShadowShaders {
fn from_world(world: &mut World) -> Self {
2021-06-21 23:28:52 +00:00
let render_device = world.get_resource::<RenderDevice>().unwrap();
let pbr_shaders = world.get_resource::<PbrShaders>().unwrap();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
// View
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStage::VERTEX | ShaderStage::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(80),
2021-06-21 23:28:52 +00:00
},
count: None,
2021-06-02 02:59:17 +00:00
},
],
2021-06-21 23:28:52 +00:00
label: None,
});
2021-06-02 02:59:17 +00:00
2021-06-21 23:28:52 +00:00
let pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: None,
push_constant_ranges: &[],
bind_group_layouts: &[&view_layout, &pbr_shaders.mesh_layout],
2021-06-21 23:28:52 +00:00
});
2021-06-02 02:59:17 +00:00
2021-06-21 23:28:52 +00:00
let pipeline = render_device.create_render_pipeline(&RenderPipelineDescriptor {
label: None,
vertex: VertexState {
buffers: &[VertexBufferLayout {
array_stride: 32,
step_mode: InputStepMode::Vertex,
attributes: &[
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
],
}],
module: &pbr_shaders.shader_module,
entry_point: "vertex",
2021-06-21 23:28:52 +00:00
},
fragment: None,
2021-06-02 02:59:17 +00:00
depth_stencil: Some(DepthStencilState {
format: SHADOW_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::LessEqual,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 2,
slope_scale: 2.0,
clamp: 0.0,
},
}),
2021-06-21 23:28:52 +00:00
layout: Some(&pipeline_layout),
multisample: MultisampleState::default(),
2021-06-02 02:59:17 +00:00
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
2021-06-21 23:28:52 +00:00
strip_index_format: None,
front_face: FrontFace::Ccw,
2021-07-01 23:48:55 +00:00
cull_mode: None,
2021-06-21 23:28:52 +00:00
polygon_mode: PolygonMode::Fill,
2021-06-02 02:59:17 +00:00
clamp_depth: false,
2021-06-21 23:28:52 +00:00
conservative: false,
2021-06-02 02:59:17 +00:00
},
2021-06-21 23:28:52 +00:00
});
2021-06-02 02:59:17 +00:00
ShadowShaders {
pipeline,
2021-06-21 23:28:52 +00:00
view_layout,
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest,
compare: Some(CompareFunction::LessEqual),
..Default::default()
}),
directional_light_sampler: render_device.create_sampler(&SamplerDescriptor {
2021-06-02 02:59:17 +00:00
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest,
2021-06-21 23:28:52 +00:00
compare: Some(CompareFunction::LessEqual),
2021-06-02 02:59:17 +00:00
..Default::default()
}),
}
}
}
// TODO: ultimately these could be filtered down to lights relevant to actual views
pub fn extract_lights(
mut commands: Commands,
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
ambient_light: Res<AmbientLight>,
point_lights: Query<(Entity, &PointLight, &GlobalTransform)>,
directional_lights: Query<(Entity, &DirectionalLight, &GlobalTransform)>,
2021-06-02 02:59:17 +00:00
) {
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
commands.insert_resource(ExtractedAmbientLight {
color: ambient_light.color,
brightness: ambient_light.brightness,
});
for (entity, point_light, transform) in point_lights.iter() {
2021-06-02 02:59:17 +00:00
commands.get_or_spawn(entity).insert(ExtractedPointLight {
color: point_light.color,
intensity: point_light.intensity,
range: point_light.range,
radius: point_light.radius,
2021-07-02 01:05:20 +00:00
transform: *transform,
shadow_depth_bias: point_light.shadow_depth_bias,
shadow_normal_bias: point_light.shadow_normal_bias,
2021-06-02 02:59:17 +00:00
});
}
for (entity, directional_light, transform) in directional_lights.iter() {
commands
.get_or_spawn(entity)
.insert(ExtractedDirectionalLight {
color: directional_light.color,
illuminance: directional_light.illuminance,
direction: transform.forward(),
projection: directional_light.shadow_projection.get_projection_matrix(),
shadow_depth_bias: directional_light.shadow_depth_bias,
shadow_normal_bias: directional_light.shadow_normal_bias,
});
}
2021-06-02 02:59:17 +00:00
}
2021-07-01 23:48:55 +00:00
// Can't do `Vec3::Y * -1.0` because mul isn't const
const NEGATIVE_X: Vec3 = const_vec3!([-1.0, 0.0, 0.0]);
const NEGATIVE_Y: Vec3 = const_vec3!([0.0, -1.0, 0.0]);
const NEGATIVE_Z: Vec3 = const_vec3!([0.0, 0.0, -1.0]);
struct CubeMapFace {
target: Vec3,
up: Vec3,
}
// see https://www.khronos.org/opengl/wiki/Cubemap_Texture
const CUBE_MAP_FACES: [CubeMapFace; 6] = [
// 0 GL_TEXTURE_CUBE_MAP_POSITIVE_X
CubeMapFace {
target: NEGATIVE_X,
up: NEGATIVE_Y,
},
// 1 GL_TEXTURE_CUBE_MAP_NEGATIVE_X
CubeMapFace {
target: Vec3::X,
up: NEGATIVE_Y,
},
// 2 GL_TEXTURE_CUBE_MAP_POSITIVE_Y
CubeMapFace {
target: NEGATIVE_Y,
up: Vec3::Z,
},
// 3 GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
CubeMapFace {
target: Vec3::Y,
up: NEGATIVE_Z,
},
// 4 GL_TEXTURE_CUBE_MAP_POSITIVE_Z
CubeMapFace {
target: NEGATIVE_Z,
up: NEGATIVE_Y,
},
// 5 GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
CubeMapFace {
target: Vec3::Z,
up: NEGATIVE_Y,
},
];
fn face_index_to_name(face_index: usize) -> &'static str {
match face_index {
0 => "+x",
1 => "-x",
2 => "+y",
3 => "-y",
4 => "+z",
5 => "-z",
_ => "invalid",
}
}
2021-06-02 02:59:17 +00:00
pub struct ViewLight {
2021-07-01 23:48:55 +00:00
pub depth_texture_view: TextureView,
pub pass_name: String,
2021-06-02 02:59:17 +00:00
}
pub struct ViewLights {
pub point_light_depth_texture: Texture,
pub point_light_depth_texture_view: TextureView,
pub directional_light_depth_texture: Texture,
pub directional_light_depth_texture_view: TextureView,
2021-06-02 02:59:17 +00:00
pub lights: Vec<Entity>,
pub gpu_light_binding_index: u32,
}
#[derive(Default)]
pub struct LightMeta {
pub view_gpu_lights: DynamicUniformVec<GpuLights>,
2021-06-21 23:28:52 +00:00
pub shadow_view_bind_group: Option<BindGroup>,
2021-06-02 02:59:17 +00:00
}
2021-07-08 03:01:41 +00:00
#[allow(clippy::too_many_arguments)]
2021-06-02 02:59:17 +00:00
pub fn prepare_lights(
mut commands: Commands,
mut texture_cache: ResMut<TextureCache>,
2021-06-21 23:28:52 +00:00
render_device: Res<RenderDevice>,
2021-06-02 02:59:17 +00:00
mut light_meta: ResMut<LightMeta>,
views: Query<Entity, With<RenderPhase<Transparent3dPhase>>>,
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
ambient_light: Res<ExtractedAmbientLight>,
point_lights: Query<&ExtractedPointLight>,
directional_lights: Query<&ExtractedDirectionalLight>,
2021-06-02 02:59:17 +00:00
) {
// PERF: view.iter().count() could be views.iter().len() if we implemented ExactSizeIterator for archetype-only filters
light_meta
.view_gpu_lights
2021-06-21 23:28:52 +00:00
.reserve_and_clear(views.iter().count(), &render_device);
2021-06-02 02:59:17 +00:00
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
let ambient_color = ambient_light.color.as_rgba_linear() * ambient_light.brightness;
2021-06-02 02:59:17 +00:00
// set up light data for each view
for entity in views.iter() {
let point_light_depth_texture = texture_cache.get(
&render_device,
TextureDescriptor {
size: POINT_SHADOW_SIZE,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: SHADOW_FORMAT,
usage: TextureUsage::RENDER_ATTACHMENT | TextureUsage::SAMPLED,
label: None,
},
);
let directional_light_depth_texture = texture_cache.get(
2021-06-21 23:28:52 +00:00
&render_device,
2021-06-02 02:59:17 +00:00
TextureDescriptor {
size: DIRECTIONAL_SHADOW_SIZE,
2021-06-02 02:59:17 +00:00
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: SHADOW_FORMAT,
usage: TextureUsage::RENDER_ATTACHMENT | TextureUsage::SAMPLED,
2021-06-21 23:28:52 +00:00
label: None,
2021-06-02 02:59:17 +00:00
},
);
let mut view_lights = Vec::new();
let mut gpu_lights = GpuLights {
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
ambient_color: ambient_color.into(),
n_point_lights: point_lights.iter().len() as u32,
n_directional_lights: directional_lights.iter().len() as u32,
point_lights: [GpuPointLight::default(); MAX_POINT_LIGHTS],
directional_lights: [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS],
2021-06-02 02:59:17 +00:00
};
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
for (light_index, light) in point_lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
let projection =
Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, light.range);
2021-06-02 02:59:17 +00:00
2021-07-01 23:48:55 +00:00
// ignore scale because we don't want to effectively scale light radius and range
// by applying those as a view transform to shadow map rendering of objects
// and ignore rotation because we want the shadow map projections to align with the axes
let view_translation = GlobalTransform::from_translation(light.transform.translation);
for (face_index, CubeMapFace { target, up }) in CUBE_MAP_FACES.iter().enumerate() {
// use the cubemap projection direction
let view_rotation = GlobalTransform::identity().looking_at(*target, *up);
let depth_texture_view =
point_light_depth_texture
2021-07-01 23:48:55 +00:00
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: (light_index * 6 + face_index) as u32,
array_layer_count: NonZeroU32::new(1),
});
let view_light_entity = commands
.spawn()
.insert_bundle((
ViewLight {
depth_texture_view,
pass_name: format!(
"shadow pass point light {} {}",
light_index,
face_index_to_name(face_index)
),
},
2021-07-01 23:48:55 +00:00
ExtractedView {
width: POINT_SHADOW_SIZE.width,
height: POINT_SHADOW_SIZE.height,
2021-07-01 23:48:55 +00:00
transform: view_translation * view_rotation,
projection,
},
RenderPhase::<ShadowPhase>::default(),
))
.id();
view_lights.push(view_light_entity);
}
gpu_lights.point_lights[light_index] = GpuPointLight {
2021-06-02 02:59:17 +00:00
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
bevy_pbr2: Add support for most of the StandardMaterial textures (#4) * bevy_pbr2: Add support for most of the StandardMaterial textures Normal maps are not included here as they require tangents in a vertex attribute. * bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init * texture_pipelined: Add a light to the scene so we can see stuff * WIP bevy_pbr2: back to front sorting hack * bevy_pbr2: Uniform control flow for texture sampling in pbr.frag From 'fintelia' on the Bevy Render Rework Round 2 discussion: "My understanding is that GPUs these days never use the "execute both branches and select the result" strategy. Rather, what they do is evaluate the branch condition on all threads of a warp, and jump over it if all of them evaluate to false. If even a single thread needs to execute the if statement body, however, then the remaining threads are paused until that is completed." * bevy_pbr2: Simplify texture and sampler names The StandardMaterial_ prefix is no longer needed * bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now * bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform * bevy_pbr2: Add pbr_pipelined example * Fix view vector in pbr frag to work in ortho * bevy_pbr2: Use a 90 degree y fov and light range projection for lights * bevy_pbr2: Add AmbientLight resource * bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader * bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection The uniform contains the view_projection matrix so this was incorrect. * bevy_pbr2: PointLight is an OmniLight as it has a radius * bevy_pbr2: Factoring out duplicated code * bevy_pbr2: Implement RenderAsset for StandardMaterial * Remove unnecessary texture and sampler clones * fix comment formatting * remove redundant Buffer:from * Don't extract meshes when their material textures aren't ready * make missing textures in the queue step an error Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-06-27 23:10:23 +00:00
color: (light.color.as_rgba_linear() * light.intensity).into(),
2021-07-02 01:05:20 +00:00
radius: light.radius,
position: light.transform.translation,
2021-07-01 23:48:55 +00:00
inverse_square_range: 1.0 / (light.range * light.range),
near: 0.1,
far: light.range,
// proj: projection,
shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias,
2021-06-02 02:59:17 +00:00
};
}
for (i, light) in directional_lights
.iter()
.enumerate()
.take(MAX_DIRECTIONAL_LIGHTS)
{
// direction is negated to be ready for N.L
let dir_to_light = -light.direction;
// convert from illuminance (lux) to candelas
//
// exposure is hard coded at the moment but should be replaced
// by values coming from the camera
// see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings
const APERTURE: f32 = 4.0;
const SHUTTER_SPEED: f32 = 1.0 / 250.0;
const SENSITIVITY: f32 = 100.0;
let ev100 =
f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0);
let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
let intensity = light.illuminance * exposure;
// NOTE: A directional light seems to have to have an eye position on the line along the direction of the light
// through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this.
let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y);
// NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
let projection = light.projection;
gpu_lights.directional_lights[i] = GpuDirectionalLight {
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
color: (light.color.as_rgba_linear() * intensity).into(),
dir_to_light,
// NOTE: * view is correct, it should not be view.inverse() here
view_projection: projection * view,
shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias,
};
let depth_texture_view =
directional_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: i as u32,
array_layer_count: NonZeroU32::new(1),
});
let view_light_entity = commands
.spawn()
.insert_bundle((
ViewLight {
depth_texture_view,
pass_name: format!("shadow pass directional light {}", i),
},
ExtractedView {
width: DIRECTIONAL_SHADOW_SIZE.width,
height: DIRECTIONAL_SHADOW_SIZE.height,
transform: GlobalTransform::from_matrix(view.inverse()),
projection,
},
RenderPhase::<ShadowPhase>::default(),
))
.id();
view_lights.push(view_light_entity);
}
let point_light_depth_texture_view =
point_light_depth_texture
2021-07-01 23:48:55 +00:00
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::CubeArray),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
2021-07-02 01:05:20 +00:00
base_array_layer: 0,
2021-07-01 23:48:55 +00:00
array_layer_count: None,
});
let directional_light_depth_texture_view = directional_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::D2Array),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
});
2021-07-01 23:48:55 +00:00
2021-06-02 02:59:17 +00:00
commands.entity(entity).insert(ViewLights {
point_light_depth_texture: point_light_depth_texture.texture,
point_light_depth_texture_view,
directional_light_depth_texture: directional_light_depth_texture.texture,
directional_light_depth_texture_view,
2021-06-02 02:59:17 +00:00
lights: view_lights,
gpu_light_binding_index: light_meta.view_gpu_lights.push(gpu_lights),
});
}
light_meta
.view_gpu_lights
2021-06-21 23:28:52 +00:00
.write_to_staging_buffer(&render_device);
2021-06-02 02:59:17 +00:00
}
pub struct ShadowPhase;
pub struct ShadowPassNode {
main_view_query: QueryState<&'static ViewLights>,
view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase<ShadowPhase>)>,
}
impl ShadowPassNode {
pub const IN_VIEW: &'static str = "view";
pub fn new(world: &mut World) -> Self {
Self {
main_view_query: QueryState::new(world),
view_light_query: QueryState::new(world),
}
}
}
impl Node for ShadowPassNode {
fn input(&self) -> Vec<SlotInfo> {
vec![SlotInfo::new(ShadowPassNode::IN_VIEW, SlotType::Entity)]
}
fn update(&mut self, world: &mut World) {
self.main_view_query.update_archetypes(world);
self.view_light_query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
2021-06-21 23:28:52 +00:00
render_context: &mut RenderContext,
2021-06-02 02:59:17 +00:00
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
2021-07-02 01:05:20 +00:00
if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) {
for view_light_entity in view_lights.lights.iter().copied() {
let (view_light, shadow_phase) = self
.view_light_query
.get_manual(world, view_light_entity)
.unwrap();
2021-06-21 23:28:52 +00:00
let pass_descriptor = RenderPassDescriptor {
label: Some(&view_light.pass_name),
2021-06-21 23:28:52 +00:00
color_attachments: &[],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
2021-07-01 23:48:55 +00:00
view: &view_light.depth_texture_view,
depth_ops: Some(Operations {
load: LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
2021-06-02 02:59:17 +00:00
}),
};
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
2021-06-21 23:28:52 +00:00
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for drawable in shadow_phase.drawn_things.iter() {
let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap();
draw_function.draw(
world,
&mut tracked_pass,
view_light_entity,
drawable.draw_key,
drawable.sort_key,
);
}
}
2021-06-02 02:59:17 +00:00
}
Ok(())
}
}
2021-06-21 23:28:52 +00:00
type DrawShadowMeshParams<'s, 'w> = (
Res<'w, ShadowShaders>,
Res<'w, ExtractedMeshes>,
Res<'w, LightMeta>,
Res<'w, MeshMeta>,
Res<'w, RenderAssets<Mesh>>,
2021-06-21 23:28:52 +00:00
Query<'w, 's, &'w ViewUniformOffset>,
2021-06-02 02:59:17 +00:00
);
pub struct DrawShadowMesh {
2021-06-21 23:28:52 +00:00
params: SystemState<DrawShadowMeshParams<'static, 'static>>,
2021-06-02 02:59:17 +00:00
}
impl DrawShadowMesh {
pub fn new(world: &mut World) -> Self {
Self {
params: SystemState::new(world),
}
}
}
impl Draw for DrawShadowMesh {
2021-06-21 23:28:52 +00:00
fn draw<'w>(
2021-06-02 02:59:17 +00:00
&mut self,
2021-06-21 23:28:52 +00:00
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
2021-06-02 02:59:17 +00:00
view: Entity,
draw_key: usize,
_sort_key: usize,
) {
let (shadow_shaders, extracted_meshes, light_meta, mesh_meta, meshes, views) =
2021-06-21 23:28:52 +00:00
self.params.get(world);
let view_uniform_offset = views.get(view).unwrap();
let extracted_mesh = &extracted_meshes.into_inner().meshes[draw_key];
2021-07-01 23:48:55 +00:00
let shadow_shaders = shadow_shaders.into_inner();
pass.set_render_pipeline(&shadow_shaders.pipeline);
2021-06-02 02:59:17 +00:00
pass.set_bind_group(
0,
2021-06-21 23:28:52 +00:00
light_meta
.into_inner()
.shadow_view_bind_group
.as_ref()
.unwrap(),
&[view_uniform_offset.offset],
2021-06-02 02:59:17 +00:00
);
let transform_bindgroup_key = mesh_meta.mesh_transform_bind_group_key.unwrap();
2021-06-02 02:59:17 +00:00
pass.set_bind_group(
1,
2021-06-21 23:28:52 +00:00
mesh_meta
.into_inner()
.mesh_transform_bind_group
.get_value(transform_bindgroup_key)
2021-06-21 23:28:52 +00:00
.unwrap(),
&[extracted_mesh.transform_binding_offset],
2021-06-02 02:59:17 +00:00
);
let gpu_mesh = meshes.into_inner().get(&extracted_mesh.mesh).unwrap();
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
if let Some(index_info) = &gpu_mesh.index_info {
2021-06-21 23:28:52 +00:00
pass.set_index_buffer(index_info.buffer.slice(..), 0, IndexFormat::Uint32);
2021-06-02 02:59:17 +00:00
pass.draw_indexed(0..index_info.count, 0, 0..1);
} else {
panic!("non-indexed drawing not supported yet")
}
}
}