Skip to content

Commit b8a24da

Browse files
committed
basic dynamic instancing
1 parent 4589c2c commit b8a24da

File tree

4 files changed

+135
-3
lines changed

4 files changed

+135
-3
lines changed

packages/engine/Source/Scene/Model/Model.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,32 @@ Object.defineProperties(Model.prototype, {
753753
},
754754
},
755755

756+
/**
757+
* Sets model matrices for instancing
758+
*
759+
* @memberof Model.prototype
760+
* @type {boolean}
761+
*
762+
* @default []
763+
*/
764+
apiInstances: {
765+
get: function () {
766+
return this._apiInstances;
767+
},
768+
set: function (value) {
769+
this._apiInstances = value;
770+
for (const runtimeNode in this._sceneGraph._runtimeNodes) {
771+
if (
772+
defined(
773+
this._sceneGraph._runtimeNodes[runtimeNode]._apiInstancesDirty,
774+
)
775+
) {
776+
this._sceneGraph._runtimeNodes[runtimeNode]._apiInstancesDirty = true;
777+
}
778+
}
779+
},
780+
},
781+
756782
/**
757783
* Whether or not to cull the model using frustum/horizon culling. If the model is part of a 3D Tiles tileset, this property
758784
* will always be false, since the 3D Tiles culling system is used.
@@ -3086,7 +3112,7 @@ Model.fromGltfAsync = async function (options) {
30863112
const model = new Model(modelOptions);
30873113

30883114
if (defined(options.apiInstances)) {
3089-
model.apiInstances = options.apiInstances;
3115+
model._apiInstances = options.apiInstances;
30903116
}
30913117

30923118
const resourceCredits = model._resource.credits;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import SceneMode from "../SceneMode.js";
2+
3+
/**
4+
* The model matrix update stage is responsible for updating the instancingTransformsBuffer of a runtime node.
5+
*
6+
* @namespace ModelInstancesUpdateStage
7+
*
8+
* @private
9+
*/
10+
const ModelInstancesUpdateStage = {};
11+
ModelInstancesUpdateStage.name = "ModelInstancesUpdateStage"; // Helps with debugging
12+
13+
/**
14+
* Processes a runtime node. Updates the instancingTransformsBuffer.
15+
*
16+
* @param {ModelRuntimeNode} runtimeNode
17+
* @param {ModelSceneGraph} sceneGraph
18+
* @param {FrameState} frameState
19+
*
20+
* @private
21+
*/
22+
ModelInstancesUpdateStage.update = function (
23+
runtimeNode,
24+
sceneGraph,
25+
frameState,
26+
) {
27+
if (!runtimeNode._apiInstancesDirty) {
28+
return;
29+
}
30+
31+
const use2D = frameState.mode !== SceneMode.SCENE3D;
32+
if (use2D && sceneGraph._model._projectTo2D) {
33+
return;
34+
}
35+
36+
updateRuntimeNode(runtimeNode, sceneGraph, frameState);
37+
runtimeNode._apiInstancesDirty = false;
38+
};
39+
40+
/**
41+
* Recursively update all child runtime nodes and their runtime primitives.
42+
*
43+
* @private
44+
*/
45+
function updateRuntimeNode(runtimeNode, sceneGraph, frameState) {
46+
let i;
47+
48+
const instances = runtimeNode._sceneGraph._model._apiInstances;
49+
const model = runtimeNode._sceneGraph._model;
50+
const buffer = runtimeNode.instancingTransformsBuffer;
51+
const transformsTypedArray = transformsToTypedArray(instances);
52+
53+
runtimeNode.instancingTransformsBuffer.copyFromArrayView(
54+
transformsTypedArray,
55+
);
56+
57+
model._modelResources.push(buffer);
58+
const keepTypedArray = model._enablePick && !frameState.context.webgl2;
59+
60+
if (keepTypedArray) {
61+
runtimeNode.transformsTypedArray = transformsTypedArray;
62+
}
63+
64+
runtimeNode.instancingTransformsBuffer = buffer;
65+
66+
const childrenLength = runtimeNode.children.length;
67+
68+
for (i = 0; i < childrenLength; i++) {
69+
const childRuntimeNode = sceneGraph._runtimeNodes[runtimeNode.children[i]];
70+
71+
updateRuntimeNode(childRuntimeNode, sceneGraph, frameState);
72+
}
73+
}
74+
75+
function transformsToTypedArray(transforms) {
76+
const elements = 12;
77+
const count = transforms.length;
78+
const transformsTypedArray = new Float32Array(count * elements);
79+
80+
for (let i = 0; i < count; i++) {
81+
const transform = transforms[i];
82+
const offset = elements * i;
83+
84+
transformsTypedArray[offset + 0] = transform[0];
85+
transformsTypedArray[offset + 1] = transform[4];
86+
transformsTypedArray[offset + 2] = transform[8];
87+
transformsTypedArray[offset + 3] = transform[12];
88+
transformsTypedArray[offset + 4] = transform[1];
89+
transformsTypedArray[offset + 5] = transform[5];
90+
transformsTypedArray[offset + 6] = transform[9];
91+
transformsTypedArray[offset + 7] = transform[13];
92+
transformsTypedArray[offset + 8] = transform[2];
93+
transformsTypedArray[offset + 9] = transform[6];
94+
transformsTypedArray[offset + 10] = transform[10];
95+
transformsTypedArray[offset + 11] = transform[14];
96+
}
97+
98+
return transformsTypedArray;
99+
}
100+
101+
export default ModelInstancesUpdateStage;

packages/engine/Source/Scene/Model/ModelRuntimeNode.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import TranslationRotationScale from "../../Core/TranslationRotationScale.js";
88
import Quaternion from "../../Core/Quaternion.js";
99
import InstancingPipelineStage from "./InstancingPipelineStage.js";
1010
import ModelMatrixUpdateStage from "./ModelMatrixUpdateStage.js";
11+
import ModelInstancesUpdateStage from "./ModelInstancesUpdateStage.js";
1112
import NodeStatisticsPipelineStage from "./NodeStatisticsPipelineStage.js";
1213

1314
/**
@@ -55,6 +56,8 @@ function ModelRuntimeNode(options) {
5556
this._computedTransform = new Matrix4(); // Computed in initialize()
5657
this._transformDirty = false;
5758

59+
this._apiInstancesDirty = false;
60+
5861
// Used for animation
5962
this._transformParameters = undefined;
6063
this._morphWeights = [];
@@ -585,8 +588,10 @@ ModelRuntimeNode.prototype.configurePipeline = function () {
585588
pipelineStages.push(InstancingPipelineStage);
586589
}
587590

588-
if (defined(this.sceneGraph._model.apiInstances)) {
591+
if (defined(this.sceneGraph._model._apiInstances)) {
589592
pipelineStages.push(InstancingPipelineStage);
593+
updateStages.push(ModelInstancesUpdateStage);
594+
this.apiInstances = this.sceneGraph._model._apiInstances;
590595
}
591596

592597
pipelineStages.push(NodeStatisticsPipelineStage);

packages/engine/Source/Scene/Model/ModelSceneGraph.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ ModelSceneGraph.prototype.buildDrawCommands = function (frameState) {
507507
nodePipelineStage.process(
508508
nodeRenderResources,
509509
runtimeNode.node,
510-
runtimeNode.sceneGraph._model.apiInstances,
510+
runtimeNode.sceneGraph._model._apiInstances,
511511
frameState,
512512
);
513513
}

0 commit comments

Comments
 (0)