EXT_mesh_gpu_instancing.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import { Vector3, Quaternion, Matrix } from 'babylonjs/Maths/math.vector';
  2. import { InstancedMesh } from 'babylonjs/Meshes/instancedMesh';
  3. import { Mesh } from 'babylonjs/Meshes/mesh';
  4. import { TransformNode } from "babylonjs/Meshes/transformNode";
  5. import { StringTools } from 'babylonjs/Misc/stringTools';
  6. import { Nullable } from "babylonjs/types";
  7. import { GLTFLoader, ArrayItem } from "../glTFLoader";
  8. import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
  9. import { INode } from "../glTFLoaderInterfaces";
  10. import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
  11. const NAME = "EXT_mesh_gpu_instancing";
  12. interface IEXTMeshGpuInstancing {
  13. mesh?: number;
  14. attributes: { [name: string]: number };
  15. }
  16. const T = new Vector3(0, 0, 0);
  17. const R = new Quaternion(0, 0, 0, 1);
  18. const S = new Vector3(1, 1, 1);
  19. const M = new Matrix();
  20. /**
  21. * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1691)
  22. * [Playground Sample](https://playground.babylonjs.com/#QFIGLW#9)
  23. * !!! Experimental Extension Subject to Changes !!!
  24. */
  25. export class EXT_mesh_gpu_instancing implements IGLTFLoaderExtension {
  26. /**
  27. * The name of this extension.
  28. */
  29. public readonly name = NAME;
  30. /**
  31. * Defines whether this extension is enabled.
  32. */
  33. public enabled: boolean;
  34. private _loader: GLTFLoader;
  35. /** @hidden */
  36. constructor(loader: GLTFLoader) {
  37. this._loader = loader;
  38. this.enabled = this._loader.isExtensionUsed(NAME);
  39. }
  40. /** @hidden */
  41. public dispose() {
  42. delete this._loader;
  43. }
  44. /** @hidden */
  45. public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
  46. return GLTFLoader.LoadExtensionAsync<IEXTMeshGpuInstancing, TransformNode>(context, node, this.name, (extensionContext, extension) => {
  47. const promise = this._loader.loadNodeAsync(`#/nodes/${node.index}`, node, assign);
  48. if (!node._primitiveBabylonMeshes) {
  49. return promise;
  50. }
  51. let useThinInstancesForAllMeshes = true;
  52. let canUseThinInstances = false;
  53. // Hide the source meshes.
  54. for (const babylonMesh of node._primitiveBabylonMeshes) {
  55. if (!(babylonMesh as Mesh).thinInstanceSetBuffer) {
  56. babylonMesh.isVisible = false;
  57. useThinInstancesForAllMeshes = false;
  58. } else {
  59. canUseThinInstances = true;
  60. (babylonMesh as Mesh).thinInstanceCount = 1; // make sure mesh.hasThinInstances returns true from now on (else async loading of the thin instance data will lead to problems
  61. // as the mesh won't be considered as having thin instances until thinInstanceSetBuffer is called)
  62. }
  63. }
  64. const promises = new Array<Promise<Nullable<Float32Array>>>();
  65. let instanceCount = 0;
  66. const loadAttribute = (attribute: string) => {
  67. if (extension.attributes[attribute] == undefined) {
  68. promises.push(Promise.resolve(null));
  69. return;
  70. }
  71. const accessor = ArrayItem.Get(`${extensionContext}/attributes/${attribute}`, this._loader.gltf.accessors, extension.attributes[attribute]);
  72. promises.push(this._loader._loadFloatAccessorAsync(`/accessors/${accessor.bufferView}`, accessor));
  73. if (instanceCount === 0) {
  74. instanceCount = accessor.count;
  75. } else if (instanceCount !== accessor.count) {
  76. throw new Error(`${extensionContext}/attributes: Instance buffer accessors do not have the same count.`);
  77. }
  78. };
  79. loadAttribute("TRANSLATION");
  80. loadAttribute("ROTATION");
  81. loadAttribute("SCALE");
  82. if (instanceCount == 0) {
  83. for (const babylonMesh of node._primitiveBabylonMeshes) {
  84. if ((babylonMesh as Mesh).thinInstanceSetBuffer) {
  85. (babylonMesh as Mesh).thinInstanceCount = 0;
  86. }
  87. }
  88. return promise;
  89. }
  90. if (!useThinInstancesForAllMeshes) {
  91. const digitLength = instanceCount.toString().length;
  92. for (let i = 0; i < instanceCount; ++i) {
  93. for (const babylonMesh of node._primitiveBabylonMeshes!) {
  94. if (!(babylonMesh as Mesh).thinInstanceSetBuffer) {
  95. const instanceName = `${babylonMesh.name || babylonMesh.id}_${StringTools.PadNumber(i, digitLength)}`;
  96. const babylonInstancedMesh = (babylonMesh as (InstancedMesh | Mesh)).createInstance(instanceName);
  97. babylonInstancedMesh.setParent(babylonMesh);
  98. }
  99. }
  100. }
  101. }
  102. return promise.then((babylonTransformNode) => {
  103. return Promise.all(promises).then(([translationBuffer, rotationBuffer, scaleBuffer]) => {
  104. const matrices = canUseThinInstances ? new Float32Array(instanceCount * 16) : null;
  105. if (matrices) {
  106. T.copyFromFloats(0, 0, 0);
  107. R.copyFromFloats(0, 0, 0, 1);
  108. S.copyFromFloats(1, 1, 1);
  109. for (let i = 0; i < instanceCount; ++i) {
  110. translationBuffer && Vector3.FromArrayToRef(translationBuffer, i * 3, T);
  111. rotationBuffer && Quaternion.FromArrayToRef(rotationBuffer, i * 4, R);
  112. scaleBuffer && Vector3.FromArrayToRef(scaleBuffer, i * 3, S);
  113. Matrix.ComposeToRef(S, R, T, M);
  114. M.copyToArray(matrices, i * 16);
  115. }
  116. }
  117. for (const babylonMesh of node._primitiveBabylonMeshes!) {
  118. if (!(babylonMesh as Mesh).thinInstanceSetBuffer) {
  119. const babylonInstancedMeshes = babylonMesh.getChildMeshes(true, (node) => (node as AbstractMesh).isAnInstance);
  120. for (let i = 0; i < instanceCount; ++i) {
  121. const babylonInstancedMesh = babylonInstancedMeshes[i];
  122. translationBuffer && Vector3.FromArrayToRef(translationBuffer, i * 3, babylonInstancedMesh.position);
  123. rotationBuffer && Quaternion.FromArrayToRef(rotationBuffer, i * 4, babylonInstancedMesh.rotationQuaternion!);
  124. scaleBuffer && Vector3.FromArrayToRef(scaleBuffer, i * 3, babylonInstancedMesh.scaling);
  125. babylonInstancedMesh.refreshBoundingInfo();
  126. }
  127. } else {
  128. (babylonMesh as Mesh).refreshBoundingInfo();
  129. (babylonMesh as Mesh).thinInstanceSetBuffer("matrix", matrices, 16, true);
  130. }
  131. }
  132. return babylonTransformNode;
  133. });
  134. });
  135. });
  136. }
  137. }
  138. GLTFLoader.RegisterExtension(NAME, (loader) => new EXT_mesh_gpu_instancing(loader));