Skip to content

Commit b070cb4

Browse files
mvaligurskyMartin Valigursky
and
Martin Valigursky
authored
More configurable order for mesh rendering (#7422)
* More configurable order for mesh rendering * lint --------- Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 9401034 commit b070cb4

File tree

5 files changed

+103
-101
lines changed

5 files changed

+103
-101
lines changed

src/scene/constants.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ export const fresnelNames = {
164164
// Legacy
165165
export const LAYER_HUD = 0;
166166
export const LAYER_GIZMO = 1;
167-
export const LAYER_FX = 2;
168167
// 3 - 14 are custom user layers
169168
export const LAYER_WORLD = 15;
170169

@@ -820,9 +819,6 @@ export const SHADOWUPDATE_THISFRAME = 1;
820819
*/
821820
export const SHADOWUPDATE_REALTIME = 2;
822821

823-
export const SORTKEY_FORWARD = 0;
824-
export const SORTKEY_DEPTH = 1;
825-
826822
// flags used on the mask property of the Light, and also on mask property of the MeshInstance
827823
export const MASK_AFFECT_DYNAMIC = 1;
828824
export const MASK_AFFECT_LIGHTMAPPED = 2;

src/scene/layer.js

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { Debug } from '../core/debug.js';
22
import { hash32Fnv1a } from '../core/hash.js';
33
import {
44
LIGHTTYPE_DIRECTIONAL,
5-
LAYER_FX,
6-
SORTKEY_FORWARD,
75
SORTMODE_BACK2FRONT, SORTMODE_CUSTOM, SORTMODE_FRONT2BACK, SORTMODE_MATERIALMESH, SORTMODE_NONE
86
} from './constants.js';
97
import { Material } from './materials/material.js';
@@ -28,20 +26,20 @@ function sortManual(drawCallA, drawCallB) {
2826
}
2927

3028
function sortMaterialMesh(drawCallA, drawCallB) {
31-
const keyA = drawCallA._key[SORTKEY_FORWARD];
32-
const keyB = drawCallB._key[SORTKEY_FORWARD];
33-
if (keyA === keyB && drawCallA.mesh && drawCallB.mesh) {
29+
const keyA = drawCallA._sortKeyForward;
30+
const keyB = drawCallB._sortKeyForward;
31+
if (keyA === keyB) {
3432
return drawCallB.mesh.id - drawCallA.mesh.id;
3533
}
3634
return keyB - keyA;
3735
}
3836

3937
function sortBackToFront(drawCallA, drawCallB) {
40-
return drawCallB.zdist - drawCallA.zdist;
38+
return drawCallB._sortKeyDynamic - drawCallA._sortKeyDynamic;
4139
}
4240

4341
function sortFrontToBack(drawCallA, drawCallB) {
44-
return drawCallA.zdist - drawCallB.zdist;
42+
return drawCallA._sortKeyDynamic - drawCallB._sortKeyDynamic;
4543
}
4644

4745
const sortCallbacks = [null, sortManual, sortMaterialMesh, sortBackToFront, sortFrontToBack];
@@ -797,24 +795,32 @@ class Layer {
797795

798796
/**
799797
* @param {MeshInstance[]} drawCalls - Array of mesh instances.
800-
* @param {number} drawCallsCount - Number of mesh instances.
801798
* @param {Vec3} camPos - Camera position.
802799
* @param {Vec3} camFwd - Camera forward vector.
803800
* @private
804801
*/
805-
_calculateSortDistances(drawCalls, drawCallsCount, camPos, camFwd) {
806-
for (let i = 0; i < drawCallsCount; i++) {
802+
_calculateSortDistances(drawCalls, camPos, camFwd) {
803+
const count = drawCalls.length;
804+
const { x: px, y: py, z: pz } = camPos;
805+
const { x: fx, y: fy, z: fz } = camFwd;
806+
807+
for (let i = 0; i < count; i++) {
807808
const drawCall = drawCalls[i];
808-
if (drawCall.layer <= LAYER_FX) continue; // Only alpha sort mesh instances in the main world (backwards comp)
809+
810+
// compute distance from camera to mesh along the forward vector
811+
let zDist;
809812
if (drawCall.calculateSortDistance) {
810-
drawCall.zdist = drawCall.calculateSortDistance(drawCall, camPos, camFwd);
811-
continue;
813+
zDist = drawCall.calculateSortDistance(drawCall, camPos, camFwd);
814+
} else {
815+
const meshPos = drawCall.aabb.center;
816+
zDist = (meshPos.x - px) * fx + (meshPos.y - py) * fy + (meshPos.z - pz) * fz;
812817
}
813-
const meshPos = drawCall.aabb.center;
814-
const tempx = meshPos.x - camPos.x;
815-
const tempy = meshPos.y - camPos.y;
816-
const tempz = meshPos.z - camPos.z;
817-
drawCall.zdist = tempx * camFwd.x + tempy * camFwd.y + tempz * camFwd.z;
818+
819+
// scale the bucket to give it a significantly higher magnitude than distance (1 billion)
820+
const bucket = drawCall._drawBucket * 1e9;
821+
822+
// create sorting key based on the drawBucket and distance
823+
drawCall._sortKeyDynamic = bucket + zDist;
818824
}
819825
}
820826

@@ -840,6 +846,7 @@ class Layer {
840846
* @ignore
841847
*/
842848
sortVisible(camera, transparent) {
849+
843850
const sortMode = transparent ? this.transparentSortMode : this.opaqueSortMode;
844851
if (sortMode === SORTMODE_NONE) {
845852
return;
@@ -863,7 +870,7 @@ class Layer {
863870
if (sortMode === SORTMODE_BACK2FRONT || sortMode === SORTMODE_FRONT2BACK) {
864871
const sortPos = cameraNode.getPosition();
865872
const sortDir = cameraNode.forward;
866-
this._calculateSortDistances(instances, instances.length, sortPos, sortDir);
873+
this._calculateSortDistances(instances, sortPos, sortDir);
867874
}
868875

869876
instances.sort(sortCallbacks[sortMode]);

src/scene/mesh-instance.js

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import { BoundingSphere } from '../core/shape/bounding-sphere.js';
44
import { BindGroup } from '../platform/graphics/bind-group.js';
55
import { UniformBuffer } from '../platform/graphics/uniform-buffer.js';
66
import {
7-
BLEND_NONE, BLEND_NORMAL,
87
LAYER_WORLD,
98
MASK_AFFECT_DYNAMIC, MASK_BAKE, MASK_AFFECT_LIGHTMAPPED,
109
RENDERSTYLE_SOLID,
1110
SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADERDEF_TANGENTS, SHADERDEF_NOSHADOW, SHADERDEF_SKIN,
1211
SHADERDEF_SCREENSPACE, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_BATCH,
13-
SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT, SHADERDEF_INSTANCING, SHADERDEF_MORPH_TEXTURE_BASED_INT,
14-
SORTKEY_FORWARD
12+
SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT, SHADERDEF_INSTANCING, SHADERDEF_MORPH_TEXTURE_BASED_INT
1513
} from './constants.js';
1614
import { GraphNode } from './graph-node.js';
1715
import { getDefaultMaterial } from './materials/default-material.js';
@@ -208,6 +206,12 @@ class MeshInstance {
208206
*/
209207
drawOrder = 0;
210208

209+
/**
210+
* @type {number}
211+
* @ignore
212+
*/
213+
_drawBucket = 127;
214+
211215
/**
212216
* The graph node defining the transform for this instance.
213217
*
@@ -322,8 +326,28 @@ class MeshInstance {
322326
/** @private */
323327
_updateAabbFunc = null;
324328

325-
/** @private */
326-
_key = [0, 0];
329+
/**
330+
* The internal sorting key used by the shadow renderer.
331+
*
332+
* @ignore
333+
*/
334+
_sortKeyShadow = 0;
335+
336+
/**
337+
* The internal sorting key used by the forward renderer, in case SORTMODE_MATERIALMESH sorting
338+
* is used.
339+
*
340+
* @private
341+
*/
342+
_sortKeyForward = 0;
343+
344+
/**
345+
* The internal sorting key used by the forward renderer, in case SORTMODE_BACK2FRONT or
346+
* SORTMODE_FRONT2BACK sorting is used.
347+
*
348+
* @ignore
349+
*/
350+
_sortKeyDynamic = 0;
327351

328352
/** @private */
329353
_layer = LAYER_WORLD;
@@ -419,6 +443,31 @@ class MeshInstance {
419443
this.updateKey();
420444
}
421445

446+
/**
447+
* Sets the draw bucket for mesh instances. The draw bucket, an integer from 0 to 255 (default
448+
* 127), serves as the primary sort key for mesh rendering. Meshes are sorted in ascending order
449+
* by draw bucket, then by sort mode. This setting is only effective when mesh instances are
450+
* added to a {@link Layer} with its {@link Layer#opaqueSortMode} or
451+
* {@link Layer#transparentSortMode} (depending on the material) set to
452+
* {@link SORTMODE_BACK2FRONT}, {@link SORTMODE_FRONT2BACK}, or {@link SORTMODE_MATERIALMESH}.
453+
*
454+
* @type {number}
455+
*/
456+
set drawBucket(bucket) {
457+
// 8bit integer
458+
this._drawBucket = Math.floor(bucket) & 0xff;
459+
this.updateKey();
460+
}
461+
462+
/**
463+
* Gets the draw bucket for mesh instance.
464+
*
465+
* @type {number}
466+
*/
467+
get drawBucket() {
468+
return this._drawBucket;
469+
}
470+
422471
/**
423472
* Sets the render style of the mesh instance. Can be:
424473
*
@@ -702,15 +751,6 @@ class MeshInstance {
702751
return this._material;
703752
}
704753

705-
set layer(layer) {
706-
this._layer = layer;
707-
this.updateKey();
708-
}
709-
710-
get layer() {
711-
return this._layer;
712-
}
713-
714754
/**
715755
* @param {number} shaderDefs - The shader definitions to set.
716756
* @private
@@ -827,11 +867,11 @@ class MeshInstance {
827867
}
828868

829869
set key(val) {
830-
this._key[SORTKEY_FORWARD] = val;
870+
this._sortKeyForward = val;
831871
}
832872

833873
get key() {
834-
return this._key[SORTKEY_FORWARD];
874+
return this._sortKeyForward;
835875
}
836876

837877
/**
@@ -965,21 +1005,15 @@ class MeshInstance {
9651005

9661006
updateKey() {
9671007

968-
// render alphatest/atoc after opaque
969-
const material = this.material;
970-
const blendType = (material.alphaToCoverage || material.alphaTest) ? BLEND_NORMAL : material.blendType;
971-
972-
// Key definition:
973-
// Bit
9741008
// 31 : sign bit (leave)
975-
// 27 - 30 : layer
976-
// 26 : translucency type (opaque/transparent)
977-
// 25 : unused
978-
// 0 - 24 : Material ID (if opaque) or 0 (if transparent - will be depth)
979-
this._key[SORTKEY_FORWARD] =
980-
((this.layer & 0x0f) << 27) |
981-
((blendType === BLEND_NONE ? 1 : 0) << 26) |
982-
((material.id & 0x1ffffff) << 0);
1009+
// 30 - 25 : 8 bits for draw bucket - this is the highest priority for sorting
1010+
// 24 : 1 bit for alpha test / coverage, to render them after opaque to keep GPU efficiency
1011+
// 23 - 0 : 24 bits for material ID
1012+
const { material } = this;
1013+
this._sortKeyForward =
1014+
(this._drawBucket << 25) |
1015+
((material.alphaToCoverage || material.alphaTest) ? 0x1000000 : 0) |
1016+
(material.id & 0xffffff);
9831017
}
9841018

9851019
/**

src/scene/renderer/renderer.js

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { BindGroup, DynamicBindGroup } from '../../platform/graphics/bind-group.
2222
import { UniformFormat, UniformBufferFormat } from '../../platform/graphics/uniform-buffer-format.js';
2323
import { BindGroupFormat, BindUniformBufferFormat, BindTextureFormat } from '../../platform/graphics/bind-group-format.js';
2424
import {
25-
SORTKEY_DEPTH, SORTKEY_FORWARD,
2625
VIEW_CENTER, PROJECTION_ORTHOGRAPHIC,
2726
LIGHTTYPE_DIRECTIONAL, MASK_AFFECT_DYNAMIC, MASK_AFFECT_LIGHTMAPPED, MASK_BAKE,
2827
SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME,
@@ -266,50 +265,6 @@ class Renderer {
266265
this.lightTextureAtlas = null;
267266
}
268267

269-
sortCompare(drawCallA, drawCallB) {
270-
if (drawCallA.layer === drawCallB.layer) {
271-
if (drawCallA.drawOrder && drawCallB.drawOrder) {
272-
return drawCallA.drawOrder - drawCallB.drawOrder;
273-
} else if (drawCallA.zdist && drawCallB.zdist) {
274-
return drawCallB.zdist - drawCallA.zdist; // back to front
275-
} else if (drawCallA.zdist2 && drawCallB.zdist2) {
276-
return drawCallA.zdist2 - drawCallB.zdist2; // front to back
277-
}
278-
}
279-
280-
return drawCallB._key[SORTKEY_FORWARD] - drawCallA._key[SORTKEY_FORWARD];
281-
}
282-
283-
sortCompareMesh(drawCallA, drawCallB) {
284-
if (drawCallA.layer === drawCallB.layer) {
285-
if (drawCallA.drawOrder && drawCallB.drawOrder) {
286-
return drawCallA.drawOrder - drawCallB.drawOrder;
287-
} else if (drawCallA.zdist && drawCallB.zdist) {
288-
return drawCallB.zdist - drawCallA.zdist; // back to front
289-
}
290-
}
291-
292-
const keyA = drawCallA._key[SORTKEY_FORWARD];
293-
const keyB = drawCallB._key[SORTKEY_FORWARD];
294-
295-
if (keyA === keyB && drawCallA.mesh && drawCallB.mesh) {
296-
return drawCallB.mesh.id - drawCallA.mesh.id;
297-
}
298-
299-
return keyB - keyA;
300-
}
301-
302-
sortCompareDepth(drawCallA, drawCallB) {
303-
const keyA = drawCallA._key[SORTKEY_DEPTH];
304-
const keyB = drawCallB._key[SORTKEY_DEPTH];
305-
306-
if (keyA === keyB && drawCallA.mesh && drawCallB.mesh) {
307-
return drawCallB.mesh.id - drawCallA.mesh.id;
308-
}
309-
310-
return keyB - keyA;
311-
}
312-
313268
/**
314269
* Set up the viewport and the scissor for camera rendering.
315270
*

src/scene/renderer/shadow-renderer.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI,
1313
SHADER_SHADOW,
1414
SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME,
15-
SORTKEY_DEPTH,
1615
shadowTypeInfo
1716
} from '../constants.js';
1817
import { ShaderPass } from '../shader-pass.js';
@@ -192,7 +191,18 @@ class ShadowRenderer {
192191
}
193192

194193
// this sorts the shadow casters by the shader id
195-
visible.sort(this.renderer.sortCompareDepth);
194+
visible.sort(this.sortCompareShader);
195+
}
196+
197+
sortCompareShader(drawCallA, drawCallB) {
198+
const keyA = drawCallA._sortKeyShadow;
199+
const keyB = drawCallB._sortKeyShadow;
200+
201+
if (keyA === keyB) {
202+
return drawCallB.mesh.id - drawCallA.mesh.id;
203+
}
204+
205+
return keyB - keyA;
196206
}
197207

198208
setupRenderState(device, light) {
@@ -316,7 +326,7 @@ class ShadowRenderer {
316326
Debug.assert(shadowShader, `no shader for pass ${shadowPass}`, material);
317327

318328
// sort shadow casters by shader
319-
meshInstance._key[SORTKEY_DEPTH] = shadowShader.id;
329+
meshInstance._sortKeyShadow = shadowShader.id;
320330

321331
device.setShader(shadowShader);
322332

0 commit comments

Comments
 (0)