diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl index 3ef77cb6e8..ef40883157 100644 --- a/assets/voxygen/shaders/clouds-frag.glsl +++ b/assets/voxygen/shaders/clouds-frag.glsl @@ -49,9 +49,9 @@ vec3 wpos_at(vec2 uv) { vec4 clip_space = vec4((uv * 2.0 - 1.0) * vec2(1, -1), buf_depth, 1.0); vec4 view_space = inv * clip_space; view_space /= view_space.w; - if (buf_depth == 1.0) { + if (buf_depth == 0.0) { vec3 direction = normalize(view_space.xyz); - return direction.xyz * 100000.0 + cam_pos.xyz; + return direction.xyz * 524288.0625 + cam_pos.xyz; } else { return view_space.xyz; } diff --git a/assets/voxygen/shaders/skybox-vert.glsl b/assets/voxygen/shaders/skybox-vert.glsl index 77f820ef4a..e3eaf45546 100644 --- a/assets/voxygen/shaders/skybox-vert.glsl +++ b/assets/voxygen/shaders/skybox-vert.glsl @@ -31,7 +31,7 @@ void main() { all_mat * vec4(v_pos + cam_pos.xyz, 1); // gl_Position = vec4(gl_Position.xy, sign(gl_Position.z) * gl_Position.w, gl_Position.w); - gl_Position.z = gl_Position.w; + gl_Position.z = 0; // gl_Position.z = gl_Position.w - 0.000001;//0.0; // gl_Position.z = 1.0; // gl_Position.z = -1.0; diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index aa8a7b1431..3f1b82f4e6 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -227,9 +227,9 @@ impl FigurePipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/fluid.rs b/voxygen/src/render/pipelines/fluid.rs index 97176157a5..219c5561f9 100644 --- a/voxygen/src/render/pipelines/fluid.rs +++ b/voxygen/src/render/pipelines/fluid.rs @@ -178,9 +178,9 @@ impl FluidPipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs index bd40198b7d..f7774cf24f 100644 --- a/voxygen/src/render/pipelines/lod_terrain.rs +++ b/voxygen/src/render/pipelines/lod_terrain.rs @@ -187,9 +187,9 @@ impl LodTerrainPipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 5487d4b53d..2653782216 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -233,9 +233,9 @@ impl ParticlePipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index 2369191ccc..f682ba9472 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -83,9 +83,9 @@ impl SkyboxPipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index 01823355d3..f07f52e41e 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -270,9 +270,9 @@ impl SpritePipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 752118ac24..54eb29e3c4 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -261,9 +261,9 @@ impl TerrainPipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: Some(wgpu::DepthStencilStateDescriptor { - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, + depth_compare: wgpu::CompareFunction::GreaterEqual, stencil: wgpu::StencilStateDescriptor { front: wgpu::StencilStateFaceDescriptor::IGNORE, back: wgpu::StencilStateFaceDescriptor::IGNORE, diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 4a4e4fe5b0..eee3c5cc76 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -655,12 +655,12 @@ impl Renderer { mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, }); let tgt_depth_view = tgt_depth_tex.create_view(&wgpu::TextureViewDescriptor { label: None, - format: Some(wgpu::TextureFormat::Depth24Plus), + format: Some(wgpu::TextureFormat::Depth32Float), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, @@ -679,12 +679,13 @@ impl Renderer { mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24Plus, + format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsage::RENDER_ATTACHMENT, }); + // TODO: Consider no depth buffer for the final draw to the window? let win_depth_view = tgt_depth_tex.create_view(&wgpu::TextureViewDescriptor { label: None, - format: Some(wgpu::TextureFormat::Depth24Plus), + format: Some(wgpu::TextureFormat::Depth32Float), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, @@ -2106,7 +2107,8 @@ fn create_pipelines( let figure_vert_mod = create_shader("figure-vert", ShaderKind::Vertex)?; - // let terrain_point_shadow_vert_mod = create_shader("Point-light-shadows-vert", ShaderKind::Vertex)?; + // let terrain_point_shadow_vert_mod = create_shader("Point-light-shadows-vert", + // ShaderKind::Vertex)?; let terrain_directed_shadow_vert_mod = create_shader("light-shadows-directed-vert", ShaderKind::Vertex)?; diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index b6728191ee..6e4ff852f1 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -54,7 +54,7 @@ impl<'a> Drawer<'a> { wgpu::RenderPassDepthStencilAttachmentDescriptor { attachment: &self.renderer.tgt_depth_view, depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), + load: wgpu::LoadOp::Clear(0.0), store: true, }), stencil_ops: None, @@ -157,7 +157,7 @@ impl<'a> Drawer<'a> { pub fn draw_point_shadow<'b: 'a>( &mut self, matrices: &[shadow::PointLightMatrix; 126], - chunks: impl Clone + Iterator, &'b terrain::BoundLocals)>, + chunks: impl Clone + Iterator, &'b terrain::BoundLocals)>, ) { if let Some(ref shadow_renderer) = self.renderer.shadow_map { const STRIDE: usize = std::mem::size_of::(); diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 7bc8e2fa35..a22624c0c9 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -1,11 +1,12 @@ use common::{terrain::TerrainGrid, vol::ReadVol}; use common_base::span; -use std::f32::consts::PI; +use core::{f32::consts::PI, fmt::Debug}; +use num::traits::{real::Real, FloatConst}; use treeculler::Frustum; use vek::*; -pub const NEAR_PLANE: f32 = 0.25; -pub const FAR_PLANE: f32 = 100000.0; +pub const NEAR_PLANE: f32 = 0.0625; +pub const FAR_PLANE: f32 = 524288.0625; const FIRST_PERSON_INTERP_TIME: f32 = 0.1; const THIRD_PERSON_INTERP_TIME: f32 = 0.1; @@ -31,6 +32,9 @@ pub struct Dependents { pub view_mat_inv: Mat4, pub proj_mat: Mat4, pub proj_mat_inv: Mat4, + /// Specifically there for satisfying our treeculler dependency, which can't + /// handle inverted depth planes. + pub proj_mat_treeculler: Mat4, pub cam_pos: Vec3, pub cam_dir: Vec3, } @@ -64,6 +68,249 @@ fn clamp_and_modulate(ori: Vec3) -> Vec3 { } } +/// Generalized method to construct a perspective projection with x ∈ [-1,1], y +/// ∈ [-1,1], z ∈ [0,1] given fov_y_radians, aspect_ratio, 1/n, and 1/f. Note +/// that you pass in *1/n* and *1/f*, not n and f like you normally would for a +/// perspective projection; this is done to enable uniform handling of both +/// finite and infinite far planes. +/// +/// The only requirements on n and f are: 1/n ≠ 1/f, and 0 ≤ 1/n * 1/f. +/// +/// This ensures that the near and far plane are not identical (or else your +/// projection would not covver any distance), and that they have the same sign +/// (or else we cannot rely on clipping to properly fix your scene). This also +/// ensures that at least one of 1/n and 1/f is not 0, and by construction it +/// guarantees that neither n nor f are 0; these are required in order to make +/// sense of the definition of near and far planes, and avoid collapsing all +/// depths to a single point. +/// +/// For "typical" projections (matching perspective_lh_no), you would satisfy +/// the stronger requirements. We give the typical conditions for each bullet +/// point, and then explain the consequences of not satisfying these conditions: +/// +/// * 1/n < 1/f (0 to 1 depth planes, meaning n = near and f = far; if f < n, +/// depth planes go from 1 to 0, meaning f = near and n = far, aka "reverse +/// depth"). +/// +/// This is by far the most +/// likely thing to want to change; inverted depth coordinates have *far* +/// better accuracy for DirectX / Metal / WGPU-like APIs, when using +/// floating point depth, while not being *worse* than the alternative +/// (OpenGL-like depth, or when using fixed-point / integer depth). For +/// maximum benefit, make sure you are using Depth32F, as on most platforms +/// this is the only depth buffer size where floating point can be used. +/// +/// It is a bit unintuitive to prove this, but it turns out that when using +/// 1 to 0 depth planes, the point where the depth buffer has its worst +/// precision is not at the far plane (as with 0 to 1 depth planes) nor at +/// the near plane, as you might expect, but exactly at far/2 (the +/// near plane setting does not affect the point of minimum accuracy at +/// all!). However, don't let this fool you into believing the point of +/// worst precision has simply been moved around--for *any* fixed Δz that is +/// the minimum amount of depth precision you want over the whole range, and +/// any near plane, you can set the far plane farther (generally much much +/// farther!) with reversed clip space than you can with standard clip space +/// while still getting at least that much depth precision in the worst +/// case. Nor is this a small worst-case; for many desirable near and far +/// plane combinations, more than half the visible space will have +/// completely unusable precision under 0 to 1 depth, while having much better +/// than needed precision under 1 to 0 depth. +/// +/// To compute the exact (at least "roughly exact") worst-case accuracy for +/// floating point depth and a given precision target Δz, for reverse clip +/// planes (this can be computed for the non-reversed case too, but it's +/// painful and the values are horrible, so don't bother), we compute +/// (assuming a finite far plane--see below for details on the infinite +/// case) the change in the integer representation of the mantissa at z=n/2: +/// +/// ```ignore +/// e = floor(ln(near/(far - near))/ln(2)) +/// db/dz = 2^(2-e) / ((1 / far - 1 / near) * (far)^2) +/// ``` +/// +/// Then the maximum precision you can safely use to get a change in the +/// integer representation of the mantissa (assuming 32-bit floating points) +/// is around: +/// +/// ```ignore +/// abs(2^(-23) / (db/dz)). +/// ``` +/// +/// In particular, if your worst-case target accuracy over the depth range +/// is Δz, you should be okay if: +/// +/// ```ignore +/// abs(Δz * (db/dz)) * 2^(23) ≥ 1. +/// ``` +/// +/// This only accounts for precision of the final floating-point value, so +/// it's possible that artifacts may be introduced elsewhere during the +/// computation that reduce precision further; the most famous example of +/// this is that OpenGL wipes out most of the precision gains by going from +/// [-1,1] to [0,1] by letting +/// +/// ```ignore +/// clip space depth = depth * 0.5 + 0.5 +/// ``` +/// +/// which results in huge precision errors by removing nearly all the +/// floating point values with the most precision (those close to 0). +/// Fortunately, most such artifacts are absent under the wgpu/DirectX/Metal +/// depth clip space model, so with any luck remaining depth errors due to +/// the perspective warp itself should be minimal. +/// +/// * 0 ≠ 1/far (finite far plane). When this is false, the far plane is at +/// infinity; this removes the restriction of having a far plane at all, often +/// with minimal reduction in accuracy for most values in the scene. In fact, +/// in almost all cases with non-reversed depth planes, it *improves* accuracy +/// over the finite case for the vast majority of the range; however, you +/// should be using reversed depth planes, and if you are then there is a +/// quite natural accuracy vs. distance tradeoff in the infinite case. +/// +/// When using an infinite far plane, the worst-case accuracy is *always* at +/// infinity, and gets progressively worse as you get farther away from the +/// near plane. However, there is a second advantage that may not be +/// immediately apparent: the perspective warp becomes much simpler, +/// potentially removing artifacts! Specifically, in the 0 to 1 depth plane +/// case, the assigned depth value (after perspective division) becomes: +/// +/// ```ignore +/// depth = 1 - near/z +/// ``` +/// +/// while in the 1 to 0 depth plane case (which you should be using), the +/// equation is even simpler: +/// +/// ```ignore +/// depth = near/z +/// ``` +/// +/// In the 1 to 0 case, in particular, you can see that the depth value is +/// *linear in z in log space.* This lets us compute, for any given target +/// precision, a *very* simple worst-case upper bound on the maximum +/// absolute z value for which that precision can be achieved (the upper +/// bound is tight in some cases, but in others may be conservative): +/// +/// ```ignore +/// db/dz ≥ 1/z +/// ``` +/// +/// Plugging that into our old formula, we find that we attain the required +/// precision at least in the range (again, this is for the 1 to 0 infinite +/// case only!): +/// +/// ```ignore +/// abs(z) ≤ Δz * 2^23 +/// ``` +/// +/// One thing you may notice is that this worst-case bound *does not depend +/// on the near plane.* This means that (within reason) you can put the near +/// plane as close as you like and still attain this bound. Of course, the +/// bound is not completely tight, but it should not be off by more than a +/// factor of 2 or so (informally proven, not made rigorous yet), so for most +/// practical purposes you can set the near plane as low as you like in this +/// case. +/// +/// * 0 < 1/near (positive near plane--best used when moving *to* left-handed +/// spaces, as we normally do in OpenGL and DirectX). A use case for *not* +/// doing this is that it allows moving *from* a left-handed space *to* a +/// right-handed space in WGPU / DirectX / Metal coordinates; this means that +/// if matrices were already set up for OpenGL using functions like look_at_rh +/// that assume right-handed coordinates, we can simply switch these to +/// look_at_lh and use a right-handed perspective projection with a negative +/// near plane, to get correct rendering behavior. Details are out of scope +/// for this comment. +/// +/// Note that there is one final, very important thing that affects possible +/// precision--the actual underlying precision of the floating point format at a +/// particular value! As your z values go up, their precision will shrink, so +/// if at all possible try to shrink your z values down to the lowest range in +/// which they can be. Unfortunately, this cannot be part of the perspective +/// projection itself, because by the time z gets to the projection it is +/// usually too late for values to still be integers (or coarse-grained powers +/// of 2). Instead, try to scale down x, y, and z as soon as possible before +/// submitting them to the GPU, ideally by as large as possible of a power of 2 +/// that works for your use case. Not only will this improve depth precision +/// and recall, it will also help address other artifacts caused by values far +/// from z (such as improperly rounded rotations, or improper line equations due +/// to greedy meshing). +/// +/// TODO: Consider passing fractions rather than 1/n and 1/f directly, even +/// though the logic for why it should be okay to pass them directly is probably +/// sound (they are both valid z values in the range, so gl_FragCoord.w will be +/// assigned to this, meaning if they are imprecise enough then the whole +/// calculation will be similarly imprecies). +/// +/// TODO: Since it's a bit confusing that n and f are not always near and far, +/// and a negative near plane can (probably) be emulated with simple actions on +/// the perspective matrix, consider removing this functionailty and replacing +/// our assertion with a single condition: `(1/far) * (1/near) < (1/near)²`. +pub fn perspective_lh_zo_general( + fov_y_radians: T, + aspect_ratio: T, + inv_n: T, + inv_f: T, +) -> Mat4 +where + T: Real + FloatConst + Debug, +{ + // Per comments, we only need these two assertions to make sure our calculations + // make sense. + debug_assert_ne!( + inv_n, inv_f, + "The near and far plane distances cannot be equal, found: {:?} = {:?}", + inv_n, inv_f + ); + debug_assert!( + T::zero() <= inv_n * inv_f, + "The near and far plane distances must have the same sign, found: {:?} * {:?} < 0", + inv_n, + inv_f + ); + + // TODO: Would be nice to separate out the aspect ratio computations. + let two = T::one() + T::one(); + let tan_half_fovy = (fov_y_radians / two).tan(); + let m00 = T::one() / (aspect_ratio * tan_half_fovy); + let m11 = T::one() / tan_half_fovy; + let m23 = -T::one() / (inv_n - inv_f); + let m22 = inv_n * (-m23); + Mat4::new( + m00, + T::zero(), + T::zero(), + T::zero(), + T::zero(), + m11, + T::zero(), + T::zero(), + T::zero(), + T::zero(), + m22, + m23, + T::zero(), + T::zero(), + T::one(), + T::zero(), + ) +} + +/// Same as perspective_lh_zo_general, but for right-handed source spaces. +pub fn perspective_rh_zo_general( + fov_y_radians: T, + aspect_ratio: T, + inv_n: T, + inv_f: T, +) -> Mat4 +where + T: Real + FloatConst + Debug, +{ + let mut m = perspective_lh_zo_general(fov_y_radians, aspect_ratio, inv_n, inv_f); + m[(2, 2)] = -m[(2, 2)]; + m[(3, 2)] = -m[(3, 2)]; + m +} + impl Camera { /// Create a new `Camera` with default parameters. pub fn new(aspect: f32, mode: CameraMode) -> Self { @@ -89,6 +336,7 @@ impl Camera { view_mat_inv: Mat4::identity(), proj_mat: Mat4::identity(), proj_mat_inv: Mat4::identity(), + proj_mat_treeculler: Mat4::identity(), cam_pos: Vec3::zero(), cam_dir: Vec3::unit_y(), }, @@ -135,14 +383,19 @@ impl Camera { * Mat4::translation_3d(-self.focus.map(|e| e.fract())); self.dependents.view_mat_inv = self.dependents.view_mat.inverted(); + // NOTE: We reverse the far and near planes to produce an inverted depth + // buffer (1 to 0 z planes). self.dependents.proj_mat = - Mat4::perspective_rh_zo(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); + perspective_rh_zo_general(self.fov, self.aspect, 1.0 / FAR_PLANE, 1.0 / NEAR_PLANE); + // For treeculler, we also produce a version without inverted depth. + self.dependents.proj_mat_treeculler = + perspective_rh_zo_general(self.fov, self.aspect, 1.0 / NEAR_PLANE, 1.0 / FAR_PLANE); self.dependents.proj_mat_inv = self.dependents.proj_mat.inverted(); // TODO: Make this more efficient. self.dependents.cam_pos = Vec3::from(self.dependents.view_mat_inv * Vec4::unit_w()); self.frustum = Frustum::from_modelview_projection( - (self.dependents.proj_mat + (self.dependents.proj_mat_treeculler * self.dependents.view_mat * Mat4::translation_3d(-self.focus.map(|e| e.trunc()))) .into_col_arrays(), diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 1c32650931..7531fb77ee 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -129,7 +129,7 @@ impl<'a> SceneData<'a> { /// W_e = 2 is the width of the image plane (for our projections, since they go /// from -1 to 1) n_e = near_plane is the near plane for the view frustum /// θ = (fov / 2) is the half-angle of the FOV (the one passed to -/// Mat4::projection_rh_no). +/// Mat4::projection_rh_zo). /// /// Although the widths for the x and y image planes are the same, they are /// different in this framework due to the introduction of an aspect ratio: @@ -657,8 +657,7 @@ impl Scene { &scene_data, focus_pos, self.loaded_distance, - view_mat, - proj_mat, + &self.camera, ); // Maintain the figures. @@ -696,9 +695,10 @@ impl Scene { // OpenGL coordinates. Note that the matrix for directional light // is *already* linear in the depth buffer. // - // Also, observe that we flip the texture sampling matrix in order to account for the - // fact that DirectX renders top-down. - let texture_mat = Mat4::::scaling_3d::>(Vec3::new(0.5, -0.5, 1.0)) * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0)); + // Also, observe that we flip the texture sampling matrix in order to account + // for the fact that DirectX renders top-down. + let texture_mat = Mat4::::scaling_3d::>(Vec3::new(0.5, -0.5, 1.0)) + * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0)); // We need to compute these offset matrices to transform world space coordinates // to the translated ones we use when multiplying by the light space // matrix; this helps avoid precision loss during the @@ -722,7 +722,8 @@ impl Scene { let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt(); let gamma = sin_gamma.asin(); let view_mat = math::Mat4::from_col_array(view_mat.into_col_array()); - // coordinates are transformed from world space (right-handed) to view space (right-handed). + // coordinates are transformed from world space (right-handed) to view space + // (right-handed). let bounds1 = math::fit_psr( view_mat.map_cols(math::Vec4::from), visible_light_volume.iter().copied(), @@ -756,7 +757,8 @@ impl Scene { ); let light_all_mat = l_r * directed_proj_mat * light_view_mat; - // coordinates are transformed from world space (right-handed) to rotated light space (left-handed). + // coordinates are transformed from world space (right-handed) to rotated light + // space (left-handed). let bounds0 = math::fit_psr( light_all_mat, visible_light_volume.iter().copied(), @@ -765,11 +767,13 @@ impl Scene { // Vague idea: project z_n from the camera view to the light view (where it's // tilted by γ). // - // NOTE: To transform a normal by M, we multiply by the transpose of the inverse of M. - // For the cases below, we are transforming by an already-inverted matrix, so the - // transpose of its inverse is just the transpose of the original matrix. - // normals as well as points, rather than taking the transpose of the matrix, - // is that our matrix is (for normals) a pure rotation matrix, which means it is d + // NOTE: To transform a normal by M, we multiply by the transpose of the inverse + // of M. For the cases below, we are transforming by an + // already-inverted matrix, so the transpose of its inverse is + // just the transpose of the original matrix. normals as well as + // points, rather than taking the transpose of the matrix, + // is that our matrix is (for normals) a pure rotation matrix, which means it is + // d let (z_0, z_1) = { // view space, right-handed coordinates. let p_z = bounds1.max.z; @@ -782,15 +786,24 @@ impl Scene { let light_all_inv = light_all_mat.inverted(); // moves from view-space (right-handed) to world-space (right-handed). - let view_point = view_inv * math::Vec4::from_point(math::Vec3::forward_rh() * p_z/* + math::Vec4::unit_w() */); + let view_point = view_inv + * math::Vec4::from_point( + math::Vec3::forward_rh() * p_z, /* + math::Vec4::unit_w() */ + ); let view_plane = view_mat.transposed() * math::Vec4::forward_rh(); // moves from rotated light space (left-handed) to world space (right-handed). - let light_point = light_all_inv * math::Vec4::from_point(math::Vec3::up() * p_y/* + math::Vec4::unit_w() */); + let light_point = light_all_inv + * math::Vec4::from_point( + math::Vec3::up() * p_y, /* + math::Vec4::unit_w() */ + ); let light_plane = light_all_mat.transposed() * math::Vec4::up(); // moves from rotated light space (left-handed) to world space (right-handed). - let shadow_point = light_all_inv * math::Vec4::from_point(math::Vec3::right() * p_x/* + math::Vec4::unit_w() */); + let shadow_point = light_all_inv + * math::Vec4::from_point( + math::Vec3::right() * p_x, /* + math::Vec4::unit_w() */ + ); let shadow_plane = light_all_mat.transposed() * math::Vec4::right(); // Find the point at the intersection of the three planes; note that since the @@ -834,7 +847,10 @@ impl Scene { // // NOTE: I don't think the w component should be anything but 1 here, but // better safe than sorry. - (f64::from(z0.homogenized().dot(math::Vec4::forward_rh())), f64::from(z1.homogenized().dot(math::Vec4::forward_rh()))) + ( + f64::from(z0.homogenized().dot(math::Vec4::forward_rh())), + f64::from(z1.homogenized().dot(math::Vec4::forward_rh())), + ) }; // all of this is in rotated light-space (left-handed). @@ -848,8 +864,8 @@ impl Scene { let w_l_y = d; // NOTE: See section 5.1.2.2 of Lloyd's thesis. - // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to worry - // about the handedness of their ratio. + // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to + // worry about the handedness of their ratio. let alpha = z_1 / z_0; let alpha_sqrt = alpha.sqrt(); let directed_near_normal = if factor < 0.0 { @@ -954,16 +970,19 @@ impl Scene { shadow_mats.resize_with(6, PointLightMatrix::default); // Now, we tackle point lights. // First, create a perspective projection matrix at 90 degrees (to cover a whole - // face of the cube map we're using). - let shadow_proj = Mat4::perspective_rh_zo( + // face of the cube map we're using); we use a negative near plane to exactly + // match OpenGL's behavior if we use a left-handed coordinate system everywhere + // else. + let shadow_proj = camera::perspective_rh_zo_general( 90.0f32.to_radians(), point_shadow_aspect, - SHADOW_NEAR, - SHADOW_FAR, + 1.0 / SHADOW_NEAR, + 1.0 / SHADOW_FAR, ); // NOTE: We negate here to emulate a right-handed projection with a negative - // near plane, which produces the correct transformation to exactly match OpenGL's - // rendering behavior if we use a left-handed coordinate system everywhere else. + // near plane, which produces the correct transformation to exactly match + // OpenGL's rendering behavior if we use a left-handed coordinate + // system everywhere else. let shadow_proj = shadow_proj * Mat4::scaling_3d(-1.0); // Next, construct the 6 orientations we'll use for the six faces, in terms of diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 9c09bbc11b..675e94fa58 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -15,7 +15,10 @@ use crate::{ }, }; -use super::{math, SceneData}; +use super::{ + camera::{self, Camera}, + math, SceneData, +}; use common::{ assets::{self, AssetExt, DotVoxAsset}, figure::Segment, @@ -767,9 +770,14 @@ impl Terrain { scene_data: &SceneData, focus_pos: Vec3, loaded_distance: f32, - view_mat: Mat4, - proj_mat: Mat4, + camera: &Camera, ) -> (Aabb, Vec>, math::Aabr) { + let camera::Dependents { + view_mat, + proj_mat_treeculler, + .. + } = camera.dependents(); + // Remove any models for chunks that have been recently removed. // Note: Does this before adding to todo list just in case removed chunks were // replaced with new chunks (although this would probably be recorded as @@ -1215,7 +1223,7 @@ impl Terrain { span!(guard, "Construct view frustum"); let focus_off = focus_pos.map(|e| e.trunc()); let frustum = Frustum::from_modelview_projection( - (proj_mat * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(), + (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(), ); drop(guard); @@ -1292,10 +1300,13 @@ impl Terrain { let focus_off = math::Vec3::from(focus_off); let visible_bounds_fine = visible_bounding_box.as_::(); let inv_proj_view = - math::Mat4::from_col_arrays((proj_mat * view_mat).into_col_arrays()) + math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays()) .as_::() .inverted(); let ray_direction = math::Vec3::::from(ray_direction); + // NOTE: We use proj_mat_treeculler here because + // calc_focused_light_volume_points makes the assumption that the + // near plane lies before the far plane. let visible_light_volume = math::calc_focused_light_volume_points( inv_proj_view, ray_direction.as_::(), @@ -1455,7 +1466,10 @@ impl Terrain { light_data.iter().take(1).for_each(|_light| { drawer.draw_point_shadow( &global.point_light_matrices, - chunk_iter.clone().filter(|chunk| chunk.can_shadow_point).map(|chunk| (&chunk.opaque_model, &chunk.locals)), + chunk_iter + .clone() + .filter(|chunk| chunk.can_shadow_point) + .map(|chunk| (&chunk.opaque_model, &chunk.locals)), ); }); }