Skip to content

Commit

Permalink
Use the infinite reverse right-handed perspective projection (#2543)
Browse files Browse the repository at this point in the history
# Objective

Forward perspective projections have poor floating point precision distribution over the depth range. Reverse projections fair much better, and instead of having to have a far plane, with the reverse projection, using an infinite far plane is not a problem. The infinite reverse perspective projection has become the industry standard. The renderer rework is a great time to migrate to it.

## Solution

All perspective projections, including point lights, have been moved to using `glam::Mat4::perspective_infinite_reverse_rh()` and so have no far plane. As various depth textures are shared between orthographic and perspective projections, a quirk of this PR is that the near and far planes of the orthographic projection are swapped when the Mat4 is computed. This has no impact on 2D/3D orthographic projection usage, and provides consistency in shaders, texture clear values, etc. throughout the codebase.

## Known issues

For some reason, when looking along -Z, all geometry is black. The camera can be translated up/down / strafed left/right and geometry will still be black. Moving forward/backward or rotating the camera away from looking exactly along -Z causes everything to work as expected.

I have tried to debug this issue but both in macOS and Windows I get crashes when doing pixel debugging. If anyone could reproduce this and debug it I would be very grateful. Otherwise I will have to try to debug it further without pixel debugging, though the projections and such all looked fine to me.
  • Loading branch information
superdump committed Aug 27, 2021
1 parent cea7db1 commit 045f324
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 44 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 14 additions & 9 deletions examples/3d/3d_scene_pipelined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -76,6 +73,7 @@ fn setup(
}),
..Default::default()
});

// cube
commands
.spawn_bundle(PbrBundle {
Expand Down Expand Up @@ -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),
Expand All @@ -131,7 +135,7 @@ fn setup(
});
});

// light
// green point light
commands
.spawn_bundle(PointLightBundle {
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
Expand All @@ -158,7 +162,7 @@ fn setup(
});
});

// light
// blue point light
commands
.spawn_bundle(PointLightBundle {
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
Expand All @@ -185,6 +189,7 @@ fn setup(
});
});

// directional 'sun' light
const HALF_SIZE: f32 = 10.0;
commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
Expand Down
71 changes: 71 additions & 0 deletions examples/3d/orthographic_pipelined.rs
Original file line number Diff line number Diff line change
@@ -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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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()
});
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pipelined/bevy_core_pipeline/src/main_pass_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion pipelined/bevy_pbr2/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
6 changes: 4 additions & 2 deletions pipelined/bevy_pbr2/src/render/depth.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// NOTE: Keep in sync with pbr.wgsl
[[block]]
struct View {
view_proj: mat4x4<f32>;
projection: mat4x4<f32>;
world_position: vec3<f32>;
};
[[group(0), binding(0)]]
Expand All @@ -9,7 +11,7 @@ var view: View;

[[block]]
struct Mesh {
transform: mat4x4<f32>;
model: mat4x4<f32>;
};
[[group(1), binding(0)]]
var mesh: Mesh;
Expand All @@ -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<f32>(vertex.position, 1.0);
out.clip_position = view.view_proj * mesh.model * vec4<f32>(vertex.position, 1.0);
return out;
}
16 changes: 8 additions & 8 deletions pipelined/bevy_pbr2/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
}),
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
};
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 11 additions & 5 deletions pipelined/bevy_pbr2/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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,
},
Expand Down Expand Up @@ -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,
}],
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -455,6 +457,7 @@ struct MeshDrawInfo {
#[derive(Debug, AsStd140)]
pub struct MeshUniform {
model: Mat4,
inverse_transpose_model: Mat4,
flags: u32,
}

Expand Down Expand Up @@ -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,
});
}
Expand Down
Loading

0 comments on commit 045f324

Please sign in to comment.