diff --git a/Cargo.toml b/Cargo.toml index 0fccee6aac6be..93d0f69c90778 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,10 @@ path = "examples/3d/msaa.rs" name = "orthographic" path = "examples/3d/orthographic.rs" +[[example]] +name = "orthographic_pipelined" +path = "examples/3d/orthographic_pipelined.rs" + [[example]] name = "parenting" path = "examples/3d/parenting.rs" diff --git a/examples/3d/3d_scene_pipelined.rs b/examples/3d/3d_scene_pipelined.rs index f3bef825b1a8f..d3c25432568fa 100644 --- a/examples/3d/3d_scene_pipelined.rs +++ b/examples/3d/3d_scene_pipelined.rs @@ -36,11 +36,7 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands.insert_resource(AmbientLight { - color: Color::ORANGE_RED, - brightness: 0.02, - }); - // plane + // ground plane commands.spawn_bundle(PbrBundle { mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })), material: materials.add(StandardMaterial { @@ -51,6 +47,7 @@ fn setup( ..Default::default() }); + // left wall let mut transform = Transform::from_xyz(2.5, 2.5, 0.0); transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2)); commands.spawn_bundle(PbrBundle { @@ -63,7 +60,7 @@ fn setup( }), ..Default::default() }); - + // back (right) wall let mut transform = Transform::from_xyz(0.0, 2.5, -2.5); transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2)); commands.spawn_bundle(PbrBundle { @@ -76,6 +73,7 @@ fn setup( }), ..Default::default() }); + // cube commands .spawn_bundle(PbrBundle { @@ -104,7 +102,13 @@ fn setup( }) .insert(Movable); - // light + // ambient light + commands.insert_resource(AmbientLight { + color: Color::ORANGE_RED, + brightness: 0.02, + }); + + // red point light commands .spawn_bundle(PointLightBundle { // transform: Transform::from_xyz(5.0, 8.0, 2.0), @@ -131,7 +135,7 @@ fn setup( }); }); - // light + // green point light commands .spawn_bundle(PointLightBundle { // transform: Transform::from_xyz(5.0, 8.0, 2.0), @@ -158,7 +162,7 @@ fn setup( }); }); - // light + // blue point light commands .spawn_bundle(PointLightBundle { // transform: Transform::from_xyz(5.0, 8.0, 2.0), @@ -185,6 +189,7 @@ fn setup( }); }); + // directional 'sun' light const HALF_SIZE: f32 = 10.0; commands.spawn_bundle(DirectionalLightBundle { directional_light: DirectionalLight { diff --git a/examples/3d/orthographic_pipelined.rs b/examples/3d/orthographic_pipelined.rs new file mode 100644 index 0000000000000..31f80170bb4b0 --- /dev/null +++ b/examples/3d/orthographic_pipelined.rs @@ -0,0 +1,71 @@ +use bevy::{ + ecs::prelude::*, + math::Vec3, + pbr2::{PbrBundle, PointLightBundle, StandardMaterial}, + prelude::{App, Assets, Transform}, + render2::{ + camera::OrthographicCameraBundle, + color::Color, + mesh::{shape, Mesh}, + }, + PipelinedDefaultPlugins, +}; + +fn main() { + App::new() + .add_plugins(PipelinedDefaultPlugins) + .add_startup_system(setup.system()) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // set up the camera + let mut camera = OrthographicCameraBundle::new_3d(); + camera.orthographic_projection.scale = 3.0; + camera.transform = Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y); + + // camera + commands.spawn_bundle(camera); + + // plane + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..Default::default() + }); + // cubes + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(1.5, 0.5, 1.5), + ..Default::default() + }); + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(1.5, 0.5, -1.5), + ..Default::default() + }); + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(-1.5, 0.5, 1.5), + ..Default::default() + }); + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(-1.5, 0.5, -1.5), + ..Default::default() + }); + // light + commands.spawn_bundle(PointLightBundle { + transform: Transform::from_xyz(3.0, 8.0, 5.0), + ..Default::default() + }); +} diff --git a/examples/README.md b/examples/README.md index c0343a9016db7..c718f32547fd9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -103,6 +103,7 @@ Example | File | Description `load_gltf_pipelined` | [`3d/load_gltf_pipelined.rs`](./3d/load_gltf_pipelined.rs) | Loads and renders a gltf file as a scene `msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges `orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications) +`orthographic_pipelined` | [`3d/orthographic_pipelined.rs`](./3d/orthographic_pipelined.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications) `parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations `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 diff --git a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs index a3d0438d7690b..b2a1ab644bd8d 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs @@ -62,7 +62,7 @@ impl Node for MainPass3dNode { depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: depth_texture, depth_ops: Some(Operations { - load: LoadOp::Clear(1.0), + load: LoadOp::Clear(0.0), store: true, }), stencil_ops: None, diff --git a/pipelined/bevy_pbr2/src/light.rs b/pipelined/bevy_pbr2/src/light.rs index dab8eee43ab07..c5b2f60f56fe0 100644 --- a/pipelined/bevy_pbr2/src/light.rs +++ b/pipelined/bevy_pbr2/src/light.rs @@ -46,7 +46,7 @@ impl Default for PointLight { impl PointLight { pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02; - pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.5; + pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; } #[derive(Clone, Debug)] diff --git a/pipelined/bevy_pbr2/src/render/depth.wgsl b/pipelined/bevy_pbr2/src/render/depth.wgsl index 9313118d16f1d..36367c1b9c23d 100644 --- a/pipelined/bevy_pbr2/src/render/depth.wgsl +++ b/pipelined/bevy_pbr2/src/render/depth.wgsl @@ -1,6 +1,8 @@ +// NOTE: Keep in sync with pbr.wgsl [[block]] struct View { view_proj: mat4x4; + projection: mat4x4; world_position: vec3; }; [[group(0), binding(0)]] @@ -9,7 +11,7 @@ var view: View; [[block]] struct Mesh { - transform: mat4x4; + model: mat4x4; }; [[group(1), binding(0)]] var mesh: Mesh; @@ -25,6 +27,6 @@ struct VertexOutput { [[stage(vertex)]] fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - out.clip_position = view.view_proj * mesh.transform * vec4(vertex.position, 1.0); + out.clip_position = view.view_proj * mesh.model * vec4(vertex.position, 1.0); return out; } diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index c26a4b754ed5e..fb45a1f2b132a 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -54,8 +54,8 @@ pub type ExtractedDirectionalLightShadowMap = DirectionalLightShadowMap; #[repr(C)] #[derive(Copy, Clone, AsStd140, Default, Debug)] pub struct GpuPointLight { + projection: Mat4, color: Vec4, - // proj: Mat4, position: Vec3, inverse_square_range: f32, radius: f32, @@ -120,7 +120,7 @@ impl FromWorld for ShadowShaders { 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), + min_binding_size: BufferSize::new(144), }, count: None, }, @@ -168,7 +168,7 @@ impl FromWorld for ShadowShaders { depth_stencil: Some(DepthStencilState { format: SHADOW_FORMAT, depth_write_enabled: true, - depth_compare: CompareFunction::LessEqual, + depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, @@ -205,7 +205,7 @@ impl FromWorld for ShadowShaders { mag_filter: FilterMode::Linear, min_filter: FilterMode::Linear, mipmap_filter: FilterMode::Nearest, - compare: Some(CompareFunction::LessEqual), + compare: Some(CompareFunction::GreaterEqual), ..Default::default() }), directional_light_sampler: render_device.create_sampler(&SamplerDescriptor { @@ -215,7 +215,7 @@ impl FromWorld for ShadowShaders { mag_filter: FilterMode::Linear, min_filter: FilterMode::Linear, mipmap_filter: FilterMode::Nearest, - compare: Some(CompareFunction::LessEqual), + compare: Some(CompareFunction::GreaterEqual), ..Default::default() }), } @@ -435,7 +435,7 @@ pub fn prepare_lights( // 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) { let projection = - Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, light.range); + Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1); // 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 @@ -484,6 +484,7 @@ pub fn prepare_lights( } gpu_lights.point_lights[light_index] = GpuPointLight { + projection, // 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() * light.intensity).into(), @@ -492,7 +493,6 @@ pub fn prepare_lights( 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, }; @@ -656,7 +656,7 @@ impl Node for ShadowPassNode { depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &view_light.depth_texture_view, depth_ops: Some(Operations { - load: LoadOp::Clear(1.0), + load: LoadOp::Clear(0.0), store: true, }), stencil_ops: None, diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index 683ad9b5db21c..47d75c5ad6e15 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -53,7 +53,7 @@ impl FromWorld for PbrShaders { 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), + min_binding_size: BufferSize::new(144), }, count: None, }, @@ -66,7 +66,7 @@ impl FromWorld for PbrShaders { has_dynamic_offset: true, // TODO: change this to GpuLights::std140_size_static once crevice fixes this! // Context: https://github.com/LPGhatguy/crevice/issues/29 - min_binding_size: BufferSize::new(1024), + min_binding_size: BufferSize::new(1424), }, count: None, }, @@ -225,7 +225,9 @@ impl FromWorld for PbrShaders { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: BufferSize::new(80), + // TODO: change this to MeshUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(144), }, count: None, }], @@ -291,7 +293,7 @@ impl FromWorld for PbrShaders { depth_stencil: Some(DepthStencilState { format: TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: CompareFunction::Less, + depth_compare: CompareFunction::Greater, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, @@ -455,6 +457,7 @@ struct MeshDrawInfo { #[derive(Debug, AsStd140)] pub struct MeshUniform { model: Mat4, + inverse_transpose_model: Mat4, flags: u32, } @@ -486,13 +489,16 @@ pub fn prepare_meshes( .transform_uniforms .reserve_and_clear(extracted_meshes.meshes.len(), &render_device); for extracted_mesh in extracted_meshes.meshes.iter_mut() { + let model = extracted_mesh.transform; + let inverse_transpose_model = model.inverse().transpose(); let flags = if extracted_mesh.receives_shadows { MeshFlags::SHADOW_RECEIVER } else { MeshFlags::NONE }; extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform { - model: extracted_mesh.transform, + model, + inverse_transpose_model, flags: flags.bits, }); } diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index de9c856be9e53..5532a5bb1fde3 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -1,7 +1,9 @@ // TODO: try merging this block with the binding? +// NOTE: Keep in sync with depth.wgsl [[block]] struct View { view_proj: mat4x4; + projection: mat4x4; world_position: vec3; }; @@ -9,6 +11,7 @@ struct View { [[block]] struct Mesh { model: mat4x4; + inverse_transpose_model: mat4x4; // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32; }; @@ -41,9 +44,11 @@ fn vertex(vertex: Vertex) -> VertexOutput { out.uv = vertex.uv; out.world_position = 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 - // of normals - out.world_normal = mat3x3(mesh.model.x.xyz, mesh.model.y.xyz, mesh.model.z.xyz) * vertex.normal; + out.world_normal = mat3x3( + mesh.inverse_transpose_model.x.xyz, + mesh.inverse_transpose_model.y.xyz, + mesh.inverse_transpose_model.z.xyz + ) * vertex.normal; return out; } @@ -100,8 +105,8 @@ let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; struct PointLight { + projection: mat4x4; color: vec4; - // projection: mat4x4; position: vec3; inverse_square_range: f32; radius: f32; @@ -408,14 +413,22 @@ fn fetch_point_shadow(light_id: i32, frag_position: vec4, surface_normal: v let abs_position_ls = abs(frag_ls); let major_axis_magnitude = max(abs_position_ls.x, max(abs_position_ls.y, abs_position_ls.z)); - // do a full projection - // vec4 clip = light.projection * vec4(0.0, 0.0, -major_axis_magnitude, 1.0); - // float depth = (clip.z / clip.w); + // NOTE: These simplifications come from multiplying: + // projection * vec4(0, 0, -major_axis_magnitude, 1.0) + // and keeping only the terms that have any impact on the depth. + // Projection-agnostic approach: + let z = -major_axis_magnitude * light.projection[2][2] + light.projection[3][2]; + let w = -major_axis_magnitude * light.projection[2][3] + light.projection[3][3]; + + // For perspective_rh: + // let proj_r = light.far / (light.near - light.far); + // let z = -major_axis_magnitude * proj_r + light.near * proj_r; + // let w = major_axis_magnitude; + + // For perspective_infinite_reverse_rh: + // let z = light.near; + // let w = major_axis_magnitude; - // alternatively do only the necessary multiplications using near/far - let proj_r = light.far / (light.near - light.far); - let z = -major_axis_magnitude * proj_r + light.near * proj_r; - let w = major_axis_magnitude; let depth = z / w; // do the lookup, using HW PCF and comparison @@ -521,12 +534,12 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { // # endif var V: vec3; - if (view.view_proj.w.w != 1.0) { // If the projection is not orthographic + if (view.projection.w.w != 1.0) { // If the projection is not orthographic // Only valid for a perpective projection V = normalize(view.world_position.xyz - in.world_position.xyz); } else { // Ortho view vec - V = normalize(vec3(-view.view_proj.x.z, -view.view_proj.y.z, -view.view_proj.z.z)); + V = normalize(vec3(view.view_proj.x.z, view.view_proj.y.z, view.view_proj.z.z)); } // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" diff --git a/pipelined/bevy_render2/src/camera/projection.rs b/pipelined/bevy_render2/src/camera/projection.rs index 61a0870458c6e..fabfd2134ecaf 100644 --- a/pipelined/bevy_render2/src/camera/projection.rs +++ b/pipelined/bevy_render2/src/camera/projection.rs @@ -21,7 +21,7 @@ pub struct PerspectiveProjection { impl CameraProjection for PerspectiveProjection { fn get_projection_matrix(&self) -> Mat4 { - Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far) + Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near) } fn update(&mut self, width: f32, height: f32) { @@ -88,8 +88,10 @@ impl CameraProjection for OrthographicProjection { self.right * self.scale, self.bottom * self.scale, self.top * self.scale, - self.near, + // NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0] + // This is for interoperability with pipelines using infinite reverse perspective projections. self.far, + self.near, ) } diff --git a/pipelined/bevy_render2/src/view/mod.rs b/pipelined/bevy_render2/src/view/mod.rs index e00a3bdcdc5bc..d4d02cf35ef06 100644 --- a/pipelined/bevy_render2/src/view/mod.rs +++ b/pipelined/bevy_render2/src/view/mod.rs @@ -42,6 +42,7 @@ pub struct ExtractedView { #[derive(Clone, AsStd140)] pub struct ViewUniform { view_proj: Mat4, + projection: Mat4, world_position: Vec3, } @@ -64,9 +65,11 @@ fn prepare_views( .uniforms .reserve_and_clear(extracted_views.iter_mut().len(), &render_resources); for (entity, camera) in extracted_views.iter() { + let projection = camera.projection; let view_uniforms = ViewUniformOffset { offset: view_meta.uniforms.push(ViewUniform { - view_proj: camera.projection * camera.transform.compute_matrix().inverse(), + view_proj: projection * camera.transform.compute_matrix().inverse(), + projection, world_position: camera.transform.translation, }), }; diff --git a/pipelined/bevy_sprite2/src/render/mod.rs b/pipelined/bevy_sprite2/src/render/mod.rs index 7e29640b510e5..7310b968c4ec7 100644 --- a/pipelined/bevy_sprite2/src/render/mod.rs +++ b/pipelined/bevy_sprite2/src/render/mod.rs @@ -15,7 +15,7 @@ use bevy_render2::{ renderer::{RenderContext, RenderDevice}, shader::Shader, texture::{BevyDefault, Image}, - view::{ViewMeta, ViewUniform, ViewUniformOffset}, + view::{ViewMeta, ViewUniformOffset}, RenderWorld, }; use bevy_transform::components::GlobalTransform; @@ -42,7 +42,9 @@ impl FromWorld for SpriteShaders { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: BufferSize::new(std::mem::size_of::() as u64), + // 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(144), }, count: None, }],