Skip to content

Commit 91978db

Browse files
authored
Merge pull request #78 from pmndrs/feat/perspective-reverse-z
feat(mat4): add perspectiveReverseZ
2 parents 82387b5 + 1f0a913 commit 91978db

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎API.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ import { mat4 } from 'math';
616616
- `mat4.frustumZO(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4` — Generates a frustum matrix with the given bounds, suitable for WebGPU.
617617
- `mat4.perspectiveNO(out: Mat4, fovy: number, aspect: number, near: number, far: number): Mat4` — Generates a perspective projection matrix with the given bounds.
618618
- `mat4.perspectiveZO(out: Mat4, fovy: number, aspect: number, near: number, far: number): Mat4` — Generates a perspective projection matrix suitable for WebGPU with the given bounds.
619+
- `mat4.perspectiveReverseZ(out: Mat4, fovy: number, aspect: number, near: number, far: number): Mat4` — Generates a reverse-Z perspective projection matrix with the given bounds.
619620
- `mat4.perspectiveFromFieldOfViewNO(out: Mat4, fov: { upDegrees: number; downDegrees: number; leftDegrees: number; rightDegrees: number; }, near: number, far: number): Mat4` — Generates a perspective projection matrix with the given field of view.
620621
- `mat4.perspectiveFromFieldOfViewZO(out: Mat4, fov: { upDegrees: number; downDegrees: number; leftDegrees: number; rightDegrees: number; }, near: number, far: number): Mat4` — Generates a perspective projection matrix with the given field of view, suitable for WebGPU.
621622
- `mat4.orthoNO(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4` — Generates a orthogonal projection matrix with the given bounds.

‎src/core/mat4.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,49 @@ export function perspectiveZO(out: Mat4, fovy: number, aspect: number, near: num
18691869
return out;
18701870
}
18711871

1872+
/**
1873+
* Generates a reverse-Z perspective projection matrix with the given bounds.
1874+
* The near plane maps to a normalized device coordinate Z of 1 and the far plane to 0,
1875+
* using WebGPU/Vulkan/DirectX/Metal's [0, 1] clip volume.
1876+
* Pair with a floating point depth buffer, a depth clear of 0 and a `greater` depth compare
1877+
* for much better depth precision than a standard projection.
1878+
* Passing Infinity for far will generate an infinite projection matrix,
1879+
* where depth approaches 0 as distance approaches infinity.
1880+
*
1881+
* @param out mat4 frustum matrix will be written into
1882+
* @param fovy Vertical field of view in radians
1883+
* @param aspect Aspect ratio. typically viewport width/height
1884+
* @param near Near bound of the frustum
1885+
* @param far Far bound of the frustum, can be Infinity
1886+
* @returns out
1887+
*/
1888+
export function perspectiveReverseZ(out: Mat4, fovy: number, aspect: number, near: number, far: number): Mat4 {
1889+
const f = 1.0 / Math.tan(fovy / 2);
1890+
out[0] = f / aspect;
1891+
out[1] = 0;
1892+
out[2] = 0;
1893+
out[3] = 0;
1894+
out[4] = 0;
1895+
out[5] = f;
1896+
out[6] = 0;
1897+
out[7] = 0;
1898+
out[8] = 0;
1899+
out[9] = 0;
1900+
out[11] = -1;
1901+
out[12] = 0;
1902+
out[13] = 0;
1903+
out[15] = 0;
1904+
if (far !== Number.POSITIVE_INFINITY) {
1905+
const fn = 1 / (far - near);
1906+
out[10] = near * fn;
1907+
out[14] = far * near * fn;
1908+
} else {
1909+
out[10] = 0;
1910+
out[14] = near;
1911+
}
1912+
return out;
1913+
}
1914+
18721915
/**
18731916
* Generates a perspective projection matrix with the given field of view.
18741917
* This is primarily useful for generating projection matrices to be used

‎tst/unit/core/mat4.test.ts‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22
import type { Mat4, Vec3 } from '../../../src';
3-
import { mat4, quat } from '../../../src';
3+
import { mat4, quat, vec3 } from '../../../src';
44

55
describe('mat4', () => {
66
describe('create', () => {
@@ -673,6 +673,25 @@ describe('mat4', () => {
673673
expect(result[10]).not.toBe(mat4.perspectiveNO(mat4.create(), Math.PI / 4, 16 / 9, 0.1, 100)[10]);
674674
});
675675

676+
it('perspectiveReverseZ should map near to depth 1 and far to depth 0', () => {
677+
const projection = mat4.perspectiveReverseZ(mat4.create(), Math.PI / 3, 2, 0.1, 100);
678+
const point: Vec3 = [0, 0, 0];
679+
680+
expect(projection).toEqual(mat4.perspectiveZO(mat4.create(), Math.PI / 3, 2, 100, 0.1));
681+
expect(vec3.transformMat4(point, [0, 0, -0.1], projection)[2]).toBeCloseTo(1);
682+
expect(vec3.transformMat4(point, [0, 0, -100], projection)[2]).toBeCloseTo(0);
683+
});
684+
685+
it('perspectiveReverseZ should support an infinite far plane', () => {
686+
const projection = mat4.perspectiveReverseZ(mat4.create(), Math.PI / 3, 2, 0.1, Infinity);
687+
const point: Vec3 = [0, 0, 0];
688+
689+
expect(projection.every(Number.isFinite)).toBe(true);
690+
expect(vec3.transformMat4(point, [0, 0, -0.1], projection)[2]).toBeCloseTo(1);
691+
expect(vec3.transformMat4(point, [0, 0, -0.2], projection)[2]).toBeCloseTo(0.5);
692+
expect(vec3.transformMat4(point, [0, 0, -1e9], projection)[2]).toBeCloseTo(0, 8);
693+
});
694+
676695
it('orthoZO should create zero-to-one depth range orthographic', () => {
677696
const result = mat4.create();
678697
mat4.orthoZO(result, -1, 1, -1, 1, 0.1, 100);

0 commit comments

Comments
 (0)