Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/scene/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,49 @@ const _frustumPoints = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3
* @ignore
*/
class Camera {
/** @private */
static _flipYProjectionMatrix = new Mat4().setScale(1, -1, 1);

/** @private */
static _webGpuDepthRangeMatrix = new Mat4().set([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0.5, 0,
0, 0, 0.5, 1
]);

/** @private */
static _applyShaderProjectionScratch = new Mat4();

/**
* Builds the projection matrix matching shader `matrix_projection` after optional flip-Y
* for render targets and optional WebGPU clip-depth range adjustment.
*
* @param {Mat4} projection - Source projection ({@link Camera#projectionMatrix}).
* @param {Mat4} out - Receives the transformed matrix.
* @param {boolean} flipY - When true, apply render-target Y flip first.
* @param {boolean} applyWebGpuDepthRange - When true, map clip Z from -1..1 to 0..1.
* @returns {Mat4} out
*/
static applyShaderProjectionTransform(projection, out, flipY, applyWebGpuDepthRange) {
if (!flipY && !applyWebGpuDepthRange) {
out.copy(projection);
return out;
}
if (flipY && applyWebGpuDepthRange) {
const scratch = Camera._applyShaderProjectionScratch;
scratch.mul2(Camera._flipYProjectionMatrix, projection);
out.mul2(Camera._webGpuDepthRangeMatrix, scratch);
return out;
}
if (flipY) {
out.mul2(Camera._flipYProjectionMatrix, projection);
return out;
}
out.mul2(Camera._webGpuDepthRangeMatrix, projection);
return out;
}

/**
* @type {ShaderPassInfo|null}
*/
Expand Down
11 changes: 7 additions & 4 deletions src/scene/gsplat-unified/gsplat-compute-local-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { GSPLAT_FORWARD, PROJECTION_ORTHOGRAPHIC, FOG_NONE, GSPLAT_DEBUG_HEATMAP
import { Debug } from '../../core/debug.js';
import { Color } from '../../core/math/color.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Camera } from '../camera.js';
import { GSplatRenderer } from './gsplat-renderer.js';
import { FramePassGSplatComputeLocal } from './frame-pass-gsplat-compute-local.js';
import { computeGsplatLocalDispatchPrepSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-dispatch-prep.js';
Expand Down Expand Up @@ -85,6 +86,7 @@ const MAX_CHUNKS_PER_TILE = 8;
const _viewProjMat = new Mat4();
const _viewProjData = new Float32Array(16);
const _viewData = new Float32Array(16);
const _shaderProjMat = new Mat4();
const _fogColorLinear = new Color();
const _fogColorArray = new Float32Array(3);

Expand Down Expand Up @@ -640,16 +642,17 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
const cam = camera.camera;

const view = cam.viewMatrix;
const proj = cam.projectionMatrix;
_viewProjMat.mul2(proj, view);
const flipY = !!camera.renderTarget?.flipY;
const webgpu = device.isWebGPU;
_viewProjMat.mul2(Camera.applyShaderProjectionTransform(cam.projectionMatrix, _shaderProjMat, flipY, webgpu), view);
_viewProjData.set(_viewProjMat.data);
_viewData.set(view.data);
const focal = width * proj.data[0];
const focal = width * _shaderProjMat.data[0];

const alphaClip = pickMode ? this._alphaClip : (1.0 / 255.0);

// Ensure fisheyeProj is up-to-date (culling may not have run this frame)
this.fisheyeProj.update(this._fisheye, camera.fov, proj);
this.fisheyeProj.update(this._fisheye, camera.fov, cam.projectionMatrix);

const fisheyeEnabled = this.fisheyeProj.enabled;
const createCountShader = (pick, fisheye) => this._createCountShaderAndFormat(pick, fisheye);
Expand Down
10 changes: 7 additions & 3 deletions src/scene/gsplat-unified/gsplat-hybrid-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { GSplatResourceBase } from '../gsplat/gsplat-resource-base.js';
import { MeshInstance } from '../mesh-instance.js';
import { GSplatRenderer } from './gsplat-renderer.js';
import { CACHE_STRIDE } from './gsplat-projector-constants.js';
import { Camera } from '../camera.js';

// Module-scope scratch matrix used only inside `_computeClipToViewZ`. The output
// Module-scope scratch matrices used only inside `_computeClipToViewZ`. The output
// (`Float32Array`) lives on each renderer instance because it must remain valid
// until the GPU upload happens at draw time, and multiple renderer instances may
// render concurrently with different cameras.
const _invProjMat = new Mat4();
const _shaderProjMat = new Mat4();

/**
* @import { StorageBuffer } from '../../platform/graphics/storage-buffer.js'
Expand Down Expand Up @@ -277,7 +279,8 @@ class GSplatHybridRenderer extends GSplatRenderer {
* @private
*/
_computeClipToViewZ(cameraNode, dst) {
const cam = cameraNode.camera;
const camComp = cameraNode.camera;
const cam = camComp.camera;
if (this.fisheyeProj.enabled) {
const near = cam.nearClip;
const far = cam.farClip;
Expand All @@ -287,7 +290,8 @@ class GSplatHybridRenderer extends GSplatRenderer {
dst[3] = near;
return;
}
_invProjMat.copy(cam.projectionMatrix).invert();
const flipY = !!camComp.renderTarget?.flipY;
_invProjMat.copy(Camera.applyShaderProjectionTransform(cam.projectionMatrix, _shaderProjMat, flipY, this.device.isWebGPU)).invert();
const d = _invProjMat.data;
dst[0] = -d[2];
dst[1] = -d[6];
Expand Down
1 change: 1 addition & 0 deletions src/scene/gsplat-unified/gsplat-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,7 @@ class GSplatManager {
minContribution: gsplat.minContribution,
viewportWidth,
viewportHeight,
flipY: !!cameraNode.camera.renderTarget?.flipY,
pickMode,
fisheyeProj
});
Expand Down
13 changes: 9 additions & 4 deletions src/scene/gsplat-unified/gsplat-projector.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
UNIFORMTYPE_VEC3
} from '../../platform/graphics/constants.js';
import { PROJECTION_ORTHOGRAPHIC } from '../constants.js';
import { Camera } from '../camera.js';
import { GSplatResourceBase } from '../gsplat/gsplat-resource-base.js';
import { GSplatSortBinWeights } from './gsplat-sort-bin-weights.js';
import { CACHE_STRIDE } from './gsplat-projector-constants.js';
Expand All @@ -51,6 +52,7 @@ const _dispatchSize = new Vec2();
const _viewProjMat = new Mat4();
const _viewProjData = new Float32Array(16);
const _viewData = new Float32Array(16);
const _shaderProjMat = new Mat4();

/**
* Owns the per-splat compute pass for the hybrid GSplat renderer (Pass B in the pipeline):
Expand Down Expand Up @@ -449,6 +451,8 @@ class GSplatProjector {
* @param {number} params.minContribution - Minimum total contribution before culling.
* @param {number} params.viewportWidth - Render viewport width in pixels.
* @param {number} params.viewportHeight - Render viewport height in pixels.
* @param {boolean} params.flipY - Whether the active render target uses `flipY` (must match
* {@link Renderer#setCameraUniforms}).
* @param {boolean} [params.pickMode] - Whether to write picking IDs into the cache.
* @param {import('../graphics/fisheye-projection.js').FisheyeProjection} [params.fisheyeProj] -
* Fisheye projection state. When `fisheyeProj.enabled` is true the projector picks the
Expand All @@ -459,7 +463,8 @@ class GSplatProjector {
workBuffer, cameraNode, compactedSplatIds, sortElementCountBuffer,
totalCapacity, radialSort, numBits, minDist, maxDist,
alphaClip, minPixelSize, minContribution,
viewportWidth, viewportHeight, pickMode = false,
viewportWidth, viewportHeight, flipY,
pickMode = false,
fisheyeProj
} = params;

Expand Down Expand Up @@ -508,12 +513,12 @@ class GSplatProjector {
const cameraComponent = cameraNode.camera;
const cam = cameraComponent.camera;
const view = cam.viewMatrix;
const proj = cam.projectionMatrix;
_viewProjMat.mul2(proj, view);
const webgpu = this.device.isWebGPU;
_viewProjMat.mul2(Camera.applyShaderProjectionTransform(cam.projectionMatrix, _shaderProjMat, flipY, webgpu), view);
_viewProjData.set(_viewProjMat.data);
_viewData.set(view.data);

const focal = viewportWidth * proj.data[0];
const focal = viewportWidth * _shaderProjMat.data[0];

this.cameraPositionData[0] = cameraPos.x;
this.cameraPositionData[1] = cameraPos.y;
Expand Down
27 changes: 4 additions & 23 deletions src/scene/renderer/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ import { ShadowRendererDirectional } from './shadow-renderer-directional.js';
import { ShadowRenderer } from './shadow-renderer.js';
import { WorldClustersAllocator } from './world-clusters-allocator.js';
import { FramePassUpdateClustered } from './frame-pass-update-clustered.js';
import { Camera } from '../camera.js';

/**
* @import { Camera } from '../camera.js'
* @import { CulledInstances } from '../layer.js'
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { LayerComposition } from '../composition/layer-composition.js'
Expand All @@ -58,19 +58,10 @@ const viewMat = new Mat4();
const viewMat3 = new Mat3();
const tempSphere = new BoundingSphere();
const tempFrustum = new Frustum();
const _flipYMat = new Mat4().setScale(1, -1, 1);
const _tempLightSet = new Set();
const _tempLayerSet = new Set();
const _dynamicBindGroup = new DynamicBindGroup();

// Converts a projection matrix in OpenGL style (depth range of -1..1) to a DirectX style (depth range of 0..1).
const _fixProjRangeMat = new Mat4().set([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0.5, 0,
0, 0, 0.5, 1
]);

// helton sequence of 2d offsets for jittering
const _haltonSequence = [
new Vec2(0.5, 0.333333),
Expand All @@ -93,8 +84,6 @@ const _haltonSequence = [

const _tempProjMat0 = new Mat4();
const _tempProjMat1 = new Mat4();
const _tempProjMat2 = new Mat4();
const _tempProjMat3 = new Mat4();
const _tempProjMat4 = new Mat4();
const _tempProjMat5 = new Mat4();
const _tempSet = new Set();
Expand Down Expand Up @@ -335,17 +324,9 @@ class Renderer {
}
let projMatSkybox = camera.getProjectionMatrixSkybox();

// flip projection matrices
if (flipY) {
projMat = _tempProjMat0.mul2(_flipYMat, projMat);
projMatSkybox = _tempProjMat1.mul2(_flipYMat, projMatSkybox);
}

// update depth range of projection matrices (-1..1 to 0..1)
if (this.device.isWebGPU) {
projMat = _tempProjMat2.mul2(_fixProjRangeMat, projMat);
projMatSkybox = _tempProjMat3.mul2(_fixProjRangeMat, projMatSkybox);
}
const webgpu = this.device.isWebGPU;
projMat = Camera.applyShaderProjectionTransform(projMat, _tempProjMat0, flipY, webgpu);
projMatSkybox = Camera.applyShaderProjectionTransform(projMatSkybox, _tempProjMat1, flipY, webgpu);

// camera jitter
const { jitter } = camera;
Expand Down