Use the infinite reverse right-handed perspective projection (#2543)

# 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.
This commit is contained in:
Robert Swain 2021-08-27 20:15:09 +00:00
parent cea7db1050
commit 045f324e97
13 changed files with 153 additions and 44 deletions

View File

@ -179,6 +179,10 @@ path = "examples/3d/msaa.rs"
name = "orthographic" name = "orthographic"
path = "examples/3d/orthographic.rs" path = "examples/3d/orthographic.rs"
[[example]]
name = "orthographic_pipelined"
path = "examples/3d/orthographic_pipelined.rs"
[[example]] [[example]]
name = "parenting" name = "parenting"
path = "examples/3d/parenting.rs" path = "examples/3d/parenting.rs"

View File

@ -36,11 +36,7 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
) { ) {
commands.insert_resource(AmbientLight { // ground plane
color: Color::ORANGE_RED,
brightness: 0.02,
});
// plane
commands.spawn_bundle(PbrBundle { commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })), mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })),
material: materials.add(StandardMaterial { material: materials.add(StandardMaterial {
@ -51,6 +47,7 @@ fn setup(
..Default::default() ..Default::default()
}); });
// left wall
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0); let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2)); transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle { commands.spawn_bundle(PbrBundle {
@ -63,7 +60,7 @@ fn setup(
}), }),
..Default::default() ..Default::default()
}); });
// back (right) wall
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5); let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2)); transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle { commands.spawn_bundle(PbrBundle {
@ -76,6 +73,7 @@ fn setup(
}), }),
..Default::default() ..Default::default()
}); });
// cube // cube
commands commands
.spawn_bundle(PbrBundle { .spawn_bundle(PbrBundle {
@ -104,7 +102,13 @@ fn setup(
}) })
.insert(Movable); .insert(Movable);
// light // ambient light
commands.insert_resource(AmbientLight {
color: Color::ORANGE_RED,
brightness: 0.02,
});
// red point light
commands commands
.spawn_bundle(PointLightBundle { .spawn_bundle(PointLightBundle {
// transform: Transform::from_xyz(5.0, 8.0, 2.0), // transform: Transform::from_xyz(5.0, 8.0, 2.0),
@ -131,7 +135,7 @@ fn setup(
}); });
}); });
// light // green point light
commands commands
.spawn_bundle(PointLightBundle { .spawn_bundle(PointLightBundle {
// transform: Transform::from_xyz(5.0, 8.0, 2.0), // transform: Transform::from_xyz(5.0, 8.0, 2.0),
@ -158,7 +162,7 @@ fn setup(
}); });
}); });
// light // blue point light
commands commands
.spawn_bundle(PointLightBundle { .spawn_bundle(PointLightBundle {
// transform: Transform::from_xyz(5.0, 8.0, 2.0), // 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; const HALF_SIZE: f32 = 10.0;
commands.spawn_bundle(DirectionalLightBundle { commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight { directional_light: DirectionalLight {

View File

@ -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()
});
}

View File

@ -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 `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 `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` | [`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 `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` | [`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

View File

@ -62,7 +62,7 @@ impl Node for MainPass3dNode {
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: depth_texture, view: depth_texture,
depth_ops: Some(Operations { depth_ops: Some(Operations {
load: LoadOp::Clear(1.0), load: LoadOp::Clear(0.0),
store: true, store: true,
}), }),
stencil_ops: None, stencil_ops: None,

View File

@ -46,7 +46,7 @@ impl Default for PointLight {
impl PointLight { impl PointLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02; 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)] #[derive(Clone, Debug)]

View File

@ -1,6 +1,8 @@
// NOTE: Keep in sync with pbr.wgsl
[[block]] [[block]]
struct View { struct View {
view_proj: mat4x4<f32>; view_proj: mat4x4<f32>;
projection: mat4x4<f32>;
world_position: vec3<f32>; world_position: vec3<f32>;
}; };
[[group(0), binding(0)]] [[group(0), binding(0)]]
@ -9,7 +11,7 @@ var view: View;
[[block]] [[block]]
struct Mesh { struct Mesh {
transform: mat4x4<f32>; model: mat4x4<f32>;
}; };
[[group(1), binding(0)]] [[group(1), binding(0)]]
var mesh: Mesh; var mesh: Mesh;
@ -25,6 +27,6 @@ struct VertexOutput {
[[stage(vertex)]] [[stage(vertex)]]
fn vertex(vertex: Vertex) -> VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput {
var out: 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; return out;
} }

View File

@ -54,8 +54,8 @@ pub type ExtractedDirectionalLightShadowMap = DirectionalLightShadowMap;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)] #[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuPointLight { pub struct GpuPointLight {
projection: Mat4,
color: Vec4, color: Vec4,
// proj: Mat4,
position: Vec3, position: Vec3,
inverse_square_range: f32, inverse_square_range: f32,
radius: f32, radius: f32,
@ -120,7 +120,7 @@ impl FromWorld for ShadowShaders {
has_dynamic_offset: true, has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this! // TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29 // Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(80), min_binding_size: BufferSize::new(144),
}, },
count: None, count: None,
}, },
@ -168,7 +168,7 @@ impl FromWorld for ShadowShaders {
depth_stencil: Some(DepthStencilState { depth_stencil: Some(DepthStencilState {
format: SHADOW_FORMAT, format: SHADOW_FORMAT,
depth_write_enabled: true, depth_write_enabled: true,
depth_compare: CompareFunction::LessEqual, depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState { stencil: StencilState {
front: StencilFaceState::IGNORE, front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE,
@ -205,7 +205,7 @@ impl FromWorld for ShadowShaders {
mag_filter: FilterMode::Linear, mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear, min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest, mipmap_filter: FilterMode::Nearest,
compare: Some(CompareFunction::LessEqual), compare: Some(CompareFunction::GreaterEqual),
..Default::default() ..Default::default()
}), }),
directional_light_sampler: render_device.create_sampler(&SamplerDescriptor { directional_light_sampler: render_device.create_sampler(&SamplerDescriptor {
@ -215,7 +215,7 @@ impl FromWorld for ShadowShaders {
mag_filter: FilterMode::Linear, mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear, min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest, mipmap_filter: FilterMode::Nearest,
compare: Some(CompareFunction::LessEqual), compare: Some(CompareFunction::GreaterEqual),
..Default::default() ..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 // 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) { for (light_index, light) in point_lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
let projection = 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 // 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 // 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 { gpu_lights.point_lights[light_index] = GpuPointLight {
projection,
// premultiply color by intensity // premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3] // 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(), 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), inverse_square_range: 1.0 / (light.range * light.range),
near: 0.1, near: 0.1,
far: light.range, far: light.range,
// proj: projection,
shadow_depth_bias: light.shadow_depth_bias, shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias, shadow_normal_bias: light.shadow_normal_bias,
}; };
@ -656,7 +656,7 @@ impl Node for ShadowPassNode {
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &view_light.depth_texture_view, view: &view_light.depth_texture_view,
depth_ops: Some(Operations { depth_ops: Some(Operations {
load: LoadOp::Clear(1.0), load: LoadOp::Clear(0.0),
store: true, store: true,
}), }),
stencil_ops: None, stencil_ops: None,

View File

@ -53,7 +53,7 @@ impl FromWorld for PbrShaders {
has_dynamic_offset: true, has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this! // TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29 // Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(80), min_binding_size: BufferSize::new(144),
}, },
count: None, count: None,
}, },
@ -66,7 +66,7 @@ impl FromWorld for PbrShaders {
has_dynamic_offset: true, has_dynamic_offset: true,
// TODO: change this to GpuLights::std140_size_static once crevice fixes this! // TODO: change this to GpuLights::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29 // Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(1024), min_binding_size: BufferSize::new(1424),
}, },
count: None, count: None,
}, },
@ -225,7 +225,9 @@ impl FromWorld for PbrShaders {
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(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, count: None,
}], }],
@ -291,7 +293,7 @@ impl FromWorld for PbrShaders {
depth_stencil: Some(DepthStencilState { depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float, format: TextureFormat::Depth32Float,
depth_write_enabled: true, depth_write_enabled: true,
depth_compare: CompareFunction::Less, depth_compare: CompareFunction::Greater,
stencil: StencilState { stencil: StencilState {
front: StencilFaceState::IGNORE, front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE,
@ -455,6 +457,7 @@ struct MeshDrawInfo {
#[derive(Debug, AsStd140)] #[derive(Debug, AsStd140)]
pub struct MeshUniform { pub struct MeshUniform {
model: Mat4, model: Mat4,
inverse_transpose_model: Mat4,
flags: u32, flags: u32,
} }
@ -486,13 +489,16 @@ 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() {
let model = extracted_mesh.transform;
let inverse_transpose_model = model.inverse().transpose();
let flags = if extracted_mesh.receives_shadows { let flags = if extracted_mesh.receives_shadows {
MeshFlags::SHADOW_RECEIVER MeshFlags::SHADOW_RECEIVER
} else { } else {
MeshFlags::NONE MeshFlags::NONE
}; };
extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform { extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform {
model: extracted_mesh.transform, model,
inverse_transpose_model,
flags: flags.bits, flags: flags.bits,
}); });
} }

View File

@ -1,7 +1,9 @@
// TODO: try merging this block with the binding? // TODO: try merging this block with the binding?
// NOTE: Keep in sync with depth.wgsl
[[block]] [[block]]
struct View { struct View {
view_proj: mat4x4<f32>; view_proj: mat4x4<f32>;
projection: mat4x4<f32>;
world_position: vec3<f32>; world_position: vec3<f32>;
}; };
@ -9,6 +11,7 @@ struct View {
[[block]] [[block]]
struct Mesh { struct Mesh {
model: mat4x4<f32>; model: mat4x4<f32>;
inverse_transpose_model: mat4x4<f32>;
// 'flags' is a bit field indicating various options. u32 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;
}; };
@ -41,9 +44,11 @@ fn vertex(vertex: Vertex) -> VertexOutput {
out.uv = vertex.uv; out.uv = vertex.uv;
out.world_position = world_position; out.world_position = world_position;
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 out.world_normal = mat3x3<f32>(
// of normals mesh.inverse_transpose_model.x.xyz,
out.world_normal = mat3x3<f32>(mesh.model.x.xyz, mesh.model.y.xyz, mesh.model.z.xyz) * vertex.normal; mesh.inverse_transpose_model.y.xyz,
mesh.inverse_transpose_model.z.xyz
) * vertex.normal;
return out; return out;
} }
@ -100,8 +105,8 @@ let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
struct PointLight { struct PointLight {
projection: mat4x4<f32>;
color: vec4<f32>; color: vec4<f32>;
// projection: mat4x4<f32>;
position: vec3<f32>; position: vec3<f32>;
inverse_square_range: f32; inverse_square_range: f32;
radius: f32; radius: f32;
@ -408,14 +413,22 @@ fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>, surface_normal: v
let abs_position_ls = abs(frag_ls); 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)); let major_axis_magnitude = max(abs_position_ls.x, max(abs_position_ls.y, abs_position_ls.z));
// do a full projection // NOTE: These simplifications come from multiplying:
// vec4 clip = light.projection * vec4(0.0, 0.0, -major_axis_magnitude, 1.0); // projection * vec4(0, 0, -major_axis_magnitude, 1.0)
// float depth = (clip.z / clip.w); // 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; let depth = z / w;
// do the lookup, using HW PCF and comparison // do the lookup, using HW PCF and comparison
@ -521,12 +534,12 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
// # endif // # endif
var V: vec3<f32>; var V: vec3<f32>;
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 // Only valid for a perpective projection
V = normalize(view.world_position.xyz - in.world_position.xyz); V = normalize(view.world_position.xyz - in.world_position.xyz);
} else { } else {
// Ortho view vec // Ortho view vec
V = normalize(vec3<f32>(-view.view_proj.x.z, -view.view_proj.y.z, -view.view_proj.z.z)); V = normalize(vec3<f32>(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" // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"

View File

@ -21,7 +21,7 @@ pub struct PerspectiveProjection {
impl CameraProjection for PerspectiveProjection { impl CameraProjection for PerspectiveProjection {
fn get_projection_matrix(&self) -> Mat4 { 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) { fn update(&mut self, width: f32, height: f32) {
@ -88,8 +88,10 @@ impl CameraProjection for OrthographicProjection {
self.right * self.scale, self.right * self.scale,
self.bottom * self.scale, self.bottom * self.scale,
self.top * 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.far,
self.near,
) )
} }

View File

@ -42,6 +42,7 @@ pub struct ExtractedView {
#[derive(Clone, AsStd140)] #[derive(Clone, AsStd140)]
pub struct ViewUniform { pub struct ViewUniform {
view_proj: Mat4, view_proj: Mat4,
projection: Mat4,
world_position: Vec3, world_position: Vec3,
} }
@ -64,9 +65,11 @@ fn prepare_views(
.uniforms .uniforms
.reserve_and_clear(extracted_views.iter_mut().len(), &render_resources); .reserve_and_clear(extracted_views.iter_mut().len(), &render_resources);
for (entity, camera) in extracted_views.iter() { for (entity, camera) in extracted_views.iter() {
let projection = camera.projection;
let view_uniforms = ViewUniformOffset { let view_uniforms = ViewUniformOffset {
offset: view_meta.uniforms.push(ViewUniform { 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, world_position: camera.transform.translation,
}), }),
}; };

View File

@ -15,7 +15,7 @@ use bevy_render2::{
renderer::{RenderContext, RenderDevice}, renderer::{RenderContext, RenderDevice},
shader::Shader, shader::Shader,
texture::{BevyDefault, Image}, texture::{BevyDefault, Image},
view::{ViewMeta, ViewUniform, ViewUniformOffset}, view::{ViewMeta, ViewUniformOffset},
RenderWorld, RenderWorld,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -42,7 +42,9 @@ impl FromWorld for SpriteShaders {
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(std::mem::size_of::<ViewUniform>() 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, count: None,
}], }],