From c28b7357f19e80455f1e20c16e81c9a988b1bf58 Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 16 Jun 2025 17:47:49 -0400 Subject: [PATCH 01/22] initial skeleton webgpu support --- package-lock.json | 1 + package.json | 4 +- src/core/InstancedMesh2.ts | 181 +++++++++++++++++++++---------------- tsconfig.json | 3 +- 4 files changed, 110 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index bfb8fa7..0748927 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@stylistic/eslint-plugin": "^4.4.0", "@three.ez/main": "^0.5.10", "@types/three": "^0.177.0", + "@webgpu/types": "^0.1.40", "eslint": "^9.28.0", "meshoptimizer": "^0.23.0", "simplex-noise": "^4.0.3", diff --git a/package.json b/package.json index ce541fa..88bcffa 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "shadow-LOD", "uniform-per-instance", "instancedMesh2", - "skinning" + "skinning", + "webgpu" ], "scripts": { "start": "vite", @@ -55,6 +56,7 @@ "@stylistic/eslint-plugin": "^4.4.0", "@three.ez/main": "^0.5.10", "@types/three": "^0.177.0", + "@webgpu/types": "^0.1.40", "eslint": "^9.28.0", "meshoptimizer": "^0.23.0", "simplex-noise": "^4.0.3", diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index cc080c0..b3357b1 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -1,4 +1,4 @@ -import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; +import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer, WebGPURenderer, WebGPUProgramParameters } from 'three'; import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; @@ -36,11 +36,11 @@ export interface InstancedMesh2Params { */ allowsEuler?: boolean; /** - * WebGL renderer instance. + * Renderer instance (WebGL or WebGPU). * If not provided, buffers will be initialized during the first render, resulting in no instances being rendered initially. * @default null */ - renderer?: WebGLRenderer; + renderer?: WebGLRenderer | WebGPURenderer; } /** @@ -160,7 +160,7 @@ export class InstancedMesh2< * Callback function called if an instance is inside the frustum. */ public onFrustumEnter: OnFrustumEnterCallback = null; - /** @internal */ _renderer: WebGLRenderer = null; + /** @internal */ _renderer: WebGLRenderer | WebGPURenderer = null; /** @internal */ _instancesCount = 0; /** @internal */ _instancesArrayCount = 0; /** @internal */ _perObjectFrustumCulled = true; @@ -259,7 +259,7 @@ export class InstancedMesh2< this.initMatricesTexture(); } - public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { + public override onBeforeShadow(renderer: WebGLRenderer | WebGPURenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { this.patchMaterial(renderer, depthMaterial); // if multimaterial we compute frustum culling only on first material @@ -269,14 +269,18 @@ export class InstancedMesh2< this.performFrustumCulling(shadowCamera, camera); } - this.matricesTexture.update(renderer); - this.colorsTexture?.update(renderer); - this.uniformsTexture?.update(renderer); - this.boneTexture?.update(renderer); - // TODO convert also morph texture to squared texture to use partial update + if (renderer instanceof WebGLRenderer) { + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + this.uniformsTexture?.update(renderer); + this.boneTexture?.update(renderer); + } else { + // WebGPU texture updates will be added in phase 2 + console.warn('WebGPU texture updates are not yet implemented'); + } } - public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { + public override onBeforeRender(renderer: WebGLRenderer | WebGPURenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { this.patchMaterial(renderer, material); if (!this.instanceIndex) { @@ -291,11 +295,15 @@ export class InstancedMesh2< this.performFrustumCulling(camera); } - this.matricesTexture.update(renderer); - this.colorsTexture?.update(renderer); - this.uniformsTexture?.update(renderer); - this.boneTexture?.update(renderer); - // TODO convert also morph texture to squared texture to use partial update + if (renderer instanceof WebGLRenderer) { + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + this.uniformsTexture?.update(renderer); + this.boneTexture?.update(renderer); + } else { + // WebGPU texture updates will be added in phase 2 + console.warn('WebGPU texture updates are not yet implemented'); + } } public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { @@ -333,16 +341,21 @@ export class InstancedMesh2< return; } - const gl = this._renderer.getContext() as WebGL2RenderingContext; - const capacity = this._capacity; - const array = new Uint32Array(capacity); + if (this._renderer instanceof WebGLRenderer) { + const gl = this._renderer.getContext() as WebGL2RenderingContext; + const capacity = this._capacity; + const array = new Uint32Array(capacity); - for (let i = 0; i < capacity; i++) { - array[i] = i; - } + for (let i = 0; i < capacity; i++) { + array[i] = i; + } - this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); - this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); + this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); + this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); + } else { + // WebGPU implementation will be added in phase 2 + console.warn('WebGPU support is not yet implemented'); + } } protected initMatricesTexture(): void { @@ -391,80 +404,94 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { - if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); + protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms | WebGPUProgramParameters, renderer: WebGLRenderer | WebGPURenderer): void => { + if (renderer instanceof WebGLRenderer) { + if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader as WebGLProgramParametersWithUniforms, renderer); - shader.instancing = false; + shader.instancing = false; - shader.defines ??= {}; - shader.defines['USE_INSTANCING_INDIRECT'] = ''; + shader.defines ??= {}; + shader.defines['USE_INSTANCING_INDIRECT'] = ''; - shader.uniforms.matricesTexture = { value: this.matricesTexture }; + shader.uniforms.matricesTexture = { value: this.matricesTexture }; - if (this.uniformsTexture) { - shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; - const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); - shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); - shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); - } + if (this.uniformsTexture) { + shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; + const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); + shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); + shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); + } - if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { - shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; - shader.uniforms.colorsTexture = { value: this.colorsTexture }; - shader.vertexShader = shader.vertexShader.replace('', ''); + if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { + shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; + shader.uniforms.colorsTexture = { value: this.colorsTexture }; + shader.vertexShader = shader.vertexShader.replace('', ''); - if (shader.vertexColors) { - shader.defines['USE_VERTEX_COLOR'] = ''; - } + if (shader.vertexColors) { + shader.defines['USE_VERTEX_COLOR'] = ''; + } - if (this._useOpacity) { - shader.defines['USE_COLOR_ALPHA'] = ''; - } else { - shader.defines['USE_COLOR'] = ''; + if (this._useOpacity) { + shader.defines['USE_COLOR_ALPHA'] = ''; + } else { + shader.defines['USE_COLOR'] = ''; + } } - } - if (this.boneTexture) { - shader.defines['USE_SKINNING'] = ''; - shader.defines['USE_INSTANCING_SKINNING'] = ''; - shader.uniforms.bindMatrix = { value: this.bindMatrix }; - shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; - shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; - shader.uniforms.boneTexture = { value: this.boneTexture }; + if (this.boneTexture) { + shader.defines['USE_SKINNING'] = ''; + shader.defines['USE_INSTANCING_SKINNING'] = ''; + shader.uniforms.bindMatrix = { value: this.bindMatrix }; + shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; + shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; + shader.uniforms.boneTexture = { value: this.boneTexture }; + } + } else { + // WebGPU shader compilation will be added in phase 2 + console.warn('WebGPU shader compilation is not yet implemented'); } }; - protected patchMaterial(renderer: WebGLRenderer, material: Material): void { + protected patchMaterial(renderer: WebGLRenderer | WebGPURenderer, material: Material): void { this._currentMaterial = material; - this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak - this._onBeforeCompileBase = material.onBeforeCompile; - material.customProgramCacheKey = this._customProgramCacheKey; - material.onBeforeCompile = this._onBeforeCompile; + + if (renderer instanceof WebGLRenderer) { + this._customProgramCacheKeyBase = material.customProgramCacheKey; + this._onBeforeCompileBase = material.onBeforeCompile; + material.customProgramCacheKey = this._customProgramCacheKey; + material.onBeforeCompile = this._onBeforeCompile; - const propertiesBase = renderer.properties; + const propertiesBase = renderer.properties; - if (!this._properties.has(material)) { - const materialProperties = {}; - this._properties.set(material, materialProperties); + if (!this._properties.has(material)) { + const materialProperties = {}; + this._properties.set(material, materialProperties); - const propertiesGetBase = this._propertiesGetBase = propertiesBase.get; + const propertiesGetBase = this._propertiesGetBase = propertiesBase.get; - this._propertiesGetMap.set(material, (object) => { - if (object === material) return materialProperties; - return propertiesGetBase(object); - }); - } + this._propertiesGetMap.set(material, (object) => { + if (object === material) return materialProperties; + return propertiesGetBase(object); + }); + } - propertiesBase.get = this._propertiesGetMap.get(material); + propertiesBase.get = this._propertiesGetMap.get(material); + } else { + // WebGPU material patching will be added in phase 2 + console.warn('WebGPU material patching is not yet implemented'); + } } - protected unpatchMaterial(renderer: WebGLRenderer, material: Material): void { + protected unpatchMaterial(renderer: WebGLRenderer | WebGPURenderer, material: Material): void { this._currentMaterial = null; - renderer.properties.get = this._propertiesGetBase; - material.onBeforeCompile = this._onBeforeCompileBase; - material.customProgramCacheKey = this._customProgramCacheKeyBase; - this._onBeforeCompileBase = null; - this._customProgramCacheKeyBase = null; + + if (renderer instanceof WebGLRenderer) { + renderer.properties.get = this._propertiesGetBase; + material.onBeforeCompile = this._onBeforeCompileBase; + material.customProgramCacheKey = this._customProgramCacheKeyBase; + this._onBeforeCompileBase = null; + this._customProgramCacheKeyBase = null; + } } /** diff --git a/tsconfig.json b/tsconfig.json index c9b43ad..f03e588 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ "noImplicitAny": false, "skipLibCheck": true, "types": [ - "vite-plugin-glsl/ext" + "vite-plugin-glsl/ext", + "@webgpu/types" ] } } \ No newline at end of file From fe650de466b4f42e47cf63c481257f34ec90e7f5 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 17 Jun 2025 11:44:15 -0400 Subject: [PATCH 02/22] Revert "initial skeleton webgpu support" This reverts commit c28b7357f19e80455f1e20c16e81c9a988b1bf58. --- package-lock.json | 1 - package.json | 4 +- src/core/InstancedMesh2.ts | 181 ++++++++++++++++--------------------- tsconfig.json | 3 +- 4 files changed, 79 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0748927..bfb8fa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@stylistic/eslint-plugin": "^4.4.0", "@three.ez/main": "^0.5.10", "@types/three": "^0.177.0", - "@webgpu/types": "^0.1.40", "eslint": "^9.28.0", "meshoptimizer": "^0.23.0", "simplex-noise": "^4.0.3", diff --git a/package.json b/package.json index 88bcffa..ce541fa 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,7 @@ "shadow-LOD", "uniform-per-instance", "instancedMesh2", - "skinning", - "webgpu" + "skinning" ], "scripts": { "start": "vite", @@ -56,7 +55,6 @@ "@stylistic/eslint-plugin": "^4.4.0", "@three.ez/main": "^0.5.10", "@types/three": "^0.177.0", - "@webgpu/types": "^0.1.40", "eslint": "^9.28.0", "meshoptimizer": "^0.23.0", "simplex-noise": "^4.0.3", diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index b3357b1..cc080c0 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -1,4 +1,4 @@ -import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer, WebGPURenderer, WebGPUProgramParameters } from 'three'; +import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; @@ -36,11 +36,11 @@ export interface InstancedMesh2Params { */ allowsEuler?: boolean; /** - * Renderer instance (WebGL or WebGPU). + * WebGL renderer instance. * If not provided, buffers will be initialized during the first render, resulting in no instances being rendered initially. * @default null */ - renderer?: WebGLRenderer | WebGPURenderer; + renderer?: WebGLRenderer; } /** @@ -160,7 +160,7 @@ export class InstancedMesh2< * Callback function called if an instance is inside the frustum. */ public onFrustumEnter: OnFrustumEnterCallback = null; - /** @internal */ _renderer: WebGLRenderer | WebGPURenderer = null; + /** @internal */ _renderer: WebGLRenderer = null; /** @internal */ _instancesCount = 0; /** @internal */ _instancesArrayCount = 0; /** @internal */ _perObjectFrustumCulled = true; @@ -259,7 +259,7 @@ export class InstancedMesh2< this.initMatricesTexture(); } - public override onBeforeShadow(renderer: WebGLRenderer | WebGPURenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { + public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { this.patchMaterial(renderer, depthMaterial); // if multimaterial we compute frustum culling only on first material @@ -269,18 +269,14 @@ export class InstancedMesh2< this.performFrustumCulling(shadowCamera, camera); } - if (renderer instanceof WebGLRenderer) { - this.matricesTexture.update(renderer); - this.colorsTexture?.update(renderer); - this.uniformsTexture?.update(renderer); - this.boneTexture?.update(renderer); - } else { - // WebGPU texture updates will be added in phase 2 - console.warn('WebGPU texture updates are not yet implemented'); - } + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + this.uniformsTexture?.update(renderer); + this.boneTexture?.update(renderer); + // TODO convert also morph texture to squared texture to use partial update } - public override onBeforeRender(renderer: WebGLRenderer | WebGPURenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { + public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { this.patchMaterial(renderer, material); if (!this.instanceIndex) { @@ -295,15 +291,11 @@ export class InstancedMesh2< this.performFrustumCulling(camera); } - if (renderer instanceof WebGLRenderer) { - this.matricesTexture.update(renderer); - this.colorsTexture?.update(renderer); - this.uniformsTexture?.update(renderer); - this.boneTexture?.update(renderer); - } else { - // WebGPU texture updates will be added in phase 2 - console.warn('WebGPU texture updates are not yet implemented'); - } + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + this.uniformsTexture?.update(renderer); + this.boneTexture?.update(renderer); + // TODO convert also morph texture to squared texture to use partial update } public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { @@ -341,21 +333,16 @@ export class InstancedMesh2< return; } - if (this._renderer instanceof WebGLRenderer) { - const gl = this._renderer.getContext() as WebGL2RenderingContext; - const capacity = this._capacity; - const array = new Uint32Array(capacity); - - for (let i = 0; i < capacity; i++) { - array[i] = i; - } + const gl = this._renderer.getContext() as WebGL2RenderingContext; + const capacity = this._capacity; + const array = new Uint32Array(capacity); - this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); - this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); - } else { - // WebGPU implementation will be added in phase 2 - console.warn('WebGPU support is not yet implemented'); + for (let i = 0; i < capacity; i++) { + array[i] = i; } + + this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); + this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); } protected initMatricesTexture(): void { @@ -404,94 +391,80 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms | WebGPUProgramParameters, renderer: WebGLRenderer | WebGPURenderer): void => { - if (renderer instanceof WebGLRenderer) { - if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader as WebGLProgramParametersWithUniforms, renderer); + protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { + if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); - shader.instancing = false; + shader.instancing = false; - shader.defines ??= {}; - shader.defines['USE_INSTANCING_INDIRECT'] = ''; + shader.defines ??= {}; + shader.defines['USE_INSTANCING_INDIRECT'] = ''; - shader.uniforms.matricesTexture = { value: this.matricesTexture }; - - if (this.uniformsTexture) { - shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; - const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); - shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); - shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); - } + shader.uniforms.matricesTexture = { value: this.matricesTexture }; - if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { - shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; - shader.uniforms.colorsTexture = { value: this.colorsTexture }; - shader.vertexShader = shader.vertexShader.replace('', ''); + if (this.uniformsTexture) { + shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; + const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); + shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); + shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); + } - if (shader.vertexColors) { - shader.defines['USE_VERTEX_COLOR'] = ''; - } + if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { + shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; + shader.uniforms.colorsTexture = { value: this.colorsTexture }; + shader.vertexShader = shader.vertexShader.replace('', ''); - if (this._useOpacity) { - shader.defines['USE_COLOR_ALPHA'] = ''; - } else { - shader.defines['USE_COLOR'] = ''; - } + if (shader.vertexColors) { + shader.defines['USE_VERTEX_COLOR'] = ''; } - if (this.boneTexture) { - shader.defines['USE_SKINNING'] = ''; - shader.defines['USE_INSTANCING_SKINNING'] = ''; - shader.uniforms.bindMatrix = { value: this.bindMatrix }; - shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; - shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; - shader.uniforms.boneTexture = { value: this.boneTexture }; + if (this._useOpacity) { + shader.defines['USE_COLOR_ALPHA'] = ''; + } else { + shader.defines['USE_COLOR'] = ''; } - } else { - // WebGPU shader compilation will be added in phase 2 - console.warn('WebGPU shader compilation is not yet implemented'); + } + + if (this.boneTexture) { + shader.defines['USE_SKINNING'] = ''; + shader.defines['USE_INSTANCING_SKINNING'] = ''; + shader.uniforms.bindMatrix = { value: this.bindMatrix }; + shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; + shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; + shader.uniforms.boneTexture = { value: this.boneTexture }; } }; - protected patchMaterial(renderer: WebGLRenderer | WebGPURenderer, material: Material): void { + protected patchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = material; - - if (renderer instanceof WebGLRenderer) { - this._customProgramCacheKeyBase = material.customProgramCacheKey; - this._onBeforeCompileBase = material.onBeforeCompile; - material.customProgramCacheKey = this._customProgramCacheKey; - material.onBeforeCompile = this._onBeforeCompile; + this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak + this._onBeforeCompileBase = material.onBeforeCompile; + material.customProgramCacheKey = this._customProgramCacheKey; + material.onBeforeCompile = this._onBeforeCompile; - const propertiesBase = renderer.properties; + const propertiesBase = renderer.properties; - if (!this._properties.has(material)) { - const materialProperties = {}; - this._properties.set(material, materialProperties); + if (!this._properties.has(material)) { + const materialProperties = {}; + this._properties.set(material, materialProperties); - const propertiesGetBase = this._propertiesGetBase = propertiesBase.get; + const propertiesGetBase = this._propertiesGetBase = propertiesBase.get; - this._propertiesGetMap.set(material, (object) => { - if (object === material) return materialProperties; - return propertiesGetBase(object); - }); - } - - propertiesBase.get = this._propertiesGetMap.get(material); - } else { - // WebGPU material patching will be added in phase 2 - console.warn('WebGPU material patching is not yet implemented'); + this._propertiesGetMap.set(material, (object) => { + if (object === material) return materialProperties; + return propertiesGetBase(object); + }); } + + propertiesBase.get = this._propertiesGetMap.get(material); } - protected unpatchMaterial(renderer: WebGLRenderer | WebGPURenderer, material: Material): void { + protected unpatchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = null; - - if (renderer instanceof WebGLRenderer) { - renderer.properties.get = this._propertiesGetBase; - material.onBeforeCompile = this._onBeforeCompileBase; - material.customProgramCacheKey = this._customProgramCacheKeyBase; - this._onBeforeCompileBase = null; - this._customProgramCacheKeyBase = null; - } + renderer.properties.get = this._propertiesGetBase; + material.onBeforeCompile = this._onBeforeCompileBase; + material.customProgramCacheKey = this._customProgramCacheKeyBase; + this._onBeforeCompileBase = null; + this._customProgramCacheKeyBase = null; } /** diff --git a/tsconfig.json b/tsconfig.json index f03e588..c9b43ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,7 @@ "noImplicitAny": false, "skipLibCheck": true, "types": [ - "vite-plugin-glsl/ext", - "@webgpu/types" + "vite-plugin-glsl/ext" ] } } \ No newline at end of file From 838d3af37dd3aa9a5b67bd1bae18c0a0cefbf2d2 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 17 Jun 2025 12:54:35 -0400 Subject: [PATCH 03/22] WIP TSL functions for features --- package-lock.json | 2 +- package.json | 2 +- src/core/InstancedMesh2.ts | 67 ++++++++------------------------------ src/shaders/tsl/nodes.ts | 39 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 src/shaders/tsl/nodes.ts diff --git a/package-lock.json b/package-lock.json index bfb8fa7..2eb8839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "vite-plugin-static-copy": "^3.0.0" }, "peerDependencies": { - "three": ">=0.159.0" + "three": ">=0.177.0" } }, "node_modules/@dimforge/rapier3d-compat": { diff --git a/package.json b/package.json index ce541fa..f980c2f 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "vite-plugin-static-copy": "^3.0.0" }, "peerDependencies": { - "three": ">=0.159.0" + "three": ">=0.177.0" }, "dependencies": { "bvh.js": "^0.0.13" diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index cc080c0..c66a0bc 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -1,4 +1,5 @@ import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; +import { AttributeNode, UniformNode, WebGPURenderer, StorageInstancedBufferAttribute, MeshBasicNodeMaterial, TSL } from 'three/webgpu'; import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; @@ -6,6 +7,8 @@ import { InstancedEntity } from './InstancedEntity.js'; import { BVHParams, InstancedMeshBVH } from './InstancedMeshBVH.js'; import { GLInstancedBufferAttribute } from './utils/GLInstancedBufferAttribute.js'; import { SquareDataTexture } from './utils/SquareDataTexture.js'; +import { instanceIndex } from 'three/tsl'; +import { getColorTexture, getInstancedMatrix, instancedColorParsVertex } from '../shaders/tsl/nodes.js'; // TODO: Add check to not update partial texture if needsuupdate already true // TODO: if bvh present, can override? @@ -76,7 +79,7 @@ export class InstancedMesh2< /** * Attribute storing indices of the instances to be rendered. */ - public instanceIndex: GLInstancedBufferAttribute = null; + public instanceIndex: typeof TSL.instanceIndex = null; /** * Texture storing matrices for instances. */ @@ -172,7 +175,7 @@ export class InstancedMesh2< protected readonly _allowsEuler: boolean; protected readonly _tempInstance: InstancedEntity; protected _useOpacity = false; - protected _currentMaterial: Material = null; + protected _currentMaterial: MeshBasicNodeMaterial = null; protected _customProgramCacheKeyBase: () => string = null; protected _onBeforeCompileBase: (parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void = null; protected _propertiesGetBase: (obj: unknown) => unknown = null; @@ -259,7 +262,7 @@ export class InstancedMesh2< this.initMatricesTexture(); } - public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { + public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: MeshBasicNodeMaterial, group: any): void { this.patchMaterial(renderer, depthMaterial); // if multimaterial we compute frustum culling only on first material @@ -276,7 +279,7 @@ export class InstancedMesh2< // TODO convert also morph texture to squared texture to use partial update } - public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { + public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: MeshBasicNodeMaterial, group: any): void { this.patchMaterial(renderer, material); if (!this.instanceIndex) { @@ -298,11 +301,11 @@ export class InstancedMesh2< // TODO convert also morph texture to squared texture to use partial update } - public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { + public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: MeshBasicNodeMaterial, group: any): void { this.unpatchMaterial(renderer, depthMaterial); } - public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { + public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: MeshBasicNodeMaterial, group: any): void { this.unpatchMaterial(renderer, material); if (this.instanceIndex || (group && !this.isLastGroup(group.materialIndex))) return; this.initIndexAttribute(); @@ -340,9 +343,6 @@ export class InstancedMesh2< for (let i = 0; i < capacity; i++) { array[i] = i; } - - this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); - this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); } protected initMatricesTexture(): void { @@ -391,55 +391,16 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { - if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); - - shader.instancing = false; - - shader.defines ??= {}; - shader.defines['USE_INSTANCING_INDIRECT'] = ''; - - shader.uniforms.matricesTexture = { value: this.matricesTexture }; - - if (this.uniformsTexture) { - shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; - const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); - shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); - shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); - } - - if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { - shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; - shader.uniforms.colorsTexture = { value: this.colorsTexture }; - shader.vertexShader = shader.vertexShader.replace('', ''); - - if (shader.vertexColors) { - shader.defines['USE_VERTEX_COLOR'] = ''; - } - - if (this._useOpacity) { - shader.defines['USE_COLOR_ALPHA'] = ''; - } else { - shader.defines['USE_COLOR'] = ''; - } - } - - if (this.boneTexture) { - shader.defines['USE_SKINNING'] = ''; - shader.defines['USE_INSTANCING_SKINNING'] = ''; - shader.uniforms.bindMatrix = { value: this.bindMatrix }; - shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; - shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; - shader.uniforms.boneTexture = { value: this.boneTexture }; - } + protected _setupNodes = (renderer: WebGPURenderer): void => { + // this._currentMaterial.fragmentNode = getInstancedMatrix(this.matricesTexture); + this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); }; - protected patchMaterial(renderer: WebGLRenderer, material: Material): void { + protected patchMaterial(renderer: WebGLRenderer, material: MeshBasicNodeMaterial): void { this._currentMaterial = material; this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak this._onBeforeCompileBase = material.onBeforeCompile; material.customProgramCacheKey = this._customProgramCacheKey; - material.onBeforeCompile = this._onBeforeCompile; const propertiesBase = renderer.properties; @@ -458,7 +419,7 @@ export class InstancedMesh2< propertiesBase.get = this._propertiesGetMap.get(material); } - protected unpatchMaterial(renderer: WebGLRenderer, material: Material): void { + protected unpatchMaterial(renderer: WebGLRenderer, material: MeshBasicNodeMaterial): void { this._currentMaterial = null; renderer.properties.get = this._propertiesGetBase; material.onBeforeCompile = this._onBeforeCompileBase; diff --git a/src/shaders/tsl/nodes.ts b/src/shaders/tsl/nodes.ts new file mode 100644 index 0000000..abb2ac4 --- /dev/null +++ b/src/shaders/tsl/nodes.ts @@ -0,0 +1,39 @@ +import { Fn, instanceIndex, int, ivec2, mat4, textureSize, tslFn, uniform, vec4 } from 'three/tsl'; + + +export const getColorTexture = (colorsTexture) => { + const size = int(textureSize(colorsTexture, int(0)).x).toVar(); + const j = int(instanceIndex).toVar(); + const x = int(j.remainder(size)).toVar(); + const y = int( j.div( size ) ).toVar(); + return colorsTexture.sample( ivec2( x, y ) ).setSampler( false ); +}; + + +export const getInstancedMatrix = (matricesTexture) => { + const size = int( textureSize( matricesTexture, int( 0 ) ).x ).toVar(); + const j = int( int( instanceIndex ).mul( int( 4 ) ) ).toVar(); + const x = int( j.remainder( size ) ).toVar(); + const y = int( j.div( size ) ).toVar(); + const v1 = vec4( matricesTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); + const v2 = vec4( matricesTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); + const v3 = vec4( matricesTexture.sample( ivec2( x.add( int( 2 ) ), y ) ).setSampler( false ) ).toVar(); + const v4 = vec4( matricesTexture.sample( ivec2( x.add( int( 3 ) ), y ) ).setSampler( false ) ).toVar(); + return mat4( v1, v2, v3, v4 ); +}; + + +export const getBoneMatrix = (boneTexture) => Fn(( [i] ) => { + const bonesPerInstance = uniform( 'int' ); + + const size = int( textureSize( boneTexture, int( 0 ) ).x ).toVar(); + const j = int( bonesPerInstance.mul( int( instanceIndex ) ).add( int( i ) ).mul( int( 4 ) ) ).toVar(); + const x = int( j.remainder( size ) ).toVar(); + const y = int( j.div( size ) ).toVar(); + const v1 = vec4( boneTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); + const v2 = vec4( boneTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); + const v3 = vec4( boneTexture.sample( ivec2( x.add( int( 2 ) ), y ) ).setSampler( false ) ).toVar(); + const v4 = vec4( boneTexture.sample( ivec2( x.add( int( 3 ) ), y ) ).setSampler( false ) ).toVar(); + + return mat4( v1, v2, v3, v4 ); +}); From b395828b9d29325e37e0a913d5fa16aa2b57923b Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 18 Jun 2025 14:11:57 -0400 Subject: [PATCH 04/22] separate module for InstancedMeshGPU --- InstancedMeshGPU.js | 27 ++ src/core/InstancedMesh2.ts | 67 +++- src/core/InstancedMeshGPU.ts | 619 +++++++++++++++++++++++++++++ src/core/feature/FrustumCulling.ts | 21 + 4 files changed, 720 insertions(+), 14 deletions(-) create mode 100644 InstancedMeshGPU.js create mode 100644 src/core/InstancedMeshGPU.ts diff --git a/InstancedMeshGPU.js b/InstancedMeshGPU.js new file mode 100644 index 0000000..e17b4cf --- /dev/null +++ b/InstancedMeshGPU.js @@ -0,0 +1,27 @@ +import * as THREE from 'three'; + +class InstancedMeshGPU extends THREE.InstancedMesh { + constructor(geometry, material, count) { + super(geometry, material, count); + // ...existing initialization code... + } + + onBeforeRender(renderer, scene, camera, geometry, material, group) { + if (renderer.isWebGPURenderer) { + // Custom logic for WebGPURenderer + // e.g., update instance buffer, set custom uniforms, etc. + // Example: + // this.instanceMatrix.needsUpdate = true; + // renderer.updateInstanceBuffer(this); + + // ...your WebGPU-specific code here... + } else { + // Optionally call the base implementation for other renderers + super.onBeforeRender(renderer, scene, camera, geometry, material, group); + } + } + + // ...existing methods and properties... +} + +export { InstancedMeshGPU }; \ No newline at end of file diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index c66a0bc..cc080c0 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -1,5 +1,4 @@ import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; -import { AttributeNode, UniformNode, WebGPURenderer, StorageInstancedBufferAttribute, MeshBasicNodeMaterial, TSL } from 'three/webgpu'; import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; @@ -7,8 +6,6 @@ import { InstancedEntity } from './InstancedEntity.js'; import { BVHParams, InstancedMeshBVH } from './InstancedMeshBVH.js'; import { GLInstancedBufferAttribute } from './utils/GLInstancedBufferAttribute.js'; import { SquareDataTexture } from './utils/SquareDataTexture.js'; -import { instanceIndex } from 'three/tsl'; -import { getColorTexture, getInstancedMatrix, instancedColorParsVertex } from '../shaders/tsl/nodes.js'; // TODO: Add check to not update partial texture if needsuupdate already true // TODO: if bvh present, can override? @@ -79,7 +76,7 @@ export class InstancedMesh2< /** * Attribute storing indices of the instances to be rendered. */ - public instanceIndex: typeof TSL.instanceIndex = null; + public instanceIndex: GLInstancedBufferAttribute = null; /** * Texture storing matrices for instances. */ @@ -175,7 +172,7 @@ export class InstancedMesh2< protected readonly _allowsEuler: boolean; protected readonly _tempInstance: InstancedEntity; protected _useOpacity = false; - protected _currentMaterial: MeshBasicNodeMaterial = null; + protected _currentMaterial: Material = null; protected _customProgramCacheKeyBase: () => string = null; protected _onBeforeCompileBase: (parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void = null; protected _propertiesGetBase: (obj: unknown) => unknown = null; @@ -262,7 +259,7 @@ export class InstancedMesh2< this.initMatricesTexture(); } - public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: MeshBasicNodeMaterial, group: any): void { + public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { this.patchMaterial(renderer, depthMaterial); // if multimaterial we compute frustum culling only on first material @@ -279,7 +276,7 @@ export class InstancedMesh2< // TODO convert also morph texture to squared texture to use partial update } - public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: MeshBasicNodeMaterial, group: any): void { + public override onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { this.patchMaterial(renderer, material); if (!this.instanceIndex) { @@ -301,11 +298,11 @@ export class InstancedMesh2< // TODO convert also morph texture to squared texture to use partial update } - public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: MeshBasicNodeMaterial, group: any): void { + public override onAfterShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { this.unpatchMaterial(renderer, depthMaterial); } - public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: MeshBasicNodeMaterial, group: any): void { + public override onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: any): void { this.unpatchMaterial(renderer, material); if (this.instanceIndex || (group && !this.isLastGroup(group.materialIndex))) return; this.initIndexAttribute(); @@ -343,6 +340,9 @@ export class InstancedMesh2< for (let i = 0; i < capacity; i++) { array[i] = i; } + + this.instanceIndex = new GLInstancedBufferAttribute(gl, gl.UNSIGNED_INT, 1, 4, array); + this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); } protected initMatricesTexture(): void { @@ -391,16 +391,55 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _setupNodes = (renderer: WebGPURenderer): void => { - // this._currentMaterial.fragmentNode = getInstancedMatrix(this.matricesTexture); - this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); + protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { + if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); + + shader.instancing = false; + + shader.defines ??= {}; + shader.defines['USE_INSTANCING_INDIRECT'] = ''; + + shader.uniforms.matricesTexture = { value: this.matricesTexture }; + + if (this.uniformsTexture) { + shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; + const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); + shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); + shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); + } + + if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { + shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; + shader.uniforms.colorsTexture = { value: this.colorsTexture }; + shader.vertexShader = shader.vertexShader.replace('', ''); + + if (shader.vertexColors) { + shader.defines['USE_VERTEX_COLOR'] = ''; + } + + if (this._useOpacity) { + shader.defines['USE_COLOR_ALPHA'] = ''; + } else { + shader.defines['USE_COLOR'] = ''; + } + } + + if (this.boneTexture) { + shader.defines['USE_SKINNING'] = ''; + shader.defines['USE_INSTANCING_SKINNING'] = ''; + shader.uniforms.bindMatrix = { value: this.bindMatrix }; + shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; + shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; + shader.uniforms.boneTexture = { value: this.boneTexture }; + } }; - protected patchMaterial(renderer: WebGLRenderer, material: MeshBasicNodeMaterial): void { + protected patchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = material; this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak this._onBeforeCompileBase = material.onBeforeCompile; material.customProgramCacheKey = this._customProgramCacheKey; + material.onBeforeCompile = this._onBeforeCompile; const propertiesBase = renderer.properties; @@ -419,7 +458,7 @@ export class InstancedMesh2< propertiesBase.get = this._propertiesGetMap.get(material); } - protected unpatchMaterial(renderer: WebGLRenderer, material: MeshBasicNodeMaterial): void { + protected unpatchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = null; renderer.properties.get = this._propertiesGetBase; material.onBeforeCompile = this._onBeforeCompileBase; diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts new file mode 100644 index 0000000..fde2f88 --- /dev/null +++ b/src/core/InstancedMeshGPU.ts @@ -0,0 +1,619 @@ +import { AttachedBindMode, BindMode, Box3, BufferGeometry, Color, ColorManagement, ColorRepresentation, DataTexture, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Skeleton, Sphere, Vector3 } from 'three'; +import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; +import { Entity } from './feature/Instances.js'; +import { LODInfo } from './feature/LOD.js'; +import { InstancedEntity } from './InstancedEntity.js'; +import { InstancedMeshBVH } from './InstancedMeshBVH.js'; +import { SquareDataTexture } from './utils/SquareDataTexture.js'; +import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; +import { getColorTexture } from '../shaders/tsl/nodes.js'; + + +/** + * Parameters for configuring an `InstancedMeshGPU` instance. + */ +export interface InstancedMeshGPUParams { + /** + * Determines the maximum number of instances that buffers can hold. + * The buffers will be expanded automatically if necessary. + * @default 1000 + */ + capacity?: number; + /** + * Determines whether to create an array of `InstancedEntity` to easily manipulate instances at the cost of more memory. + * @default false + */ + createEntities?: boolean; + /** + * Determines whether `InstancedEntity` can use the `rotation` property. + * If `true` `quaternion` and `rotation` will be synchronized, affecting performance. + * @default false + */ + allowsEuler?: boolean; + /** + * WebGL renderer instance. + * If not provided, buffers will be initialized during the first render, resulting in no instances being rendered initially. + * @default null + */ + renderer?: WebGPURenderer; +} + +/** + * Alternative `InstancedMesh` class to support additional features like frustum culling, fast raycasting, LOD and more. + * @template TData Type for additional instance data. + * @template TGeometry Type extending `BufferGeometry`. + * @template TMaterial Type extending `Material` or an array of `Material`. + * @template TEventMap Type extending `Object3DEventMap`. + */ +export class InstancedMeshGPU< + TData = {}, + TGeometry extends BufferGeometry = BufferGeometry, + TMaterial extends Material | Material[] = Material | Material[], + TEventMap extends Object3DEventMap = Object3DEventMap +> extends Mesh { + /** + * The number of instances rendered in the last frame. + */ + public declare count: number; + /** + * @defaultValue `InstancedMesh2` + */ + public override readonly type = 'InstancedMeshGPU'; + /** + * Indicates if this is an `InstancedMeshGPU`. + */ + public readonly isInstancedMeshGPU = true; + /** + * An array of `Entity` representing individual instances. + * This array is only initialized if `createEntities` is set to `true` in the constructor parameters. + */ + public instances: Entity[] = null; + /** + * Attribute storing indices of the instances to be rendered. + */ + public instanceIndex: StorageInstancedBufferAttribute = null; + /** + * Texture storing matrices for instances. + */ + public matricesTexture: SquareDataTexture; + /** + * Texture storing colors for instances. + */ + public colorsTexture: SquareDataTexture = null; + /** + * Texture storing morph target influences for instances. + */ + public morphTexture: DataTexture = null; + /** + * Texture storing bones for instances. + */ + public boneTexture: SquareDataTexture = null; + /** + * Texture storing custom uniforms per instance. + */ + public uniformsTexture: SquareDataTexture = null; + /** + * This bounding box encloses all instances, which can be calculated with `computeBoundingBox` method. + * Bounding box isn't computed by default. It needs to be explicitly computed, otherwise it's `null`. + */ + public boundingBox: Box3 = null; + /** + * This bounding sphere encloses all instances, which can be calculated with `computeBoundingSphere` method. + * Bounding sphere is computed during its first render. You may need to recompute it if an instance is transformed. + */ + public boundingSphere: Sphere = null; + /** + * BVH structure for optimized culling and intersection testing. + * It's possible to create the BVH using the `computeBVH` method. Once created it will be updated automatically. + */ + public bvh: InstancedMeshBVH = null; + /** + * Custom sort function for instances. + * It's possible to create the radix sort using the `createRadixSort` method. + * @default null + */ + public customSort: CustomSortCallback = null; + /** + * Flag indicating if raycasting should only consider the last frame frustum culled instances. + * This is ignored if the bvh has been created. + * @default false + */ + public raycastOnlyFrustum = false; + /** + * Array storing visibility and availability for instances. + * [visible0, active0, visible1, active1, ...] + */ + public readonly availabilityArray: boolean[]; + /** + * Contains data for managing LOD, allowing different levels of detail for rendering and shadow casting. + */ + public LODinfo: LODInfo = null; + /** + * Flag indicating whether to automatically perform frustum culling before rendering. + * @default true + */ + public autoUpdate = true; + /** + * Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. + * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton across multiple skinned meshes. + * @default `AttachedBindMode` + */ + public bindMode: BindMode = AttachedBindMode; + /** + * The base matrix that is used for the bound bone transforms. + */ + public bindMatrix: Matrix4 = null; + /** + * The base matrix that is used for resetting the bound bone transforms. + */ + public bindMatrixInverse: Matrix4 = null; + /** + * Skeleton representing the bone hierarchy of the skinned mesh. + */ + public skeleton: Skeleton = null; + /** + * Callback function called if an instance is inside the frustum. + */ + public onFrustumEnter: OnFrustumEnterCallback = null; + /** @internal */ _renderer: WebGPURenderer = null; + /** @internal */ _instancesCount = 0; + /** @internal */ _instancesArrayCount = 0; + /** @internal */ _perObjectFrustumCulled = true; + /** @internal */ _sortObjects = false; + /** @internal */ _capacity: number; + /** @internal */ _indexArrayNeedsUpdate = false; + /** @internal */ _geometry: TGeometry; + /** @internal */ _parentLOD: InstancedMeshGPU; + protected readonly _allowsEuler: boolean; + protected readonly _tempInstance: InstancedEntity; + protected _useOpacity = false; + protected _currentMaterial: MeshBasicNodeMaterial = null; + protected _customProgramCacheKeyBase: () => string = null; + // protected _onBeforeCompileBase: (parameters: WebGPUProgramParametersWithUniforms, renderer: WebGLRenderer) => void = null; + protected _propertiesGetBase: (obj: unknown) => unknown = null; + protected _propertiesGetMap = new WeakMap unknown>(); + protected _properties = new WeakMap(); + protected _freeIds: number[] = []; + protected _createEntities: boolean; + + // HACK TO MAKE IT WORK WITHOUT UPDATE CORE + /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering + /** @internal */ instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception + /** @internal */ instanceColor = null; // must be null to avoid exception + private _adapter: GPUAdapter; + private _device: GPUDevice; + private _context: any; + + /** + * The capacity of the instance buffers. + */ + public get capacity(): number { return this._capacity; } + + /** + * The number of active instances. + */ + public get instancesCount(): number { return this._instancesCount; } + + /** + * Determines if per-instance frustum culling is enabled. + * @default true + */ + public get perObjectFrustumCulled(): boolean { return this._perObjectFrustumCulled; } + public set perObjectFrustumCulled(value: boolean) { + this._perObjectFrustumCulled = value; + this._indexArrayNeedsUpdate = true; + } + + /** + * Determines if objects should be sorted before rendering. + * @default false + */ + public get sortObjects(): boolean { return this._sortObjects; } + public set sortObjects(value: boolean) { + this._sortObjects = value; + this._indexArrayNeedsUpdate = true; + } + + /** + * An instance of `BufferGeometry` (or derived classes), defining the object's structure. + */ + // @ts-expect-error It's defined as a property, but is overridden as an accessor. + public override get geometry(): TGeometry { return this._geometry; } + public override set geometry(value: TGeometry) { + this._geometry = value; + // this.patchGeometry(value); + } + + /** @internal */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + constructor(geometry: TGeometry, material: TMaterial, params?: InstancedMeshGPUParams, LOD?: InstancedMeshGPU); + constructor(geometry: TGeometry, material: TMaterial, params?: InstancedMeshGPUParams); + /** + * @remarks Geometry cannot be shared. If reused, it will be cloned. + * @param geometry An instance of `BufferGeometry`. + * @param material A single or an array of `Material`. + * @param params Optional configuration parameters object. See `InstancedMeshGPUParams` for details. + */ + constructor(geometry: TGeometry, material: TMaterial, params: InstancedMeshGPUParams = {}, LOD?: InstancedMeshGPU) { + if (!geometry) throw new Error('"geometry" is mandatory.'); + if (!material) throw new Error('"material" is mandatory.'); + + const { allowsEuler, renderer, createEntities } = params; + + super(geometry, null); + + const capacity = params.capacity > 0 ? params.capacity : _defaultCapacity; + this._renderer = renderer; + this._capacity = capacity; + this._parentLOD = LOD; + this._geometry = geometry; + this.material = material; + this._allowsEuler = allowsEuler ?? false; + // this._tempInstance = new InstancedEntity(this, -1, allowsEuler); + this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2); + this._createEntities = createEntities; + // this.initIndexAttribute(); + this.initMatricesTexture(); + } + + /** + * Initializes the `InstancedMeshGPU` instance. + * This method is called automatically when the instance is created. + */ + public async init(): Promise { + this._adapter = await navigator.gpu.requestAdapter(); + this._device = await this._adapter.requestDevice(); + this._context = this._renderer.domElement.getContext('webgpu'); + + this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); + } + + protected initMatricesTexture(): void { + if (!this._parentLOD) { + this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); + } + } + + protected initColorsTexture(): void { + if (!this._parentLOD) { + this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); + this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; + this.colorsTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + } + + protected materialsNeedsUpdate(): void { + if ((this.material as Material).isMaterial) { + (this.material as Material).needsUpdate = true; + return; + } + + for (const material of (this.material as Material[])) { + material.needsUpdate = true; + } + } + + /** + * Creates and computes the BVH (Bounding Volume Hierarchy) for the instances. + * It's recommended to create it when all the instance matrices have been assigned. + * Once created it will be updated automatically. + * @param config Optional configuration parameters object. See `BVHParams` for details. + */ + // public computeBVH(config: BVHParams = {}): void { + // if (!this.bvh) this.bvh = new InstancedMeshBVH(this, config.margin, config.getBBoxFromBSphere, config.accurateCulling); + // this.bvh.clear(); + // this.bvh.create(); + // } + + /** + * Disposes of the BVH structure. + */ + public disposeBVH(): void { + this.bvh = null; + } + + /** + * Sets the local transformation matrix for a specific instance. + * @param id The index of the instance. + * @param matrix A `Matrix4` representing the local transformation to apply to the instance. + */ + public setMatrixAt(id: number, matrix: Matrix4): void { + matrix.toArray(this.matricesTexture._data, id * 16); + + if (this.instances) { + const instance = this.instances[id]; + matrix.decompose(instance.position, instance.quaternion, instance.scale); + } + + this.matricesTexture.enqueueUpdate(id); + this.bvh?.move(id); + } + + /** + * Gets the local transformation matrix of a specific instance. + * @param id The index of the instance. + * @param matrix Optional `Matrix4` to store the result. + * @returns The transformation matrix of the instance. + */ + public getMatrixAt(id: number, matrix = _tempMat4): Matrix4 { + return matrix.fromArray(this.matricesTexture._data, id * 16); + } + + /** + * Retrieves the position of a specific instance. + * @param index The index of the instance. + * @param target Optional `Vector3` to store the result. + * @returns The position of the instance as a `Vector3`. + */ + public getPositionAt(index: number, target = _position): Vector3 { + const offset = index * 16; + const array = this.matricesTexture._data; + + target.x = array[offset + 12]; + target.y = array[offset + 13]; + target.z = array[offset + 14]; + + return target; + } + + /** @internal */ + public getPositionAndMaxScaleOnAxisAt(index: number, position: Vector3): number { + const offset = index * 16; + const array = this.matricesTexture._data; + + const te0 = array[offset + 0]; + const te1 = array[offset + 1]; + const te2 = array[offset + 2]; + const scaleXSq = te0 * te0 + te1 * te1 + te2 * te2; + + const te4 = array[offset + 4]; + const te5 = array[offset + 5]; + const te6 = array[offset + 6]; + const scaleYSq = te4 * te4 + te5 * te5 + te6 * te6; + + const te8 = array[offset + 8]; + const te9 = array[offset + 9]; + const te10 = array[offset + 10]; + const scaleZSq = te8 * te8 + te9 * te9 + te10 * te10; + + position.x = array[offset + 12]; + position.y = array[offset + 13]; + position.z = array[offset + 14]; + + return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq)); + } + + /** @internal */ + public applyMatrixAtToSphere(index: number, sphere: Sphere, center: Vector3, radius: number): void { + const offset = index * 16; + const array = this.matricesTexture._data; + + const te0 = array[offset + 0]; + const te1 = array[offset + 1]; + const te2 = array[offset + 2]; + const te3 = array[offset + 3]; + const te4 = array[offset + 4]; + const te5 = array[offset + 5]; + const te6 = array[offset + 6]; + const te7 = array[offset + 7]; + const te8 = array[offset + 8]; + const te9 = array[offset + 9]; + const te10 = array[offset + 10]; + const te11 = array[offset + 11]; + const te12 = array[offset + 12]; + const te13 = array[offset + 13]; + const te14 = array[offset + 14]; + const te15 = array[offset + 15]; + + const position = sphere.center; + const x = center.x; + const y = center.y; + const z = center.z; + const w = 1 / (te3 * x + te7 * y + te11 * z + te15); + + position.x = (te0 * x + te4 * y + te8 * z + te12) * w; + position.y = (te1 * x + te5 * y + te9 * z + te13) * w; + position.z = (te2 * x + te6 * y + te10 * z + te14) * w; + + const scaleXSq = te0 * te0 + te1 * te1 + te2 * te2; + const scaleYSq = te4 * te4 + te5 * te5 + te6 * te6; + const scaleZSq = te8 * te8 + te9 * te9 + te10 * te10; + + sphere.radius = radius * Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq)); + } + + /** + * Sets the visibility of a specific instance. + * @param id The index of the instance. + * @param visible Whether the instance should be visible. + */ + public setVisibilityAt(id: number, visible: boolean): void { + this.availabilityArray[id * 2] = visible; + this._indexArrayNeedsUpdate = true; + } + + /** + * Gets the visibility of a specific instance. + * @param id The index of the instance. + * @returns Whether the instance is visible. + */ + public getVisibilityAt(id: number): boolean { + return this.availabilityArray[id * 2]; + } + + /** + * Sets the availability of a specific instance. + * @param id The index of the instance. + * @param active Whether the instance is active (not deleted). + */ + public setActiveAt(id: number, active: boolean): void { + this.availabilityArray[id * 2 + 1] = active; + this._indexArrayNeedsUpdate = true; + } + + /** + * Gets the availability of a specific instance. + * @param id The index of the instance. + * @returns Whether the instance is active (not deleted). + */ + public getActiveAt(id: number): boolean { + return this.availabilityArray[id * 2 + 1]; + } + + /** + * Indicates if a specific instance is visible and active. + * @param id The index of the instance. + * @returns Whether the instance is visible and active. + */ + public getActiveAndVisibilityAt(id: number): boolean { + const offset = id * 2; + const availabilityArray = this.availabilityArray; + return availabilityArray[offset] && availabilityArray[offset + 1]; + } + + /** + * Set if a specific instance is visible and active. + * @param id The index of the instance. + * @param value Whether the instance is active and active (not deleted). + */ + public setActiveAndVisibilityAt(id: number, value: boolean): void { + const offset = id * 2; + const availabilityArray = this.availabilityArray; + availabilityArray[offset] = value; + availabilityArray[offset + 1] = value; + this._indexArrayNeedsUpdate = true; + } + + /** + * Sets the color of a specific instance. + * @param id The index of the instance. + * @param color The color to assign to the instance. + */ + public setColorAt(id: number, color: ColorRepresentation): void { + if (this.colorsTexture === null) { + this.initColorsTexture(); + } + + if ((color as Color).isColor) { + (color as Color).toArray(this.colorsTexture._data, id * 4); + } else { + _tempCol.set(color).toArray(this.colorsTexture._data, id * 4); + } + + this.colorsTexture.enqueueUpdate(id); + } + + /** + * Gets the color of a specific instance. + * @param id The index of the instance. + * @param color Optional `Color` to store the result. + * @returns The color of the instance. + */ + public getColorAt(id: number, color = _tempCol): Color { + return color.fromArray(this.colorsTexture._data, id * 4); + } + + /** + * Sets the opacity of a specific instance. + * @param id The index of the instance. + * @param value The opacity value to assign. + */ + public setOpacityAt(id: number, value: number): void { + if (!this._useOpacity) { + if (this.colorsTexture === null) { + this.initColorsTexture(); + } else { + this.materialsNeedsUpdate(); + } + this._useOpacity = true; + } + + this.colorsTexture._data[id * 4 + 3] = value; + this.colorsTexture.enqueueUpdate(id); + } + + /** + * Gets the opacity of a specific instance. + * @param id The index of the instance. + * @returns The opacity of the instance. + */ + public getOpacityAt(id: number): number { + if (!this._useOpacity) return 1; + return this.colorsTexture._data[id * 4 + 3]; + } + + /** + * Copies `position`, `quaternion`, and `scale` of a specific instance to the specified target `Object3D`. + * @param id The index of the instance. + * @param target The `Object3D` where to copy transformation data. + */ + public copyTo(id: number, target: Object3D): void { + this.getMatrixAt(id, target.matrix).decompose(target.position, target.quaternion, target.scale); + } + + /** + * Computes the bounding box that encloses all instances, and updates the `boundingBox` attribute. + */ + public computeBoundingBox(): void { + const geometry = this._geometry; + const count = this._instancesArrayCount; + + this.boundingBox ??= new Box3(); + if (geometry.boundingBox === null) geometry.computeBoundingBox(); + + const geoBoundingBox = geometry.boundingBox; + const boundingBox = this.boundingBox; + + boundingBox.makeEmpty(); + + for (let i = 0; i < count; i++) { + if (!this.getActiveAt(i)) continue; + _box3.copy(geoBoundingBox).applyMatrix4(this.getMatrixAt(i)); + boundingBox.union(_box3); + } + } + + /** + * Computes the bounding sphere that encloses all instances, and updates the `boundingSphere` attribute. + */ + public computeBoundingSphere(): void { + const geometry = this._geometry; + const count = this._instancesArrayCount; + + this.boundingSphere ??= new Sphere(); + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + const geoBoundingSphere = geometry.boundingSphere; + const boundingSphere = this.boundingSphere; + + boundingSphere.makeEmpty(); + + for (let i = 0; i < count; i++) { + if (!this.getActiveAt(i)) continue; + _sphere.copy(geoBoundingSphere).applyMatrix4(this.getMatrixAt(i)); + boundingSphere.union(_sphere); + } + } + + /** + * Frees the GPU-related resources allocated. + */ + public dispose(): void { + this.dispatchEvent({ type: 'dispose' }); + + this.matricesTexture.dispose(); + this.colorsTexture?.dispose(); + this.morphTexture?.dispose(); + this.boneTexture?.dispose(); + this.uniformsTexture?.dispose(); + } + +} + +const _defaultCapacity = 1000; +const _box3 = new Box3(); +const _sphere = new Sphere(); +const _tempMat4 = new Matrix4(); +const _tempCol = new Color(); +const _position = new Vector3(); diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index ed992d8..7f86bfe 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -43,6 +43,27 @@ declare module '../InstancedMesh2.js' { } } +declare module '../InstancedMeshGPU.js' { + interface InstancedMeshGPU { + /** + * Performs frustum culling and manages LOD visibility. + * @param camera The main camera used for rendering. + * @param cameraLOD An optional camera for LOD calculations. Defaults to the main camera. + */ + performFrustumCulling(camera: Camera, cameraLOD?: Camera): void; + + /** @internal */ frustumCulling(camera: Camera): void; + /** @internal */ updateIndexArray(): void; + /** @internal */ updateRenderList(): void; + /** @internal */ BVHCulling(camera: Camera): void; + /** @internal */ linearCulling(camera: Camera): void; + + /** @internal */ frustumCullingLOD(LODrenderList: LODRenderList, camera: Camera, cameraLOD: Camera): void; + /** @internal */ BVHCullingLOD(LODrenderList: LODRenderList, indexes: Uint32Array[], sortObjects: boolean, camera: Camera, cameraLOD: Camera): void; + /** @internal */ linearCullingLOD(LODrenderList: LODRenderList, indexes: Uint32Array[], sortObjects: boolean, camera: Camera, cameraLOD: Camera): void; + } +} + const _frustum = new Frustum(); const _renderList = new InstancedRenderList(); const _projScreenMatrix = new Matrix4(); From 99bf4cb5188b9fcf9873ed7db38c69a50b9fa309 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 18 Jun 2025 14:17:29 -0400 Subject: [PATCH 05/22] remove duplicate file --- InstancedMeshGPU.js | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 InstancedMeshGPU.js diff --git a/InstancedMeshGPU.js b/InstancedMeshGPU.js deleted file mode 100644 index e17b4cf..0000000 --- a/InstancedMeshGPU.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as THREE from 'three'; - -class InstancedMeshGPU extends THREE.InstancedMesh { - constructor(geometry, material, count) { - super(geometry, material, count); - // ...existing initialization code... - } - - onBeforeRender(renderer, scene, camera, geometry, material, group) { - if (renderer.isWebGPURenderer) { - // Custom logic for WebGPURenderer - // e.g., update instance buffer, set custom uniforms, etc. - // Example: - // this.instanceMatrix.needsUpdate = true; - // renderer.updateInstanceBuffer(this); - - // ...your WebGPU-specific code here... - } else { - // Optionally call the base implementation for other renderers - super.onBeforeRender(renderer, scene, camera, geometry, material, group); - } - } - - // ...existing methods and properties... -} - -export { InstancedMeshGPU }; \ No newline at end of file From a9b8a5b376c290aad7db89539a319c3d5b156087 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 19 Jun 2025 20:50:44 -0400 Subject: [PATCH 06/22] InstancedMeshGPU extends InstancedMesh2 --- src/core/InstancedMeshGPU.ts | 497 ++--------------------------------- 1 file changed, 22 insertions(+), 475 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index fde2f88..3b57990 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -5,8 +5,9 @@ import { LODInfo } from './feature/LOD.js'; import { InstancedEntity } from './InstancedEntity.js'; import { InstancedMeshBVH } from './InstancedMeshBVH.js'; import { SquareDataTexture } from './utils/SquareDataTexture.js'; -import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; +import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer, InstanceNode } from 'three/webgpu'; import { getColorTexture } from '../shaders/tsl/nodes.js'; +import { InstancedMesh2 } from './InstancedMesh2.js'; /** @@ -50,7 +51,7 @@ export class InstancedMeshGPU< TGeometry extends BufferGeometry = BufferGeometry, TMaterial extends Material | Material[] = Material | Material[], TEventMap extends Object3DEventMap = Object3DEventMap -> extends Mesh { +> extends InstancedMesh2 { /** * The number of instances rendered in the last frame. */ @@ -58,171 +59,48 @@ export class InstancedMeshGPU< /** * @defaultValue `InstancedMesh2` */ - public override readonly type = 'InstancedMeshGPU'; + public override readonly type = 'InstancedMeshGPU' as any; /** * Indicates if this is an `InstancedMeshGPU`. */ public readonly isInstancedMeshGPU = true; - /** - * An array of `Entity` representing individual instances. - * This array is only initialized if `createEntities` is set to `true` in the constructor parameters. - */ - public instances: Entity[] = null; /** * Attribute storing indices of the instances to be rendered. */ - public instanceIndex: StorageInstancedBufferAttribute = null; + public instanceIndex: InstanceNode = null; /** * Texture storing matrices for instances. */ - public matricesTexture: SquareDataTexture; + public matricesTexture: SquareDataTextureGPU; /** * Texture storing colors for instances. */ - public colorsTexture: SquareDataTexture = null; + public override colorsTexture: SquareDataTextureGPU = null; /** * Texture storing morph target influences for instances. */ - public morphTexture: DataTexture = null; + public override morphTexture: DataTexture = null; /** * Texture storing bones for instances. */ - public boneTexture: SquareDataTexture = null; + public override boneTexture: SquareDataTextureGPU = null; /** * Texture storing custom uniforms per instance. */ - public uniformsTexture: SquareDataTexture = null; - /** - * This bounding box encloses all instances, which can be calculated with `computeBoundingBox` method. - * Bounding box isn't computed by default. It needs to be explicitly computed, otherwise it's `null`. - */ - public boundingBox: Box3 = null; - /** - * This bounding sphere encloses all instances, which can be calculated with `computeBoundingSphere` method. - * Bounding sphere is computed during its first render. You may need to recompute it if an instance is transformed. - */ - public boundingSphere: Sphere = null; - /** - * BVH structure for optimized culling and intersection testing. - * It's possible to create the BVH using the `computeBVH` method. Once created it will be updated automatically. - */ - public bvh: InstancedMeshBVH = null; - /** - * Custom sort function for instances. - * It's possible to create the radix sort using the `createRadixSort` method. - * @default null - */ - public customSort: CustomSortCallback = null; - /** - * Flag indicating if raycasting should only consider the last frame frustum culled instances. - * This is ignored if the bvh has been created. - * @default false - */ - public raycastOnlyFrustum = false; - /** - * Array storing visibility and availability for instances. - * [visible0, active0, visible1, active1, ...] - */ - public readonly availabilityArray: boolean[]; - /** - * Contains data for managing LOD, allowing different levels of detail for rendering and shadow casting. - */ - public LODinfo: LODInfo = null; - /** - * Flag indicating whether to automatically perform frustum culling before rendering. - * @default true - */ - public autoUpdate = true; - /** - * Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. - * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton across multiple skinned meshes. - * @default `AttachedBindMode` - */ - public bindMode: BindMode = AttachedBindMode; - /** - * The base matrix that is used for the bound bone transforms. - */ - public bindMatrix: Matrix4 = null; - /** - * The base matrix that is used for resetting the bound bone transforms. - */ - public bindMatrixInverse: Matrix4 = null; - /** - * Skeleton representing the bone hierarchy of the skinned mesh. - */ - public skeleton: Skeleton = null; - /** - * Callback function called if an instance is inside the frustum. - */ - public onFrustumEnter: OnFrustumEnterCallback = null; - /** @internal */ _renderer: WebGPURenderer = null; - /** @internal */ _instancesCount = 0; - /** @internal */ _instancesArrayCount = 0; - /** @internal */ _perObjectFrustumCulled = true; - /** @internal */ _sortObjects = false; - /** @internal */ _capacity: number; - /** @internal */ _indexArrayNeedsUpdate = false; - /** @internal */ _geometry: TGeometry; - /** @internal */ _parentLOD: InstancedMeshGPU; - protected readonly _allowsEuler: boolean; - protected readonly _tempInstance: InstancedEntity; - protected _useOpacity = false; - protected _currentMaterial: MeshBasicNodeMaterial = null; - protected _customProgramCacheKeyBase: () => string = null; - // protected _onBeforeCompileBase: (parameters: WebGPUProgramParametersWithUniforms, renderer: WebGLRenderer) => void = null; - protected _propertiesGetBase: (obj: unknown) => unknown = null; - protected _propertiesGetMap = new WeakMap unknown>(); - protected _properties = new WeakMap(); - protected _freeIds: number[] = []; - protected _createEntities: boolean; + public override uniformsTexture: SquareDataTextureGPU = null; - // HACK TO MAKE IT WORK WITHOUT UPDATE CORE - /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering - /** @internal */ instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception - /** @internal */ instanceColor = null; // must be null to avoid exception - private _adapter: GPUAdapter; - private _device: GPUDevice; - private _context: any; - - /** - * The capacity of the instance buffers. - */ - public get capacity(): number { return this._capacity; } + /** @internal */ _renderer: WebGPURenderer = null; /** - * The number of active instances. + * Material for TSL nodes system */ - public get instancesCount(): number { return this._instancesCount; } + protected override _currentMaterial: MeshBasicNodeMaterial = null; - /** - * Determines if per-instance frustum culling is enabled. - * @default true - */ - public get perObjectFrustumCulled(): boolean { return this._perObjectFrustumCulled; } - public set perObjectFrustumCulled(value: boolean) { - this._perObjectFrustumCulled = value; - this._indexArrayNeedsUpdate = true; - } + /** @internal */ override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception - /** - * Determines if objects should be sorted before rendering. - * @default false - */ - public get sortObjects(): boolean { return this._sortObjects; } - public set sortObjects(value: boolean) { - this._sortObjects = value; - this._indexArrayNeedsUpdate = true; - } - - /** - * An instance of `BufferGeometry` (or derived classes), defining the object's structure. - */ - // @ts-expect-error It's defined as a property, but is overridden as an accessor. - public override get geometry(): TGeometry { return this._geometry; } - public override set geometry(value: TGeometry) { - this._geometry = value; - // this.patchGeometry(value); - } + private _adapter: GPUAdapter; + private _device: GPUDevice; + private _context: any; /** @internal */ // eslint-disable-next-line @typescript-eslint/unified-signatures @@ -240,7 +118,7 @@ export class InstancedMeshGPU< const { allowsEuler, renderer, createEntities } = params; - super(geometry, null); + super(null, null); const capacity = params.capacity > 0 ? params.capacity : _defaultCapacity; this._renderer = renderer; @@ -268,352 +146,21 @@ export class InstancedMeshGPU< this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); } - protected initMatricesTexture(): void { + protected override initMatricesTexture(): void { if (!this._parentLOD) { - this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); + this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); } } - protected initColorsTexture(): void { + protected override initColorsTexture(): void { if (!this._parentLOD) { - this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); + this.colorsTexture = new SquareDataTextureGPU(Float32Array, 4, 1, this._capacity); this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; this.colorsTexture._data.fill(1); this.materialsNeedsUpdate(); } } - protected materialsNeedsUpdate(): void { - if ((this.material as Material).isMaterial) { - (this.material as Material).needsUpdate = true; - return; - } - - for (const material of (this.material as Material[])) { - material.needsUpdate = true; - } - } - - /** - * Creates and computes the BVH (Bounding Volume Hierarchy) for the instances. - * It's recommended to create it when all the instance matrices have been assigned. - * Once created it will be updated automatically. - * @param config Optional configuration parameters object. See `BVHParams` for details. - */ - // public computeBVH(config: BVHParams = {}): void { - // if (!this.bvh) this.bvh = new InstancedMeshBVH(this, config.margin, config.getBBoxFromBSphere, config.accurateCulling); - // this.bvh.clear(); - // this.bvh.create(); - // } - - /** - * Disposes of the BVH structure. - */ - public disposeBVH(): void { - this.bvh = null; - } - - /** - * Sets the local transformation matrix for a specific instance. - * @param id The index of the instance. - * @param matrix A `Matrix4` representing the local transformation to apply to the instance. - */ - public setMatrixAt(id: number, matrix: Matrix4): void { - matrix.toArray(this.matricesTexture._data, id * 16); - - if (this.instances) { - const instance = this.instances[id]; - matrix.decompose(instance.position, instance.quaternion, instance.scale); - } - - this.matricesTexture.enqueueUpdate(id); - this.bvh?.move(id); - } - - /** - * Gets the local transformation matrix of a specific instance. - * @param id The index of the instance. - * @param matrix Optional `Matrix4` to store the result. - * @returns The transformation matrix of the instance. - */ - public getMatrixAt(id: number, matrix = _tempMat4): Matrix4 { - return matrix.fromArray(this.matricesTexture._data, id * 16); - } - - /** - * Retrieves the position of a specific instance. - * @param index The index of the instance. - * @param target Optional `Vector3` to store the result. - * @returns The position of the instance as a `Vector3`. - */ - public getPositionAt(index: number, target = _position): Vector3 { - const offset = index * 16; - const array = this.matricesTexture._data; - - target.x = array[offset + 12]; - target.y = array[offset + 13]; - target.z = array[offset + 14]; - - return target; - } - - /** @internal */ - public getPositionAndMaxScaleOnAxisAt(index: number, position: Vector3): number { - const offset = index * 16; - const array = this.matricesTexture._data; - - const te0 = array[offset + 0]; - const te1 = array[offset + 1]; - const te2 = array[offset + 2]; - const scaleXSq = te0 * te0 + te1 * te1 + te2 * te2; - - const te4 = array[offset + 4]; - const te5 = array[offset + 5]; - const te6 = array[offset + 6]; - const scaleYSq = te4 * te4 + te5 * te5 + te6 * te6; - - const te8 = array[offset + 8]; - const te9 = array[offset + 9]; - const te10 = array[offset + 10]; - const scaleZSq = te8 * te8 + te9 * te9 + te10 * te10; - - position.x = array[offset + 12]; - position.y = array[offset + 13]; - position.z = array[offset + 14]; - - return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq)); - } - - /** @internal */ - public applyMatrixAtToSphere(index: number, sphere: Sphere, center: Vector3, radius: number): void { - const offset = index * 16; - const array = this.matricesTexture._data; - - const te0 = array[offset + 0]; - const te1 = array[offset + 1]; - const te2 = array[offset + 2]; - const te3 = array[offset + 3]; - const te4 = array[offset + 4]; - const te5 = array[offset + 5]; - const te6 = array[offset + 6]; - const te7 = array[offset + 7]; - const te8 = array[offset + 8]; - const te9 = array[offset + 9]; - const te10 = array[offset + 10]; - const te11 = array[offset + 11]; - const te12 = array[offset + 12]; - const te13 = array[offset + 13]; - const te14 = array[offset + 14]; - const te15 = array[offset + 15]; - - const position = sphere.center; - const x = center.x; - const y = center.y; - const z = center.z; - const w = 1 / (te3 * x + te7 * y + te11 * z + te15); - - position.x = (te0 * x + te4 * y + te8 * z + te12) * w; - position.y = (te1 * x + te5 * y + te9 * z + te13) * w; - position.z = (te2 * x + te6 * y + te10 * z + te14) * w; - - const scaleXSq = te0 * te0 + te1 * te1 + te2 * te2; - const scaleYSq = te4 * te4 + te5 * te5 + te6 * te6; - const scaleZSq = te8 * te8 + te9 * te9 + te10 * te10; - - sphere.radius = radius * Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq)); - } - - /** - * Sets the visibility of a specific instance. - * @param id The index of the instance. - * @param visible Whether the instance should be visible. - */ - public setVisibilityAt(id: number, visible: boolean): void { - this.availabilityArray[id * 2] = visible; - this._indexArrayNeedsUpdate = true; - } - - /** - * Gets the visibility of a specific instance. - * @param id The index of the instance. - * @returns Whether the instance is visible. - */ - public getVisibilityAt(id: number): boolean { - return this.availabilityArray[id * 2]; - } - - /** - * Sets the availability of a specific instance. - * @param id The index of the instance. - * @param active Whether the instance is active (not deleted). - */ - public setActiveAt(id: number, active: boolean): void { - this.availabilityArray[id * 2 + 1] = active; - this._indexArrayNeedsUpdate = true; - } - - /** - * Gets the availability of a specific instance. - * @param id The index of the instance. - * @returns Whether the instance is active (not deleted). - */ - public getActiveAt(id: number): boolean { - return this.availabilityArray[id * 2 + 1]; - } - - /** - * Indicates if a specific instance is visible and active. - * @param id The index of the instance. - * @returns Whether the instance is visible and active. - */ - public getActiveAndVisibilityAt(id: number): boolean { - const offset = id * 2; - const availabilityArray = this.availabilityArray; - return availabilityArray[offset] && availabilityArray[offset + 1]; - } - - /** - * Set if a specific instance is visible and active. - * @param id The index of the instance. - * @param value Whether the instance is active and active (not deleted). - */ - public setActiveAndVisibilityAt(id: number, value: boolean): void { - const offset = id * 2; - const availabilityArray = this.availabilityArray; - availabilityArray[offset] = value; - availabilityArray[offset + 1] = value; - this._indexArrayNeedsUpdate = true; - } - - /** - * Sets the color of a specific instance. - * @param id The index of the instance. - * @param color The color to assign to the instance. - */ - public setColorAt(id: number, color: ColorRepresentation): void { - if (this.colorsTexture === null) { - this.initColorsTexture(); - } - - if ((color as Color).isColor) { - (color as Color).toArray(this.colorsTexture._data, id * 4); - } else { - _tempCol.set(color).toArray(this.colorsTexture._data, id * 4); - } - - this.colorsTexture.enqueueUpdate(id); - } - - /** - * Gets the color of a specific instance. - * @param id The index of the instance. - * @param color Optional `Color` to store the result. - * @returns The color of the instance. - */ - public getColorAt(id: number, color = _tempCol): Color { - return color.fromArray(this.colorsTexture._data, id * 4); - } - - /** - * Sets the opacity of a specific instance. - * @param id The index of the instance. - * @param value The opacity value to assign. - */ - public setOpacityAt(id: number, value: number): void { - if (!this._useOpacity) { - if (this.colorsTexture === null) { - this.initColorsTexture(); - } else { - this.materialsNeedsUpdate(); - } - this._useOpacity = true; - } - - this.colorsTexture._data[id * 4 + 3] = value; - this.colorsTexture.enqueueUpdate(id); - } - - /** - * Gets the opacity of a specific instance. - * @param id The index of the instance. - * @returns The opacity of the instance. - */ - public getOpacityAt(id: number): number { - if (!this._useOpacity) return 1; - return this.colorsTexture._data[id * 4 + 3]; - } - - /** - * Copies `position`, `quaternion`, and `scale` of a specific instance to the specified target `Object3D`. - * @param id The index of the instance. - * @param target The `Object3D` where to copy transformation data. - */ - public copyTo(id: number, target: Object3D): void { - this.getMatrixAt(id, target.matrix).decompose(target.position, target.quaternion, target.scale); - } - - /** - * Computes the bounding box that encloses all instances, and updates the `boundingBox` attribute. - */ - public computeBoundingBox(): void { - const geometry = this._geometry; - const count = this._instancesArrayCount; - - this.boundingBox ??= new Box3(); - if (geometry.boundingBox === null) geometry.computeBoundingBox(); - - const geoBoundingBox = geometry.boundingBox; - const boundingBox = this.boundingBox; - - boundingBox.makeEmpty(); - - for (let i = 0; i < count; i++) { - if (!this.getActiveAt(i)) continue; - _box3.copy(geoBoundingBox).applyMatrix4(this.getMatrixAt(i)); - boundingBox.union(_box3); - } - } - - /** - * Computes the bounding sphere that encloses all instances, and updates the `boundingSphere` attribute. - */ - public computeBoundingSphere(): void { - const geometry = this._geometry; - const count = this._instancesArrayCount; - - this.boundingSphere ??= new Sphere(); - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); - - const geoBoundingSphere = geometry.boundingSphere; - const boundingSphere = this.boundingSphere; - - boundingSphere.makeEmpty(); - - for (let i = 0; i < count; i++) { - if (!this.getActiveAt(i)) continue; - _sphere.copy(geoBoundingSphere).applyMatrix4(this.getMatrixAt(i)); - boundingSphere.union(_sphere); - } - } - - /** - * Frees the GPU-related resources allocated. - */ - public dispose(): void { - this.dispatchEvent({ type: 'dispose' }); - - this.matricesTexture.dispose(); - this.colorsTexture?.dispose(); - this.morphTexture?.dispose(); - this.boneTexture?.dispose(); - this.uniformsTexture?.dispose(); - } - } const _defaultCapacity = 1000; -const _box3 = new Box3(); -const _sphere = new Sphere(); -const _tempMat4 = new Matrix4(); -const _tempCol = new Color(); -const _position = new Vector3(); From 86e317fe5950faba8a79cc016f16da6b7b6626ee Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 19 Jun 2025 20:51:31 -0400 Subject: [PATCH 07/22] revert src/core/feature/FrustumCulling.ts --- src/core/feature/FrustumCulling.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 7f86bfe..ed992d8 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -43,27 +43,6 @@ declare module '../InstancedMesh2.js' { } } -declare module '../InstancedMeshGPU.js' { - interface InstancedMeshGPU { - /** - * Performs frustum culling and manages LOD visibility. - * @param camera The main camera used for rendering. - * @param cameraLOD An optional camera for LOD calculations. Defaults to the main camera. - */ - performFrustumCulling(camera: Camera, cameraLOD?: Camera): void; - - /** @internal */ frustumCulling(camera: Camera): void; - /** @internal */ updateIndexArray(): void; - /** @internal */ updateRenderList(): void; - /** @internal */ BVHCulling(camera: Camera): void; - /** @internal */ linearCulling(camera: Camera): void; - - /** @internal */ frustumCullingLOD(LODrenderList: LODRenderList, camera: Camera, cameraLOD: Camera): void; - /** @internal */ BVHCullingLOD(LODrenderList: LODRenderList, indexes: Uint32Array[], sortObjects: boolean, camera: Camera, cameraLOD: Camera): void; - /** @internal */ linearCullingLOD(LODrenderList: LODRenderList, indexes: Uint32Array[], sortObjects: boolean, camera: Camera, cameraLOD: Camera): void; - } -} - const _frustum = new Frustum(); const _renderList = new InstancedRenderList(); const _projScreenMatrix = new Matrix4(); From ce2e005c191f5c48460adb261d35e2f4298040dd Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 19 Jun 2025 21:07:19 -0400 Subject: [PATCH 08/22] redundant code --- src/core/InstancedMeshGPU.ts | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 3b57990..30d0604 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -1,10 +1,5 @@ -import { AttachedBindMode, BindMode, Box3, BufferGeometry, Color, ColorManagement, ColorRepresentation, DataTexture, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Skeleton, Sphere, Vector3 } from 'three'; -import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; -import { Entity } from './feature/Instances.js'; -import { LODInfo } from './feature/LOD.js'; -import { InstancedEntity } from './InstancedEntity.js'; -import { InstancedMeshBVH } from './InstancedMeshBVH.js'; -import { SquareDataTexture } from './utils/SquareDataTexture.js'; +import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMap } from 'three'; +import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer, InstanceNode } from 'three/webgpu'; import { getColorTexture } from '../shaders/tsl/nodes.js'; import { InstancedMesh2 } from './InstancedMesh2.js'; @@ -64,14 +59,10 @@ export class InstancedMeshGPU< * Indicates if this is an `InstancedMeshGPU`. */ public readonly isInstancedMeshGPU = true; - /** - * Attribute storing indices of the instances to be rendered. - */ - public instanceIndex: InstanceNode = null; /** * Texture storing matrices for instances. */ - public matricesTexture: SquareDataTextureGPU; + public override matricesTexture: SquareDataTextureGPU; /** * Texture storing colors for instances. */ @@ -89,7 +80,7 @@ export class InstancedMeshGPU< */ public override uniformsTexture: SquareDataTextureGPU = null; - /** @internal */ _renderer: WebGPURenderer = null; + /** @internal */ override _renderer: WebGPURenderer | any = null; /** * Material for TSL nodes system @@ -118,19 +109,12 @@ export class InstancedMeshGPU< const { allowsEuler, renderer, createEntities } = params; - super(null, null); + super(geometry, material); const capacity = params.capacity > 0 ? params.capacity : _defaultCapacity; this._renderer = renderer; this._capacity = capacity; - this._parentLOD = LOD; - this._geometry = geometry; - this.material = material; - this._allowsEuler = allowsEuler ?? false; - // this._tempInstance = new InstancedEntity(this, -1, allowsEuler); - this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2); this._createEntities = createEntities; - // this.initIndexAttribute(); this.initMatricesTexture(); } From c92e03dfc5473549dbbd05182b53dfe61df3f401 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 19 Jun 2025 21:15:17 -0400 Subject: [PATCH 09/22] fix TSL param --- src/shaders/tsl/nodes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shaders/tsl/nodes.ts b/src/shaders/tsl/nodes.ts index abb2ac4..5a2d576 100644 --- a/src/shaders/tsl/nodes.ts +++ b/src/shaders/tsl/nodes.ts @@ -23,8 +23,8 @@ export const getInstancedMatrix = (matricesTexture) => { }; -export const getBoneMatrix = (boneTexture) => Fn(( [i] ) => { - const bonesPerInstance = uniform( 'int' ); +export const getBoneMatrix = (boneTexture) => Fn((i) => { + const bonesPerInstance = uniform( 'int' ); const size = int( textureSize( boneTexture, int( 0 ) ).x ).toVar(); const j = int( bonesPerInstance.mul( int( instanceIndex ) ).add( int( i ) ).mul( int( 4 ) ) ).toVar(); From 2f513b317bb49900363b2f514fd2c97aed5f82da Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 11:03:01 -0400 Subject: [PATCH 10/22] set .fragmentNode and .colorNode in appropriate places? --- src/core/InstancedMeshGPU.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 30d0604..506ccd5 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -1,7 +1,7 @@ import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMap } from 'three'; import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; -import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer, InstanceNode } from 'three/webgpu'; -import { getColorTexture } from '../shaders/tsl/nodes.js'; +import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; +import { getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; import { InstancedMesh2 } from './InstancedMesh2.js'; @@ -47,10 +47,6 @@ export class InstancedMeshGPU< TMaterial extends Material | Material[] = Material | Material[], TEventMap extends Object3DEventMap = Object3DEventMap > extends InstancedMesh2 { - /** - * The number of instances rendered in the last frame. - */ - public declare count: number; /** * @defaultValue `InstancedMesh2` */ @@ -67,10 +63,6 @@ export class InstancedMeshGPU< * Texture storing colors for instances. */ public override colorsTexture: SquareDataTextureGPU = null; - /** - * Texture storing morph target influences for instances. - */ - public override morphTexture: DataTexture = null; /** * Texture storing bones for instances. */ @@ -87,7 +79,7 @@ export class InstancedMeshGPU< */ protected override _currentMaterial: MeshBasicNodeMaterial = null; - /** @internal */ override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception + /** @internal */ override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); private _adapter: GPUAdapter; private _device: GPUDevice; @@ -126,14 +118,14 @@ export class InstancedMeshGPU< this._adapter = await navigator.gpu.requestAdapter(); this._device = await this._adapter.requestDevice(); this._context = this._renderer.domElement.getContext('webgpu'); - - this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); } protected override initMatricesTexture(): void { if (!this._parentLOD) { this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); } + + this._currentMaterial.fragmentNode = getInstancedMatrix(this.colorsTexture); } protected override initColorsTexture(): void { @@ -143,6 +135,7 @@ export class InstancedMeshGPU< this.colorsTexture._data.fill(1); this.materialsNeedsUpdate(); } + this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); } } From dae604a6c24c9a6f05d0a3127a8cae6876c7249d Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 11:28:18 -0400 Subject: [PATCH 11/22] fix typing --- src/core/InstancedMeshGPU.ts | 90 ++++++++---------------------------- 1 file changed, 20 insertions(+), 70 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 506ccd5..51b9e5f 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -2,41 +2,25 @@ import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMa import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; import { getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; -import { InstancedMesh2 } from './InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.js'; /** * Parameters for configuring an `InstancedMeshGPU` instance. */ -export interface InstancedMeshGPUParams { - /** - * Determines the maximum number of instances that buffers can hold. - * The buffers will be expanded automatically if necessary. - * @default 1000 - */ +export interface InstancedMeshGPUParams extends Omit { + capacity?: number; - /** - * Determines whether to create an array of `InstancedEntity` to easily manipulate instances at the cost of more memory. - * @default false - */ + createEntities?: boolean; - /** - * Determines whether `InstancedEntity` can use the `rotation` property. - * If `true` `quaternion` and `rotation` will be synchronized, affecting performance. - * @default false - */ + allowsEuler?: boolean; - /** - * WebGL renderer instance. - * If not provided, buffers will be initialized during the first render, resulting in no instances being rendered initially. - * @default null - */ + renderer?: WebGPURenderer; } /** * Alternative `InstancedMesh` class to support additional features like frustum culling, fast raycasting, LOD and more. - * @template TData Type for additional instance data. * @template TGeometry Type extending `BufferGeometry`. * @template TMaterial Type extending `Material` or an array of `Material`. * @template TEventMap Type extending `Object3DEventMap`. @@ -46,78 +30,44 @@ export class InstancedMeshGPU< TGeometry extends BufferGeometry = BufferGeometry, TMaterial extends Material | Material[] = Material | Material[], TEventMap extends Object3DEventMap = Object3DEventMap -> extends InstancedMesh2 { - /** - * @defaultValue `InstancedMesh2` - */ +> extends InstancedMesh2 { + public override readonly type = 'InstancedMeshGPU' as any; - /** - * Indicates if this is an `InstancedMeshGPU`. - */ + public readonly isInstancedMeshGPU = true; - /** - * Texture storing matrices for instances. - */ + public override matricesTexture: SquareDataTextureGPU; - /** - * Texture storing colors for instances. - */ + public override colorsTexture: SquareDataTextureGPU = null; - /** - * Texture storing bones for instances. - */ + public override boneTexture: SquareDataTextureGPU = null; - /** - * Texture storing custom uniforms per instance. - */ + public override uniformsTexture: SquareDataTextureGPU = null; - /** @internal */ override _renderer: WebGPURenderer | any = null; + override _renderer: WebGPURenderer | any = null; - /** - * Material for TSL nodes system - */ - protected override _currentMaterial: MeshBasicNodeMaterial = null; + override _currentMaterial: MeshBasicNodeMaterial = null; - /** @internal */ override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); + override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); private _adapter: GPUAdapter; private _device: GPUDevice; private _context: any; - /** @internal */ - // eslint-disable-next-line @typescript-eslint/unified-signatures - constructor(geometry: TGeometry, material: TMaterial, params?: InstancedMeshGPUParams, LOD?: InstancedMeshGPU); - constructor(geometry: TGeometry, material: TMaterial, params?: InstancedMeshGPUParams); - /** - * @remarks Geometry cannot be shared. If reused, it will be cloned. - * @param geometry An instance of `BufferGeometry`. - * @param material A single or an array of `Material`. - * @param params Optional configuration parameters object. See `InstancedMeshGPUParams` for details. - */ constructor(geometry: TGeometry, material: TMaterial, params: InstancedMeshGPUParams = {}, LOD?: InstancedMeshGPU) { if (!geometry) throw new Error('"geometry" is mandatory.'); if (!material) throw new Error('"material" is mandatory.'); - const { allowsEuler, renderer, createEntities } = params; - - super(geometry, material); - - const capacity = params.capacity > 0 ? params.capacity : _defaultCapacity; - this._renderer = renderer; - this._capacity = capacity; - this._createEntities = createEntities; - this.initMatricesTexture(); + super(geometry, material, params, LOD); + this.init(); } - /** - * Initializes the `InstancedMeshGPU` instance. - * This method is called automatically when the instance is created. - */ public async init(): Promise { this._adapter = await navigator.gpu.requestAdapter(); this._device = await this._adapter.requestDevice(); this._context = this._renderer.domElement.getContext('webgpu'); + this.initMatricesTexture(); + this.initColorsTexture(); } protected override initMatricesTexture(): void { From a3a504ec50f50b7685e036e7cd9278426b24c28f Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 11:42:26 -0400 Subject: [PATCH 12/22] ensure textures are updated before each render --- src/core/InstancedMeshGPU.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 51b9e5f..ace0974 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -68,14 +68,19 @@ export class InstancedMeshGPU< this._context = this._renderer.domElement.getContext('webgpu'); this.initMatricesTexture(); this.initColorsTexture(); + // Ensure textures are updated before first render + this.matricesTexture.update(this._renderer); + this.colorsTexture?.update(this._renderer); } protected override initMatricesTexture(): void { if (!this._parentLOD) { this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); } - - this._currentMaterial.fragmentNode = getInstancedMatrix(this.colorsTexture); + // Set the node for instance matrix in the material + if (this._currentMaterial) { + (this._currentMaterial as any).matricesTexture = getInstancedMatrix(this.matricesTexture); + } } protected override initColorsTexture(): void { @@ -85,9 +90,26 @@ export class InstancedMeshGPU< this.colorsTexture._data.fill(1); this.materialsNeedsUpdate(); } - this._currentMaterial.colorNode = getColorTexture(this.colorsTexture); + // Set the node for instance color in the material + if (this._currentMaterial) { + (this._currentMaterial as any).colorNode = getColorTexture(this.colorsTexture); + } } + // Ensure textures are updated before each render + public override onBeforeRender(renderer, scene, camera, geometry, material, group): void { + super.onBeforeRender(renderer, scene, camera, geometry, material, group); + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + + // Ensure the material is a node material and set up the node graph + if (material && material.isNodeMaterial) { + // Set the instance matrix node for vertex transformation + material.positionNode = getInstancedMatrix(this.matricesTexture); + // Set the color node for fragment color + material.colorNode = getColorTexture(this.colorsTexture); + } + } } const _defaultCapacity = 1000; From a9b5f1afec73e22a734b013750639c8df20d28d5 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 12:14:24 -0400 Subject: [PATCH 13/22] setup bonesTexture and use uniforms --- src/core/InstancedMeshGPU.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index ace0974..3125481 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -1,8 +1,9 @@ import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMap } from 'three'; import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; -import { getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; +import { getBoneMatrix, getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.js'; +import { uniform, uniformTexture } from 'three/tsl'; /** @@ -46,6 +47,7 @@ export class InstancedMeshGPU< override _renderer: WebGPURenderer | any = null; + override _material: MeshBasicNodeMaterial = null; override _currentMaterial: MeshBasicNodeMaterial = null; override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); @@ -54,11 +56,13 @@ export class InstancedMeshGPU< private _device: GPUDevice; private _context: any; - constructor(geometry: TGeometry, material: TMaterial, params: InstancedMeshGPUParams = {}, LOD?: InstancedMeshGPU) { + constructor(geometry: TGeometry, material: MeshBasicNodeMaterial, params: InstancedMeshGPUParams = {}, LOD?: InstancedMeshGPU) { if (!geometry) throw new Error('"geometry" is mandatory.'); if (!material) throw new Error('"material" is mandatory.'); - super(geometry, material, params, LOD); + this._currentMaterial = material; + this._material = material; + super(geometry, null, null, LOD); this.init(); } @@ -79,7 +83,7 @@ export class InstancedMeshGPU< } // Set the node for instance matrix in the material if (this._currentMaterial) { - (this._currentMaterial as any).matricesTexture = getInstancedMatrix(this.matricesTexture); + (this._currentMaterial as any).matricesTexture = getInstancedMatrix(uniform(this.matricesTexture); } } @@ -92,7 +96,20 @@ export class InstancedMeshGPU< } // Set the node for instance color in the material if (this._currentMaterial) { - (this._currentMaterial as any).colorNode = getColorTexture(this.colorsTexture); + (this._currentMaterial as any).colorNode = getColorTexture(uniform(this.colorsTexture)); + } + } + + protected initBoneTexture(): void { + if (!this._parentLOD) { + this.boneTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); + this.boneTexture.colorSpace = ColorManagement.workingColorSpace; + this.boneTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + // Set the node for bone matrix in the material + if (this._currentMaterial) { + (this._currentMaterial as any).boneMatrixNode = getBoneMatrix(uniform(this.boneTexture)); } } From 4127e4f7a280bdd765937df6e5d3b11bb616e7d4 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 12:15:49 -0400 Subject: [PATCH 14/22] needent update twice --- src/core/InstancedMeshGPU.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 3125481..892276e 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -118,14 +118,6 @@ export class InstancedMeshGPU< super.onBeforeRender(renderer, scene, camera, geometry, material, group); this.matricesTexture.update(renderer); this.colorsTexture?.update(renderer); - - // Ensure the material is a node material and set up the node graph - if (material && material.isNodeMaterial) { - // Set the instance matrix node for vertex transformation - material.positionNode = getInstancedMatrix(this.matricesTexture); - // Set the color node for fragment color - material.colorNode = getColorTexture(this.colorsTexture); - } } } From 49b84aa28846c8d5cfbd6366fc974358f5249f32 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 13:49:41 -0400 Subject: [PATCH 15/22] deepscan fixes --- src/core/InstancedMeshGPU.ts | 7 ++++--- src/shaders/tsl/nodes.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMeshGPU.ts index 892276e..bae4050 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMeshGPU.ts @@ -1,9 +1,10 @@ import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMap } from 'three'; -import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; import { getBoneMatrix, getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.js'; -import { uniform, uniformTexture } from 'three/tsl'; +import { uniform } from 'three/tsl'; + +import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; /** @@ -83,7 +84,7 @@ export class InstancedMeshGPU< } // Set the node for instance matrix in the material if (this._currentMaterial) { - (this._currentMaterial as any).matricesTexture = getInstancedMatrix(uniform(this.matricesTexture); + (this._currentMaterial as any).matricesTexture = getInstancedMatrix(uniform(this.matricesTexture)); } } diff --git a/src/shaders/tsl/nodes.ts b/src/shaders/tsl/nodes.ts index 5a2d576..f7d7e2a 100644 --- a/src/shaders/tsl/nodes.ts +++ b/src/shaders/tsl/nodes.ts @@ -1,4 +1,4 @@ -import { Fn, instanceIndex, int, ivec2, mat4, textureSize, tslFn, uniform, vec4 } from 'three/tsl'; +import { Fn, instanceIndex, int, ivec2, mat4, textureSize, uniform, vec4 } from 'three/tsl'; export const getColorTexture = (colorsTexture) => { From a05eeb38b92fa71aee5f155cf9ac5400df2bfbba Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 20 Jun 2025 16:48:26 -0400 Subject: [PATCH 16/22] change deprecated fn --- src/shaders/tsl/nodes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shaders/tsl/nodes.ts b/src/shaders/tsl/nodes.ts index f7d7e2a..fba6ac5 100644 --- a/src/shaders/tsl/nodes.ts +++ b/src/shaders/tsl/nodes.ts @@ -4,7 +4,7 @@ import { Fn, instanceIndex, int, ivec2, mat4, textureSize, uniform, vec4 } from export const getColorTexture = (colorsTexture) => { const size = int(textureSize(colorsTexture, int(0)).x).toVar(); const j = int(instanceIndex).toVar(); - const x = int(j.remainder(size)).toVar(); + const x = int(j.mod(size)).toVar(); const y = int( j.div( size ) ).toVar(); return colorsTexture.sample( ivec2( x, y ) ).setSampler( false ); }; @@ -13,7 +13,7 @@ export const getColorTexture = (colorsTexture) => { export const getInstancedMatrix = (matricesTexture) => { const size = int( textureSize( matricesTexture, int( 0 ) ).x ).toVar(); const j = int( int( instanceIndex ).mul( int( 4 ) ) ).toVar(); - const x = int( j.remainder( size ) ).toVar(); + const x = int( j.mod( size ) ).toVar(); const y = int( j.div( size ) ).toVar(); const v1 = vec4( matricesTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); const v2 = vec4( matricesTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); @@ -28,7 +28,7 @@ export const getBoneMatrix = (boneTexture) => Fn((i) => { const size = int( textureSize( boneTexture, int( 0 ) ).x ).toVar(); const j = int( bonesPerInstance.mul( int( instanceIndex ) ).add( int( i ) ).mul( int( 4 ) ) ).toVar(); - const x = int( j.remainder( size ) ).toVar(); + const x = int( j.mod( size ) ).toVar(); const y = int( j.div( size ) ).toVar(); const v1 = vec4( boneTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); const v2 = vec4( boneTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); From 0f4bb0cf56551c3f329f4b87cd675dfa72d4f0ed Mon Sep 17 00:00:00 2001 From: Ian Date: Sun, 22 Jun 2025 21:59:20 -0400 Subject: [PATCH 17/22] use prototype pattern for extended properties --- src/core/InstancedEntity.ts | 2 +- ...ancedMesh2.ts => InstancedMesh2.common.ts} | 45 +---------- src/core/InstancedMesh2.webgl.ts | 55 +++++++++++++ ...cedMeshGPU.ts => InstancedMesh2.webgpu.ts} | 77 +++++++------------ src/core/InstancedMeshBVH.ts | 2 +- src/core/feature/Capacity.ts | 4 +- src/core/feature/FrustumCulling.ts | 2 +- src/core/feature/Instances.ts | 4 +- src/core/feature/LOD.ts | 4 +- src/core/feature/Morph.ts | 4 +- src/core/feature/Raycasting.ts | 4 +- src/core/feature/Skeleton.ts | 4 +- src/core/feature/Uniforms.ts | 4 +- src/index.ts | 2 +- src/utils/CreateFrom.ts | 2 +- src/utils/SortingUtils.ts | 2 +- 16 files changed, 102 insertions(+), 115 deletions(-) rename src/core/{InstancedMesh2.ts => InstancedMesh2.common.ts} (93%) create mode 100644 src/core/InstancedMesh2.webgl.ts rename src/core/{InstancedMeshGPU.ts => InstancedMesh2.webgpu.ts} (51%) diff --git a/src/core/InstancedEntity.ts b/src/core/InstancedEntity.ts index b7f92cd..a29059e 100644 --- a/src/core/InstancedEntity.ts +++ b/src/core/InstancedEntity.ts @@ -1,5 +1,5 @@ import { Color, ColorRepresentation, Euler, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from 'three'; -import { InstancedMesh2 } from './InstancedMesh2.js'; +import { InstancedMesh2 } from './InstancedMesh2.common.js'; import { UniformValue, UniformValueObj } from './utils/SquareDataTexture.js'; // TODO add other object3D methods diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.common.ts similarity index 93% rename from src/core/InstancedMesh2.ts rename to src/core/InstancedMesh2.common.ts index cc080c0..8c602da 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.common.ts @@ -1,5 +1,5 @@ import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; -import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; + import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; import { InstancedEntity } from './InstancedEntity.js'; @@ -391,49 +391,6 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { - if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); - - shader.instancing = false; - - shader.defines ??= {}; - shader.defines['USE_INSTANCING_INDIRECT'] = ''; - - shader.uniforms.matricesTexture = { value: this.matricesTexture }; - - if (this.uniformsTexture) { - shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; - const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); - shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); - shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); - } - - if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { - shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; - shader.uniforms.colorsTexture = { value: this.colorsTexture }; - shader.vertexShader = shader.vertexShader.replace('', ''); - - if (shader.vertexColors) { - shader.defines['USE_VERTEX_COLOR'] = ''; - } - - if (this._useOpacity) { - shader.defines['USE_COLOR_ALPHA'] = ''; - } else { - shader.defines['USE_COLOR'] = ''; - } - } - - if (this.boneTexture) { - shader.defines['USE_SKINNING'] = ''; - shader.defines['USE_INSTANCING_SKINNING'] = ''; - shader.uniforms.bindMatrix = { value: this.bindMatrix }; - shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; - shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; - shader.uniforms.boneTexture = { value: this.boneTexture }; - } - }; - protected patchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = material; this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak diff --git a/src/core/InstancedMesh2.webgl.ts b/src/core/InstancedMesh2.webgl.ts new file mode 100644 index 0000000..d42099a --- /dev/null +++ b/src/core/InstancedMesh2.webgl.ts @@ -0,0 +1,55 @@ +import { WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; + +import { InstancedMesh2 } from '@three.ez/main'; + + +/** + * @internal + * Enhances the InstancedMesh2 prototype with WebGL methods. + */ +export function extendInstancedMesh2PrototypeWebGL(): void { + + InstancedMesh2.prototype.onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { + if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); + + shader.instancing = false; + + shader.defines ??= {}; + shader.defines['USE_INSTANCING_INDIRECT'] = ''; + + shader.uniforms.matricesTexture = { value: this.matricesTexture }; + + if (this.uniformsTexture) { + shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; + const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); + shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); + shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); + } + + if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { + shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; + shader.uniforms.colorsTexture = { value: this.colorsTexture }; + shader.vertexShader = shader.vertexShader.replace('', ''); + + if (shader.vertexColors) { + shader.defines['USE_VERTEX_COLOR'] = ''; + } + + if (this._useOpacity) { + shader.defines['USE_COLOR_ALPHA'] = ''; + } else { + shader.defines['USE_COLOR'] = ''; + } + } + + if (this.boneTexture) { + shader.defines['USE_SKINNING'] = ''; + shader.defines['USE_INSTANCING_SKINNING'] = ''; + shader.uniforms.bindMatrix = { value: this.bindMatrix }; + shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; + shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; + shader.uniforms.boneTexture = { value: this.boneTexture }; + } + }; + +} \ No newline at end of file diff --git a/src/core/InstancedMeshGPU.ts b/src/core/InstancedMesh2.webgpu.ts similarity index 51% rename from src/core/InstancedMeshGPU.ts rename to src/core/InstancedMesh2.webgpu.ts index bae4050..8fed9b2 100644 --- a/src/core/InstancedMeshGPU.ts +++ b/src/core/InstancedMesh2.webgpu.ts @@ -1,7 +1,8 @@ -import { BufferGeometry, ColorManagement, DataTexture, Material, Object3DEventMap } from 'three'; +import { ColorManagement } from 'three'; import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; + import { getBoneMatrix, getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; -import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.common.js'; import { uniform } from 'three/tsl'; import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; @@ -11,7 +12,6 @@ import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; * Parameters for configuring an `InstancedMeshGPU` instance. */ export interface InstancedMeshGPUParams extends Omit { - capacity?: number; createEntities?: boolean; @@ -21,56 +21,31 @@ export interface InstancedMeshGPUParams extends Omit extends InstancedMesh2 { - - public override readonly type = 'InstancedMeshGPU' as any; - - public readonly isInstancedMeshGPU = true; - - public override matricesTexture: SquareDataTextureGPU; - - public override colorsTexture: SquareDataTextureGPU = null; - - public override boneTexture: SquareDataTextureGPU = null; - - public override uniformsTexture: SquareDataTextureGPU = null; - - override _renderer: WebGPURenderer | any = null; - - override _material: MeshBasicNodeMaterial = null; - override _currentMaterial: MeshBasicNodeMaterial = null; +export function extendInstancedMesh2PrototypeWebGL(): void { - override instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); - - private _adapter: GPUAdapter; - private _device: GPUDevice; - private _context: any; + InstancedMesh2.prototype.type = 'InstancedMeshGPU'; + InstancedMesh2.prototype.isInstancedMeshGPU = true; + + InstancedMesh2.prototype.matricesTexture = null; // SquareDataTextureGPU + InstancedMesh2.prototype.colorsTexture = null; //SquareDataTextureGPU + InstancedMesh2.prototype.boneTexture = null; // SquareDataTextureGPU; + InstancedMesh2.prototype.uniformsTexture = null; //SquareDataTextureGPU - constructor(geometry: TGeometry, material: MeshBasicNodeMaterial, params: InstancedMeshGPUParams = {}, LOD?: InstancedMeshGPU) { - if (!geometry) throw new Error('"geometry" is mandatory.'); - if (!material) throw new Error('"material" is mandatory.'); + InstancedMesh2.prototype._renderer = null; //WebGPURenderer | any + InstancedMesh2.prototype.instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); - this._currentMaterial = material; - this._material = material; - super(geometry, null, null, LOD); - this.init(); - } + InstancedMesh2.prototype._material = null; //MeshBasicNodeMaterial + InstancedMesh2.prototype._currentMaterial = null; //MeshBasicNodeMaterial - public async init(): Promise { - this._adapter = await navigator.gpu.requestAdapter(); - this._device = await this._adapter.requestDevice(); - this._context = this._renderer.domElement.getContext('webgpu'); + InstancedMesh2.prototype.init = async function(): Promise { + // this._adapter = await navigator.gpu.requestAdapter(); + // this._device = await this._adapter.requestDevice(); + // this._context = this._renderer.domElement.getContext('webgpu'); this.initMatricesTexture(); this.initColorsTexture(); // Ensure textures are updated before first render @@ -78,7 +53,7 @@ export class InstancedMeshGPU< this.colorsTexture?.update(this._renderer); } - protected override initMatricesTexture(): void { + InstancedMesh2.prototype.initMatricesTexture = function(): void { if (!this._parentLOD) { this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); } @@ -88,7 +63,7 @@ export class InstancedMeshGPU< } } - protected override initColorsTexture(): void { + InstancedMesh2.prototype.initColorsTexture = function(): void { if (!this._parentLOD) { this.colorsTexture = new SquareDataTextureGPU(Float32Array, 4, 1, this._capacity); this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; @@ -101,7 +76,7 @@ export class InstancedMeshGPU< } } - protected initBoneTexture(): void { + InstancedMesh2.prototype.initBoneTexture = function(): void { if (!this._parentLOD) { this.boneTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); this.boneTexture.colorSpace = ColorManagement.workingColorSpace; @@ -115,7 +90,7 @@ export class InstancedMeshGPU< } // Ensure textures are updated before each render - public override onBeforeRender(renderer, scene, camera, geometry, material, group): void { + InstancedMesh2.prototype.onBeforeRender = function(renderer, scene, camera, geometry, material, group): void { super.onBeforeRender(renderer, scene, camera, geometry, material, group); this.matricesTexture.update(renderer); this.colorsTexture?.update(renderer); diff --git a/src/core/InstancedMeshBVH.ts b/src/core/InstancedMeshBVH.ts index 4fe614f..3a7ca9e 100644 --- a/src/core/InstancedMeshBVH.ts +++ b/src/core/InstancedMeshBVH.ts @@ -1,7 +1,7 @@ import { box3ToArray, BVH, BVHNode, HybridBuilder, onFrustumIntersectionCallback, onFrustumIntersectionLODCallback, onIntersectionCallback, onIntersectionRayCallback, vec3ToArray, WebGLCoordinateSystem } from 'bvh.js'; import { Box3, Matrix4, Raycaster, Sphere, Vector3 } from 'three'; import { LODLevel } from './feature/LOD.js'; -import { InstancedMesh2 } from './InstancedMesh2.js'; +import { InstancedMesh2 } from './InstancedMesh2.common.js'; // TODO getBoxFromSphere updated if change geometry (and create accessor) // TODO accurateCulling in bvh.js? diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index 1700526..5316e7f 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -1,9 +1,9 @@ import { DataTexture, FloatType, RedFormat, TypedArray } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; // TODO: add optimize method to reduce buffer size and remove instances objects -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Resizes internal buffers to accommodate the specified capacity. diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index ed992d8..dd0a982 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -22,7 +22,7 @@ export type CustomSortCallback = (list: InstancedRenderItem[]) => void; */ export type OnFrustumEnterCallback = (index: number, camera: Camera, cameraLOD?: Camera, LODindex?: number) => boolean; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Performs frustum culling and manages LOD visibility. diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index d0e998b..10f20e1 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -1,5 +1,5 @@ import { InstancedEntity } from '../InstancedEntity.js'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; // TODO: optimize method to fill 'holes'. @@ -12,7 +12,7 @@ export type Entity = InstancedEntity & T; */ export type UpdateEntityCallback = (obj: Entity, index: number) => void; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Updates instances by applying a callback function to each instance. It calls `updateMatrix` for each instance. diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index eec1789..2fdf534 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -1,5 +1,5 @@ import { BufferGeometry, Material, ShaderMaterial } from 'three'; -import { InstancedMesh2, InstancedMesh2Params } from '../InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from '../InstancedMesh2.common.js'; // TODO check squaured distance in comments and code @@ -56,7 +56,7 @@ export interface LODLevel { object: InstancedMesh2; } -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Retrieves the index of the LOD level for a given distance. diff --git a/src/core/feature/Morph.ts b/src/core/feature/Morph.ts index 5950d7a..63b36d5 100644 --- a/src/core/feature/Morph.ts +++ b/src/core/feature/Morph.ts @@ -1,7 +1,7 @@ import { DataTexture, FloatType, Mesh, RedFormat } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Gets the morph target data for a specific instance. diff --git a/src/core/feature/Raycasting.ts b/src/core/feature/Raycasting.ts index bc4168f..25491e8 100644 --- a/src/core/feature/Raycasting.ts +++ b/src/core/feature/Raycasting.ts @@ -1,7 +1,7 @@ import { Intersection, Matrix4, Mesh, Ray, Raycaster, Sphere, Vector3 } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** @internal */ raycastInstances(raycaster: Raycaster, result: Intersection[]): void; /** @internal */ checkObjectIntersection(raycaster: Raycaster, objectIndex: number, result: Intersection[]): void; diff --git a/src/core/feature/Skeleton.ts b/src/core/feature/Skeleton.ts index d357242..03e70f3 100644 --- a/src/core/feature/Skeleton.ts +++ b/src/core/feature/Skeleton.ts @@ -1,8 +1,8 @@ import { Matrix4, Skeleton } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { SquareDataTexture } from '../utils/SquareDataTexture.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Initialize the skeleton of the instances. diff --git a/src/core/feature/Uniforms.ts b/src/core/feature/Uniforms.ts index d9da9aa..95cae4e 100644 --- a/src/core/feature/Uniforms.ts +++ b/src/core/feature/Uniforms.ts @@ -1,4 +1,4 @@ -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { ChannelSize, SquareDataTexture, UniformMap, UniformMapType, UniformType, UniformValue, UniformValueObj } from '../utils/SquareDataTexture.js'; type UniformSchema = { [x: string]: UniformType }; @@ -11,7 +11,7 @@ type UniformSchemaResult = { fetchInFragmentShader: boolean; }; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Retrieves a uniform value for a specific instance. diff --git a/src/index.ts b/src/index.ts index 7327037..24d7c4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export * from './core/InstancedEntity.js'; -export * from './core/InstancedMesh2.js'; +export * from './core/InstancedMesh2.common.js'; export * from './core/InstancedMeshBVH.js'; export * from './core/feature/Capacity.js'; diff --git a/src/utils/CreateFrom.ts b/src/utils/CreateFrom.ts index cf7391f..5ea22f1 100644 --- a/src/utils/CreateFrom.ts +++ b/src/utils/CreateFrom.ts @@ -1,5 +1,5 @@ import { InstancedBufferAttribute, InstancedMesh, Mesh, SkinnedMesh } from 'three'; -import { InstancedMesh2, InstancedMesh2Params } from '../core/InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from '../core/InstancedMesh2.common.js'; /** * Create an `InstancedMesh2` instance from an existing `Mesh` or `InstancedMesh`. diff --git a/src/utils/SortingUtils.ts b/src/utils/SortingUtils.ts index 07e5bfd..81c0030 100644 --- a/src/utils/SortingUtils.ts +++ b/src/utils/SortingUtils.ts @@ -1,6 +1,6 @@ import { Material } from 'three'; import { radixSort, RadixSortOptions } from 'three/addons/utils/SortUtils.js'; -import { InstancedMesh2 } from '../core/InstancedMesh2.js'; +import { InstancedMesh2 } from '../core/InstancedMesh2.common.js'; import { InstancedRenderItem } from '../core/utils/InstancedRenderList.js'; type radixSortCallback = (list: InstancedRenderItem[]) => void; From 3ca84b1056bd0ef30e9319138246e58a6db0fe7b Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 23 Jun 2025 12:45:24 -0400 Subject: [PATCH 18/22] lint errors --- src/core/InstancedMesh2.common.ts | 3 ++- src/core/InstancedMesh2.webgpu.ts | 7 ++----- src/core/feature/FrustumCulling.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/InstancedMesh2.common.ts b/src/core/InstancedMesh2.common.ts index 8c602da..800d155 100644 --- a/src/core/InstancedMesh2.common.ts +++ b/src/core/InstancedMesh2.common.ts @@ -6,6 +6,7 @@ import { InstancedEntity } from './InstancedEntity.js'; import { BVHParams, InstancedMeshBVH } from './InstancedMeshBVH.js'; import { GLInstancedBufferAttribute } from './utils/GLInstancedBufferAttribute.js'; import { SquareDataTexture } from './utils/SquareDataTexture.js'; +import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; // TODO: Add check to not update partial texture if needsuupdate already true // TODO: if bvh present, can override? @@ -396,7 +397,7 @@ export class InstancedMesh2< this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak this._onBeforeCompileBase = material.onBeforeCompile; material.customProgramCacheKey = this._customProgramCacheKey; - material.onBeforeCompile = this._onBeforeCompile; + // material.onBeforeCompile = this._onBeforeCompile; const propertiesBase = renderer.properties; diff --git a/src/core/InstancedMesh2.webgpu.ts b/src/core/InstancedMesh2.webgpu.ts index 8fed9b2..c97a976 100644 --- a/src/core/InstancedMesh2.webgpu.ts +++ b/src/core/InstancedMesh2.webgpu.ts @@ -24,9 +24,9 @@ export interface InstancedMeshGPUParams extends Omit { - // this._adapter = await navigator.gpu.requestAdapter(); - // this._device = await this._adapter.requestDevice(); - // this._context = this._renderer.domElement.getContext('webgpu'); this.initMatricesTexture(); this.initColorsTexture(); // Ensure textures are updated before first render diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index dd0a982..bad628d 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -1,7 +1,7 @@ import { BVHNode } from 'bvh.js'; import { Camera, Frustum, Material, Matrix4, Sphere, Vector3 } from 'three'; import { sortOpaque, sortTransparent } from '../../utils/SortingUtils.js'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { InstancedRenderItem, InstancedRenderList } from '../utils/InstancedRenderList.js'; import { LODRenderList } from './LOD.js'; From 55438e468c25491ad3ecddbd94f44b2715a8a759 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 25 Jun 2025 12:12:32 -0400 Subject: [PATCH 19/22] fix compiler errors for undeclared properties --- src/core/InstancedMesh2.common.ts | 42 ++++++++++++----------------- src/core/InstancedMesh2.webgl.ts | 45 +++++++++++++++++++++++++++---- src/core/InstancedMesh2.webgpu.ts | 30 ++++++++++----------- 3 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/core/InstancedMesh2.common.ts b/src/core/InstancedMesh2.common.ts index 800d155..7473a03 100644 --- a/src/core/InstancedMesh2.common.ts +++ b/src/core/InstancedMesh2.common.ts @@ -64,7 +64,7 @@ export class InstancedMesh2< /** * @defaultValue `InstancedMesh2` */ - public override readonly type = 'InstancedMesh2'; + public override type = 'InstancedMesh2'; /** * Indicates if this is an `InstancedMesh2`. */ @@ -81,11 +81,11 @@ export class InstancedMesh2< /** * Texture storing matrices for instances. */ - public matricesTexture: SquareDataTexture; + public matricesTexture: SquareDataTexture; // initialized in renderer-specific prototype /** * Texture storing colors for instances. */ - public colorsTexture: SquareDataTexture = null; + public colorsTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * Texture storing morph target influences for instances. */ @@ -93,11 +93,11 @@ export class InstancedMesh2< /** * Texture storing bones for instances. */ - public boneTexture: SquareDataTexture = null; + public boneTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * Texture storing custom uniforms per instance. */ - public uniformsTexture: SquareDataTexture = null; + public uniformsTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * This bounding box encloses all instances, which can be calculated with `computeBoundingBox` method. * Bounding box isn't computed by default. It needs to be explicitly computed, otherwise it's `null`. @@ -161,7 +161,7 @@ export class InstancedMesh2< * Callback function called if an instance is inside the frustum. */ public onFrustumEnter: OnFrustumEnterCallback = null; - /** @internal */ _renderer: WebGLRenderer = null; + /** @internal */ _renderer: WebGLRenderer = null; // initialized in renderer-specific prototype /** @internal */ _instancesCount = 0; /** @internal */ _instancesArrayCount = 0; /** @internal */ _perObjectFrustumCulled = true; @@ -183,9 +183,14 @@ export class InstancedMesh2< protected _createEntities: boolean; // HACK TO MAKE IT WORK WITHOUT UPDATE CORE - /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering - /** @internal */ instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception - /** @internal */ instanceColor = null; // must be null to avoid exception + /** @internal */ isInstancedMesh = true; + /** @internal */ instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // overridden in renderer-specific prototype if needed + /** @internal */ instanceColor = null; + init: () => void; + initMatricesTexture: () => void; + initColorsTexture: () => void; + initBoneTexture: () => void; + onBeforeCompile: (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void; /** * The capacity of the instance buffers. @@ -256,8 +261,10 @@ export class InstancedMesh2< this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2); this._createEntities = createEntities; + // Only initialize common attributes here. this.initIndexAttribute(); - this.initMatricesTexture(); + + // Renderer-specific initialization (matricesTexture, colorsTexture, etc.) is done in prototype extension. } public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { @@ -346,21 +353,6 @@ export class InstancedMesh2< this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); } - protected initMatricesTexture(): void { - if (!this._parentLOD) { - this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); - } - } - - protected initColorsTexture(): void { - if (!this._parentLOD) { - this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); - this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; - this.colorsTexture._data.fill(1); - this.materialsNeedsUpdate(); - } - } - protected materialsNeedsUpdate(): void { if ((this.material as Material).isMaterial) { (this.material as Material).needsUpdate = true; diff --git a/src/core/InstancedMesh2.webgl.ts b/src/core/InstancedMesh2.webgl.ts index d42099a..958d323 100644 --- a/src/core/InstancedMesh2.webgl.ts +++ b/src/core/InstancedMesh2.webgl.ts @@ -1,6 +1,7 @@ -import { WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; +import { ColorManagement, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; -import { InstancedMesh2 } from '@three.ez/main'; +import { InstancedMesh2 } from '../core/InstancedMesh2.common.js'; +import { SquareDataTexture } from './utils/SquareDataTexture.js'; /** @@ -8,8 +9,43 @@ import { InstancedMesh2 } from '@three.ez/main'; * Enhances the InstancedMesh2 prototype with WebGL methods. */ export function extendInstancedMesh2PrototypeWebGL(): void { - - InstancedMesh2.prototype.onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { + // WebGL-specific member initialization + InstancedMesh2.prototype.matricesTexture = null; + InstancedMesh2.prototype.colorsTexture = null; + InstancedMesh2.prototype.boneTexture = null; + InstancedMesh2.prototype.uniformsTexture = null; + InstancedMesh2.prototype._renderer = null; + // instanceMatrix is already initialized in .common, but can be overridden if needed + + InstancedMesh2.prototype.init = function(): void { + this.initMatricesTexture(); + this.initColorsTexture(); + // Ensure textures are updated before first render + this.matricesTexture.update(this._renderer); + this.colorsTexture?.update(this._renderer); + }; + + InstancedMesh2.prototype.initMatricesTexture = function(): void { + if (!this._parentLOD) { + // Only initialize if not already set + if (!this.matricesTexture) { + this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); + } + } + }; + + InstancedMesh2.prototype.initColorsTexture = function(): void { + if (!this._parentLOD) { + if (!this.colorsTexture) { + this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); + this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; + this.colorsTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + } + }; + + InstancedMesh2.prototype.onBeforeCompile = function(shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); shader.instancing = false; @@ -51,5 +87,4 @@ export function extendInstancedMesh2PrototypeWebGL(): void { shader.uniforms.boneTexture = { value: this.boneTexture }; } }; - } \ No newline at end of file diff --git a/src/core/InstancedMesh2.webgpu.ts b/src/core/InstancedMesh2.webgpu.ts index c97a976..aa209f2 100644 --- a/src/core/InstancedMesh2.webgpu.ts +++ b/src/core/InstancedMesh2.webgpu.ts @@ -29,26 +29,23 @@ export interface InstancedMeshGPUParams extends Omit { + this._currentMaterial = new MeshBasicNodeMaterial(); this.initMatricesTexture(); this.initColorsTexture(); // Ensure textures are updated before first render this.matricesTexture.update(this._renderer); this.colorsTexture?.update(this._renderer); - } + }; InstancedMesh2.prototype.initMatricesTexture = function(): void { if (!this._parentLOD) { @@ -58,7 +55,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { if (this._currentMaterial) { (this._currentMaterial as any).matricesTexture = getInstancedMatrix(uniform(this.matricesTexture)); } - } + }; InstancedMesh2.prototype.initColorsTexture = function(): void { if (!this._parentLOD) { @@ -71,7 +68,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { if (this._currentMaterial) { (this._currentMaterial as any).colorNode = getColorTexture(uniform(this.colorsTexture)); } - } + }; InstancedMesh2.prototype.initBoneTexture = function(): void { if (!this._parentLOD) { @@ -84,14 +81,15 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { if (this._currentMaterial) { (this._currentMaterial as any).boneMatrixNode = getBoneMatrix(uniform(this.boneTexture)); } - } + }; // Ensure textures are updated before each render InstancedMesh2.prototype.onBeforeRender = function(renderer, scene, camera, geometry, material, group): void { - super.onBeforeRender(renderer, scene, camera, geometry, material, group); + this.onBeforeRender(renderer, scene, camera, geometry, material, group); this.matricesTexture.update(renderer); this.colorsTexture?.update(renderer); - } + }; + } const _defaultCapacity = 1000; From d07c6943b1847ecf1d4597cdf6aa58ebbe5894e3 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 25 Jun 2025 14:07:15 -0400 Subject: [PATCH 20/22] use positionNode for initMatricesTexture --- src/core/InstancedMesh2.webgpu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/InstancedMesh2.webgpu.ts b/src/core/InstancedMesh2.webgpu.ts index aa209f2..3661c6d 100644 --- a/src/core/InstancedMesh2.webgpu.ts +++ b/src/core/InstancedMesh2.webgpu.ts @@ -53,7 +53,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { } // Set the node for instance matrix in the material if (this._currentMaterial) { - (this._currentMaterial as any).matricesTexture = getInstancedMatrix(uniform(this.matricesTexture)); + (this._currentMaterial as any).positionNode = getInstancedMatrix(uniform(this.matricesTexture)); } }; From 86fdb5c95303e14b5f606c7cddbcc9f2725fea66 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 25 Jun 2025 14:09:45 -0400 Subject: [PATCH 21/22] appropriate method names --- src/core/InstancedMesh2.common.ts | 8 +++++--- src/core/InstancedMesh2.webgpu.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/InstancedMesh2.common.ts b/src/core/InstancedMesh2.common.ts index 7473a03..7f245b9 100644 --- a/src/core/InstancedMesh2.common.ts +++ b/src/core/InstancedMesh2.common.ts @@ -186,11 +186,13 @@ export class InstancedMesh2< /** @internal */ isInstancedMesh = true; /** @internal */ instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // overridden in renderer-specific prototype if needed /** @internal */ instanceColor = null; + + // Todo: WebGPU-specific methods init: () => void; - initMatricesTexture: () => void; - initColorsTexture: () => void; - initBoneTexture: () => void; onBeforeCompile: (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void; + initPositionsNode: () => void; + initColorsNode: () => void; + initBonesNode: () => void; /** * The capacity of the instance buffers. diff --git a/src/core/InstancedMesh2.webgpu.ts b/src/core/InstancedMesh2.webgpu.ts index 3661c6d..ef7e659 100644 --- a/src/core/InstancedMesh2.webgpu.ts +++ b/src/core/InstancedMesh2.webgpu.ts @@ -47,7 +47,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { this.colorsTexture?.update(this._renderer); }; - InstancedMesh2.prototype.initMatricesTexture = function(): void { + InstancedMesh2.prototype.initPositionsNode = function(): void { if (!this._parentLOD) { this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); } @@ -57,7 +57,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { } }; - InstancedMesh2.prototype.initColorsTexture = function(): void { + InstancedMesh2.prototype.initColorsNode = function(): void { if (!this._parentLOD) { this.colorsTexture = new SquareDataTextureGPU(Float32Array, 4, 1, this._capacity); this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; @@ -70,7 +70,7 @@ export function extendInstancedMesh2PrototypeWebGPU(): void { } }; - InstancedMesh2.prototype.initBoneTexture = function(): void { + InstancedMesh2.prototype.initBonesNode = function(): void { if (!this._parentLOD) { this.boneTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); this.boneTexture.colorSpace = ColorManagement.workingColorSpace; From abf7b851ed6707648f92cd1884ceb28e01ec519b Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 25 Jun 2025 22:27:51 -0400 Subject: [PATCH 22/22] declare undeclared methods used in base class --- src/core/InstancedMesh2.common.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/InstancedMesh2.common.ts b/src/core/InstancedMesh2.common.ts index 7f245b9..24f5cd9 100644 --- a/src/core/InstancedMesh2.common.ts +++ b/src/core/InstancedMesh2.common.ts @@ -629,6 +629,15 @@ export class InstancedMesh2< this.colorsTexture.enqueueUpdate(id); } + /** + * Initializes the colors texture for the instances. + * This method should be implemented in the renderer-specific prototype. + * @throws Error if not implemented. + */ + initColorsTexture() { + throw new Error('Method not implemented.'); + } + /** * Gets the color of a specific instance. * @param id The index of the instance.