Skip to content

Commit bcc6836

Browse files
georginahalpernGeorgina Halpern
andauthored
Add floatingOriginMode to the scene (#17183)
This PR enables floatingOriginMode on the scene (defined at scene construction time), which will keep track of the active camera's position and use that as the floatingOriginOffset when sending uniform values to shader. In this mode we center the camera at world origin and offset all meshes by the floatingOriginOffset. This helps avoid floating point imprecision issues when dealing with large real world coordinates. This behavior is achieved by overriding the uniformBuffer / effect setMatrix functions to set viewMatrix translation == (0,0,0), worldMatrix translation += floatingOriginOffset, and re-multiplying the resulting worldView/viewProjection/worldViewProjection matrices with offset. We also offset the light position (stored in vLightData) as well as the vEyePosition uniforms. This is done at the callsite (rather than overriding at the effect/uniform layer). --------- Co-authored-by: Georgina Halpern <[email protected]>
1 parent fc142e7 commit bcc6836

File tree

6 files changed

+243
-16
lines changed

6 files changed

+243
-16
lines changed

packages/dev/core/src/Lights/Clustered/clusteredLightContainer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,9 @@ export class ClusteredLightContainer extends Light {
389389
const inverseSquaredRange = Math.max(light._inverseSquaredRange, this._minInverseSquaredRange);
390390

391391
// vLightData
392-
buf[off + 0] = position.x;
393-
buf[off + 1] = position.y;
394-
buf[off + 2] = position.z;
392+
buf[off + 0] = position.x - this._scene.floatingOriginOffset.x;
393+
buf[off + 1] = position.y - this._scene.floatingOriginOffset.y;
394+
buf[off + 2] = position.z - this._scene.floatingOriginOffset.z;
395395
buf[off + 3] = 0;
396396
// vLightDiffuse
397397
buf[off + 4] = diffuse.r;

packages/dev/core/src/Lights/pointLight.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,23 @@ export class PointLight extends ShadowLight {
189189
*/
190190
public transferToEffect(effect: Effect, lightIndex: string): PointLight {
191191
if (this.computeTransformedInformation()) {
192-
this._uniformBuffer.updateFloat4("vLightData", this.transformedPosition.x, this.transformedPosition.y, this.transformedPosition.z, 0.0, lightIndex);
192+
this._uniformBuffer.updateFloat4(
193+
"vLightData",
194+
this.transformedPosition.x - this._scene.floatingOriginOffset.x,
195+
this.transformedPosition.y - this._scene.floatingOriginOffset.y,
196+
this.transformedPosition.z - this._scene.floatingOriginOffset.z,
197+
0.0,
198+
lightIndex
199+
);
193200
} else {
194-
this._uniformBuffer.updateFloat4("vLightData", this.position.x, this.position.y, this.position.z, 0, lightIndex);
201+
this._uniformBuffer.updateFloat4(
202+
"vLightData",
203+
this.position.x - this._scene.floatingOriginOffset.x,
204+
this.position.y - this._scene.floatingOriginOffset.y,
205+
this.position.z - this._scene.floatingOriginOffset.z,
206+
0,
207+
lightIndex
208+
);
195209
}
196210

197211
this._uniformBuffer.updateFloat4("vLightFalloff", this.range, this._inverseSquaredRange, 0, 0, lightIndex);
@@ -200,9 +214,19 @@ export class PointLight extends ShadowLight {
200214

201215
public transferToNodeMaterialEffect(effect: Effect, lightDataUniformName: string) {
202216
if (this.computeTransformedInformation()) {
203-
effect.setFloat3(lightDataUniformName, this.transformedPosition.x, this.transformedPosition.y, this.transformedPosition.z);
217+
effect.setFloat3(
218+
lightDataUniformName,
219+
this.transformedPosition.x - this._scene.floatingOriginOffset.x,
220+
this.transformedPosition.y - this._scene.floatingOriginOffset.y,
221+
this.transformedPosition.z - this._scene.floatingOriginOffset.z
222+
);
204223
} else {
205-
effect.setFloat3(lightDataUniformName, this.position.x, this.position.y, this.position.z);
224+
effect.setFloat3(
225+
lightDataUniformName,
226+
this.position.x - this._scene.floatingOriginOffset.x,
227+
this.position.y - this._scene.floatingOriginOffset.y,
228+
this.position.z - this._scene.floatingOriginOffset.z
229+
);
206230
}
207231

208232
return this;

packages/dev/core/src/Lights/rectAreaLight.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,14 @@ export class RectAreaLight extends AreaLight {
115115
*/
116116
public transferToEffect(effect: Effect, lightIndex: string): RectAreaLight {
117117
if (this._computeTransformedInformation()) {
118-
this._uniformBuffer.updateFloat4("vLightData", this._pointTransformedPosition.x, this._pointTransformedPosition.y, this._pointTransformedPosition.z, 0, lightIndex);
118+
this._uniformBuffer.updateFloat4(
119+
"vLightData",
120+
this._pointTransformedPosition.x - this._scene.floatingOriginOffset.x,
121+
this._pointTransformedPosition.y - this._scene.floatingOriginOffset.y,
122+
this._pointTransformedPosition.z - this._scene.floatingOriginOffset.z,
123+
0,
124+
lightIndex
125+
);
119126
this._uniformBuffer.updateFloat4("vLightWidth", this._pointTransformedWidth.x / 2, this._pointTransformedWidth.y / 2, this._pointTransformedWidth.z / 2, 0, lightIndex);
120127
this._uniformBuffer.updateFloat4(
121128
"vLightHeight",
@@ -126,7 +133,14 @@ export class RectAreaLight extends AreaLight {
126133
lightIndex
127134
);
128135
} else {
129-
this._uniformBuffer.updateFloat4("vLightData", this.position.x, this.position.y, this.position.z, 0, lightIndex);
136+
this._uniformBuffer.updateFloat4(
137+
"vLightData",
138+
this.position.x - this._scene.floatingOriginOffset.x,
139+
this.position.y - this._scene.floatingOriginOffset.y,
140+
this.position.z - this._scene.floatingOriginOffset.z,
141+
0,
142+
lightIndex
143+
);
130144
this._uniformBuffer.updateFloat4("vLightWidth", this._width.x / 2, this._width.y / 2, this._width.z / 2, 0.0, lightIndex);
131145
this._uniformBuffer.updateFloat4("vLightHeight", this._height.x / 2, this._height.y / 2, this._height.z / 2, 0.0, lightIndex);
132146
}
@@ -135,9 +149,19 @@ export class RectAreaLight extends AreaLight {
135149

136150
public transferToNodeMaterialEffect(effect: Effect, lightDataUniformName: string) {
137151
if (this._computeTransformedInformation()) {
138-
effect.setFloat3(lightDataUniformName, this._pointTransformedPosition.x, this._pointTransformedPosition.y, this._pointTransformedPosition.z);
152+
effect.setFloat3(
153+
lightDataUniformName,
154+
this._pointTransformedPosition.x - this._scene.floatingOriginOffset.x,
155+
this._pointTransformedPosition.y - this._scene.floatingOriginOffset.y,
156+
this._pointTransformedPosition.z - this._scene.floatingOriginOffset.z
157+
);
139158
} else {
140-
effect.setFloat3(lightDataUniformName, this.position.x, this.position.y, this.position.z);
159+
effect.setFloat3(
160+
lightDataUniformName,
161+
this.position.x - this._scene.floatingOriginOffset.x,
162+
this.position.y - this._scene.floatingOriginOffset.y,
163+
this.position.z - this._scene.floatingOriginOffset.z
164+
);
141165
}
142166
return this;
143167
}

packages/dev/core/src/Lights/spotLight.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,11 +431,25 @@ export class SpotLight extends ShadowLight {
431431
let normalizeDirection;
432432

433433
if (this.computeTransformedInformation()) {
434-
this._uniformBuffer.updateFloat4("vLightData", this.transformedPosition.x, this.transformedPosition.y, this.transformedPosition.z, this.exponent, lightIndex);
434+
this._uniformBuffer.updateFloat4(
435+
"vLightData",
436+
this.transformedPosition.x - this._scene.floatingOriginOffset.x,
437+
this.transformedPosition.y - this._scene.floatingOriginOffset.y,
438+
this.transformedPosition.z - this._scene.floatingOriginOffset.z,
439+
this.exponent,
440+
lightIndex
441+
);
435442

436443
normalizeDirection = Vector3.Normalize(this.transformedDirection);
437444
} else {
438-
this._uniformBuffer.updateFloat4("vLightData", this.position.x, this.position.y, this.position.z, this.exponent, lightIndex);
445+
this._uniformBuffer.updateFloat4(
446+
"vLightData",
447+
this.position.x - this._scene.floatingOriginOffset.x,
448+
this.position.y - this._scene.floatingOriginOffset.y,
449+
this.position.z - this._scene.floatingOriginOffset.z,
450+
this.exponent,
451+
lightIndex
452+
);
439453

440454
normalizeDirection = Vector3.Normalize(this.direction);
441455
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Effect } from "../Materials/effect";
2+
import { TmpVectors } from "../Maths/math.vector";
3+
import type { Matrix } from "../Maths/math.vector";
4+
import type { IMatrixLike, IVector3Like } from "../Maths/math.like";
5+
import { InvertMatrixToRef, MultiplyMatricesToRef } from "../Maths/ThinMaths/thinMath.matrix.functions";
6+
import type { Scene } from "../scene";
7+
import type { DeepImmutable } from "../types";
8+
import { UniformBuffer } from "./uniformBuffer";
9+
10+
const TempFinalMat: Matrix = TmpVectors.Matrix[4];
11+
const TempMat1: Matrix = TmpVectors.Matrix[5];
12+
const TempMat2: Matrix = TmpVectors.Matrix[6];
13+
14+
function OffsetWorldToRef(offset: IVector3Like, world: DeepImmutable<IMatrixLike>, ref: Matrix): DeepImmutable<IMatrixLike> {
15+
const refArray = ref.asArray();
16+
const worldArray = world.asArray();
17+
for (let i = 0; i < 16; i++) {
18+
refArray[i] = worldArray[i];
19+
}
20+
refArray[12] -= offset.x;
21+
refArray[13] -= offset.y;
22+
refArray[14] -= offset.z;
23+
return ref;
24+
}
25+
26+
function OffsetViewToRef(view: DeepImmutable<IMatrixLike>, ref: Matrix): DeepImmutable<IMatrixLike> {
27+
const refArray = ref.asArray();
28+
const viewArray = view.asArray();
29+
for (let i = 0; i < 16; i++) {
30+
refArray[i] = viewArray[i];
31+
}
32+
refArray[12] = 0;
33+
refArray[13] = 0;
34+
refArray[14] = 0;
35+
return ref;
36+
}
37+
38+
function OffsetViewProjectionToRef(view: DeepImmutable<IMatrixLike>, projection: DeepImmutable<IMatrixLike>, ref: Matrix): DeepImmutable<IMatrixLike> {
39+
MultiplyMatricesToRef(OffsetViewToRef(view, ref), projection, ref);
40+
return ref;
41+
}
42+
43+
function OffsetWorldViewToRef(offset: IVector3Like, worldView: DeepImmutable<IMatrixLike>, view: DeepImmutable<IMatrixLike>, ref: Matrix): DeepImmutable<IMatrixLike> {
44+
// ( world * view ) * inverse ( view ) = world
45+
InvertMatrixToRef(view, TempMat1); // TempMat1 = inverseView
46+
MultiplyMatricesToRef(worldView, TempMat1, TempMat2); // TempMat2 = world, TempMat1 can be reused
47+
48+
// ( offsetWorld * offsetView ) = offsetWorldView
49+
OffsetWorldToRef(offset, TempMat2, TempMat1); // TempMat1 = offsetWorld
50+
OffsetViewToRef(view, TempMat2); // TempMat2 = offsetView
51+
MultiplyMatricesToRef(TempMat1, TempMat2, ref);
52+
53+
return ref;
54+
}
55+
56+
function OffsetWorldViewProjectionToRef(
57+
offset: IVector3Like,
58+
worldViewProjection: DeepImmutable<IMatrixLike>,
59+
viewProjection: DeepImmutable<IMatrixLike>,
60+
view: DeepImmutable<IMatrixLike>,
61+
projection: DeepImmutable<IMatrixLike>,
62+
ref: IMatrixLike
63+
): DeepImmutable<IMatrixLike> {
64+
// ( world * view * projection ) * inverse(projection) * inverse(view) = world
65+
// ( world * view * projection ) * inverse (view * projection) = world
66+
InvertMatrixToRef(viewProjection, TempMat1); // TempMat1 = inverse (view * projection)
67+
MultiplyMatricesToRef(worldViewProjection, TempMat1, TempMat2); // TempMat2 = world, TempMat1 can be reused
68+
69+
// ( offsetWorld * offsetViewProjection) = offsetWorldViewProjection
70+
OffsetWorldToRef(offset, TempMat2, TempMat1); // TempMat1 = offsetWorld
71+
OffsetViewProjectionToRef(view, projection, TempMat2); // TempMat2 = offsetViewProjection
72+
MultiplyMatricesToRef(TempMat1, TempMat2, ref);
73+
74+
return ref;
75+
}
76+
77+
function GetOffsetMatrix(uniformName: string, mat: IMatrixLike, scene: Scene): IMatrixLike {
78+
TempFinalMat.updateFlag = mat.updateFlag;
79+
switch (uniformName) {
80+
case "world":
81+
return OffsetWorldToRef(scene.floatingOriginOffset, mat, TempFinalMat);
82+
case "view":
83+
return OffsetViewToRef(mat, TempFinalMat);
84+
case "worldView":
85+
return OffsetWorldViewToRef(scene.floatingOriginOffset, mat, scene.getViewMatrix(), TempFinalMat);
86+
case "viewProjection":
87+
return OffsetViewProjectionToRef(scene.getViewMatrix(), scene.getProjectionMatrix(), TempFinalMat);
88+
case "worldViewProjection":
89+
return OffsetWorldViewProjectionToRef(scene.floatingOriginOffset, mat, scene.getTransformMatrix(), scene.getViewMatrix(), scene.getProjectionMatrix(), TempFinalMat);
90+
default:
91+
return mat;
92+
}
93+
}
94+
95+
// ---- Overriding the prototypes of effect and uniformBuffer's setMatrix functions ----
96+
const UniformBufferInternal = UniformBuffer as any;
97+
const OriginalUpdateMatrixForUniform = UniformBufferInternal.prototype._updateMatrixForUniform;
98+
const OriginalSetMatrix = Effect.prototype.setMatrix;
99+
100+
export function ResetMatrixFunctions() {
101+
Effect.prototype.setMatrix = OriginalSetMatrix;
102+
UniformBufferInternal.prototype._updateMatrixForUniform = OriginalUpdateMatrixForUniform;
103+
UniformBufferInternal.prototype._updateMatrixForUniformOverride = undefined;
104+
}
105+
export function OverrideMatrixFunctions(scene: Scene) {
106+
Effect.prototype.setMatrix = function (uniformName: string, matrix: IMatrixLike) {
107+
this._pipelineContext!.setMatrix(uniformName, GetOffsetMatrix(uniformName, matrix, scene));
108+
return this;
109+
};
110+
UniformBufferInternal.prototype._updateMatrixForUniformOverride = UniformBufferInternal.prototype._updateMatrixForUniform;
111+
UniformBufferInternal.prototype._updateMatrixForUniform = function (uniformName: string, matrix: IMatrixLike) {
112+
this._updateMatrixForUniformOverride(uniformName, GetOffsetMatrix(uniformName, matrix, scene));
113+
};
114+
}

packages/dev/core/src/scene.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type { KeyboardInfoPre, KeyboardInfo } from "./Events/keyboardEvents";
2121
import { ActionEvent } from "./Actions/actionEvent";
2222
import { PostProcessManager } from "./PostProcesses/postProcessManager";
2323
import type { IOfflineProvider } from "./Offline/IOfflineProvider";
24+
import { OverrideMatrixFunctions, ResetMatrixFunctions } from "./Materials/floatingOriginMatrixOverrides";
2425
import type { RenderingGroupInfo, IRenderingManagerAutoClearSetup } from "./Rendering/renderingManager";
2526
import { RenderingManager } from "./Rendering/renderingManager";
2627
import type {
@@ -137,6 +138,14 @@ export interface SceneOptions {
137138

138139
/** Defines if the creation of the scene should impact the engine (Eg. UtilityLayer's scene) */
139140
virtual?: boolean;
141+
142+
/**
143+
* @experimental
144+
* FloatingOriginMode helps avoid floating point imprecision of rendering large worlds by
145+
* 1. Forcing the engine to use doublePrecision mode
146+
* 2. Offsetting uniform values before passing to shader so that camera is centered at origin and world is offset by camera position
147+
*/
148+
floatingOriginMode?: boolean;
140149
}
141150

142151
/**
@@ -1227,11 +1236,18 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer {
12271236

12281237
TmpVectors.Vector4[0].set(eyePosition.x, eyePosition.y, eyePosition.z, invertNormal ? -1 : 1);
12291238

1239+
TmpVectors.Vector4[1].copyFromFloats(
1240+
TmpVectors.Vector4[0].x - this.floatingOriginOffset.x,
1241+
TmpVectors.Vector4[0].y - this.floatingOriginOffset.y,
1242+
TmpVectors.Vector4[0].z - this.floatingOriginOffset.z,
1243+
TmpVectors.Vector4[0].w
1244+
);
1245+
12301246
if (effect) {
12311247
if (isVector3) {
1232-
effect.setFloat3(variableName, TmpVectors.Vector4[0].x, TmpVectors.Vector4[0].y, TmpVectors.Vector4[0].z);
1248+
effect.setFloat3(variableName, TmpVectors.Vector4[1].x, TmpVectors.Vector4[1].y, TmpVectors.Vector4[1].z);
12331249
} else {
1234-
effect.setVector4(variableName, TmpVectors.Vector4[0]);
1250+
effect.setVector4(variableName, TmpVectors.Vector4[1]);
12351251
}
12361252
}
12371253

@@ -1245,7 +1261,14 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer {
12451261
public finalizeSceneUbo(): UniformBuffer {
12461262
const ubo = this.getSceneUniformBuffer();
12471263
const eyePosition = this.bindEyePosition(null);
1248-
ubo.updateFloat4("vEyePosition", eyePosition.x, eyePosition.y, eyePosition.z, eyePosition.w);
1264+
1265+
ubo.updateFloat4(
1266+
"vEyePosition",
1267+
eyePosition.x - this.floatingOriginOffset.x,
1268+
eyePosition.y - this.floatingOriginOffset.y,
1269+
eyePosition.z - this.floatingOriginOffset.z,
1270+
eyePosition.w
1271+
);
12491272

12501273
ubo.update();
12511274

@@ -1997,6 +2020,12 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer {
19972020
engine.scenes.push(this);
19982021
}
19992022

2023+
if (options?.floatingOriginMode) {
2024+
engine.getCreationOptions().useHighPrecisionMatrix = true;
2025+
OverrideMatrixFunctions(this);
2026+
this._floatingOriginMode = true;
2027+
}
2028+
20002029
this._uid = null;
20012030

20022031
this._renderingManager = new RenderingManager(this);
@@ -2754,6 +2783,26 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer {
27542783
this._projectionUpdateFlag = -1;
27552784
}
27562785

2786+
private _floatingOriginMode: boolean = false;
2787+
/**
2788+
* @experimental
2789+
* When true, enables floatingOriginMode which helps avoid floating point imprecision when using huge coordinate system by
2790+
* 1. Forcing the engine to use doublePrecision mode
2791+
* 2. Offsetting uniform values before passing to shader so that camera is centered at origin and world is offset by camera position
2792+
*/
2793+
public get floatingOriginMode(): boolean {
2794+
return this._floatingOriginMode;
2795+
}
2796+
2797+
private _floatingOriginOffsetDefault: Vector3 = Vector3.Zero();
2798+
/**
2799+
* @experimental
2800+
* When floatingOriginMode is enabled, offset is equal to the active camera position. If no active camera or floatingOriginMode is disabled, offset is 0.
2801+
*/
2802+
public get floatingOriginOffset(): Vector3 {
2803+
return this.floatingOriginMode && this.activeCamera ? this.activeCamera.position : this._floatingOriginOffsetDefault;
2804+
}
2805+
27572806
/**
27582807
* Gets an unique (relatively to the current scene) Id
27592808
* @returns an unique number for the scene
@@ -5713,6 +5762,8 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer {
57135762
this.onClearColorChangedObservable.clear();
57145763
this.onEnvironmentTextureChangedObservable.clear();
57155764
this.onMeshUnderPointerUpdatedObservable.clear();
5765+
5766+
ResetMatrixFunctions();
57165767
this._isDisposed = true;
57175768
}
57185769

0 commit comments

Comments
 (0)