From 9735db6e2d19c58cfe4c0fcc3c6339d30f424ede Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Sun, 3 Nov 2024 22:53:27 +0100 Subject: [PATCH 1/6] addShadowLOD --- examples/LOD.ts | 8 +-- examples/trees.ts | 2 +- src/core/InstancedMesh2.ts | 1 + src/core/feature/FrustumCulling.ts | 35 +++++++------ src/core/feature/LOD.ts | 81 +++++++++++++++++++++++------- src/core/feature/Raycasting.ts | 4 +- 6 files changed, 89 insertions(+), 42 deletions(-) diff --git a/examples/LOD.ts b/examples/LOD.ts index da04370..6e00fd5 100644 --- a/examples/LOD.ts +++ b/examples/LOD.ts @@ -16,10 +16,10 @@ const scene = new Scene(); const instancedMeshLOD = new InstancedMesh2(main.renderer, 1000000, new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' })); -instancedMeshLOD.addLevel(new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' })); -instancedMeshLOD.addLevel(new SphereGeometry(5, 20, 10), new MeshLambertMaterial({ color: 'yellow' }), 50); -instancedMeshLOD.addLevel(new SphereGeometry(5, 10, 5), new MeshLambertMaterial({ color: 'orange' }), 500); -instancedMeshLOD.addLevel(new SphereGeometry(5, 5, 3), new MeshLambertMaterial({ color: 'red' }), 1000); +instancedMeshLOD.addLOD(new SphereGeometry(5, 30, 15), new MeshLambertMaterial({ color: 'green' })); +instancedMeshLOD.addLOD(new SphereGeometry(5, 20, 10), new MeshLambertMaterial({ color: 'yellow' }), 50); +instancedMeshLOD.addLOD(new SphereGeometry(5, 10, 5), new MeshLambertMaterial({ color: 'orange' }), 500); +instancedMeshLOD.addLOD(new SphereGeometry(5, 5, 3), new MeshLambertMaterial({ color: 'red' }), 1000); instancedMeshLOD.levels[0].object.geometry.computeBoundingSphere(); // improve diff --git a/examples/trees.ts b/examples/trees.ts index 833aead..8a3e877 100644 --- a/examples/trees.ts +++ b/examples/trees.ts @@ -24,7 +24,7 @@ const trees = new InstancedMesh2(main.renderer, count, treeGLTF.geometry, treeGL trees.castShadow = true; trees.cursor = 'pointer'; -trees.addLevel(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100); +trees.addLOD(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100); trees.levels[0].object.geometry.computeBoundingSphere(); // improve trees.levels[1].object.castShadow = true; diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index 8eff476..cdfebf3 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -54,6 +54,7 @@ export class InstancedMesh2< public raycastOnlyFrustum = false; public visibilityArray: boolean[]; public levels: LODLevel[] = null; + public shadowLevels: LODLevel[] = null; /** @internal */ public _indexArray: Uint16Array | Uint32Array; /** @internal */ public _matrixArray: Float32Array; /** @internal */ public _colorArray: Float32Array = null; diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 9eb355a..5cb2ba3 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -2,10 +2,11 @@ import { BVHNode } from "bvh.js"; import { Camera, Frustum, Material, Matrix4, Sphere, Vector3, WebGLRenderer } from "three"; import { getMaxScaleOnAxisAt, getPositionAt } from "../../utils/MatrixUtils.js"; import { sortOpaque, sortTransparent } from "../../utils/SortingUtils.js"; -import { InstancedMesh2 } from "../InstancedMesh2.js"; +import { InstancedMesh2, LODLevel } from "../InstancedMesh2.js"; import { InstancedRenderList } from "../utils/InstancedRenderList.js"; // TODO: fix shadowMap LOD sorting objects? +// TODO SOON: set all visibility to false before compputing... if shadowLOD has different geometries is important. declare module '../InstancedMesh2.js' { interface InstancedMesh2 { @@ -17,9 +18,9 @@ declare module '../InstancedMesh2.js' { /** @internal */ BVHCulling(): void; /** @internal */ linearCulling(): void; - /** @internal */ frustumCullingLOD(camera: Camera, cameraLOD?: Camera): void; - /** @internal */ BVHCullingLOD(sortObjects: boolean): void; - /** @internal */ linearCullingLOD(sortObjects: boolean): void; + /** @internal */ frustumCullingLOD(levels: LODLevel[], camera: Camera, cameraLOD: Camera): void; + /** @internal */ BVHCullingLOD(levels: LODLevel[], sortObjects: boolean): void; + /** @internal */ linearCullingLOD(levels: LODLevel[], sortObjects: boolean): void; } } @@ -34,9 +35,12 @@ const _position = new Vector3(); const _sphere = new Sphere(); InstancedMesh2.prototype.performFrustumCulling = function (renderer: WebGLRenderer, camera: Camera, cameraLOD = camera): void { - if (this.levels?.length > 0) this.frustumCullingLOD(camera, cameraLOD); + const isShadowRendering = camera !== cameraLOD; + const levels = !isShadowRendering ? this.levels : (this.shadowLevels ?? this.levels); + + if (levels?.length > 0) this.frustumCullingLOD(levels, camera, cameraLOD); else if (!this._LOD) this.frustumCulling(camera); - + this.instanceIndex.update(renderer, this._count); } @@ -182,8 +186,7 @@ InstancedMesh2.prototype.linearCulling = function (): void { this._count = count; } -InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD = camera): void { - const levels = this.levels; +InstancedMesh2.prototype.frustumCullingLOD = function (levels: LODLevel[], camera: Camera, cameraLOD: Camera): void { const count = this._countIndexes; const isShadowRendering = camera !== cameraLOD; const sortObjects = !isShadowRendering && this._sortObjects; @@ -201,8 +204,8 @@ InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD _cameraPos.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(_invMatrixWorld); _cameraLODPos.setFromMatrixPosition(cameraLOD.matrixWorld).applyMatrix4(_invMatrixWorld); - if (this.bvh) this.BVHCullingLOD(sortObjects); - else this.linearCullingLOD(sortObjects); + if (this.bvh) this.BVHCullingLOD(levels, sortObjects); + else this.linearCullingLOD(levels, sortObjects); if (sortObjects) { const customSort = this.customSort; @@ -239,7 +242,7 @@ InstancedMesh2.prototype.frustumCullingLOD = function (camera: Camera, cameraLOD } } -InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void { +InstancedMesh2.prototype.BVHCullingLOD = function (levels: LODLevel[], sortObjects: boolean): void { const matrixArray = this._matrixArray; const instancesCount = this.instancesCount; const count = this._countIndexes; // reuse the same? also uintarray? @@ -258,13 +261,13 @@ InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void { } else { - this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, this.levels, (node: BVHNode<{}, number>, level: number) => { + this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, levels, (node: BVHNode<{}, number>, level: number) => { const index = node.object; if (index < instancesCount && visibilityArray[index]) { if (level === null) { const distance = getPositionAt(index, matrixArray, _position).distanceToSquared(_cameraLODPos); // distance can be get by BVH - level = this.getObjectLODIndexForDistance(distance); + level = this.getObjectLODIndexForDistance(levels, distance); } indexes[level][count[level]++] = index; @@ -274,9 +277,9 @@ InstancedMesh2.prototype.BVHCullingLOD = function (sortObjects: boolean): void { } } -InstancedMesh2.prototype.linearCullingLOD = function (sortObjects: boolean): void { +InstancedMesh2.prototype.linearCullingLOD = function (levels: LODLevel[], sortObjects: boolean): void { const matrixArray = this._matrixArray; - const bSphere = this.levels[this.levels.length - 1].object.geometry.boundingSphere; // TODO check se esiste? + const bSphere = this.geometry.boundingSphere; // TODO check se esiste? const radius = bSphere.radius; const center = bSphere.center; const instancesCount = this.instancesCount; @@ -305,7 +308,7 @@ InstancedMesh2.prototype.linearCullingLOD = function (sortObjects: boolean): voi if (sortObjects) { _renderList.push(distance, i); } else { - const levelIndex = this.getObjectLODIndexForDistance(distance); + const levelIndex = this.getObjectLODIndexForDistance(levels, distance); indexes[levelIndex][count[levelIndex]++] = i; } } diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index de9ff5d..0f06a70 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -1,53 +1,96 @@ import { BufferGeometry, Material } from "three"; -import { InstancedMesh2 } from "../InstancedMesh2.js"; +import { InstancedMesh2, LODLevel } from "../InstancedMesh2.js"; declare module '../InstancedMesh2.js' { interface InstancedMesh2 { - addLevel(geometry: BufferGeometry, material: Material, distance?: number, hysteresis?: number): this - getObjectLODIndexForDistance(distance: number): number + getObjectLODIndexForDistance(levels: LODLevel[], distance: number): number; + setFirstLODDistance(distance?: number, hysteresis?: number): this; + addLOD(geometry: BufferGeometry, material: Material, distance?: number, hysteresis?: number): this; + addShadowLOD(geometry: BufferGeometry, material: Material, distance?: number, hysteresis?: number): this; } } +InstancedMesh2.prototype.getObjectLODIndexForDistance = function (levels: LODLevel[], distance: number): number { + for (let i = levels.length - 1; i > 0; i--) { + const level = levels[i]; + const levelDistance = level.distance - (level.distance * level.hysteresis); + if (distance >= levelDistance) return i; + } + + return 0; +} -InstancedMesh2.prototype.addLevel = function(geometry: BufferGeometry, material: Material, distance = 0, hysteresis = 0): InstancedMesh2 { +InstancedMesh2.prototype.setFirstLODDistance = function (distance = 0, hysteresis = 0): InstancedMesh2 { if (this._LOD) { console.error("Cannot create LOD for this InstancedMesh2."); return; } if (!this.levels) { - this.levels = [{ distance: 0, hysteresis, object: this }]; + this.levels = [{ distance, hysteresis, object: this }]; this._countIndexes = [0]; this._indexes = [this._indexArray]; } + return this; +} + +InstancedMesh2.prototype.addLOD = function (geometry: BufferGeometry, material: Material, distance = 0, hysteresis = 0): InstancedMesh2 { + if (this._LOD) { + console.error("Cannot create LOD for this InstancedMesh2."); + return; + } + + if (!this.levels && distance === 0) { + console.error("Cannot set distance to 0 for the first LOD. Use 'setFirstLODDistance' before use 'addLOD'."); + return; + } else { + this.setFirstLODDistance(0, hysteresis); + } + const levels = this.levels; - // TODO fix renderer param - const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); - distance = Math.abs(distance ** 2); // to avoid to use Math.sqrt every time + const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param let index; + distance = distance ** 2; // to avoid to use Math.sqrt every time for (index = 0; index < levels.length; index++) { if (distance < levels[index].distance) break; } levels.splice(index, 0, { distance, hysteresis, object }); - this._countIndexes.push(0); this._indexes.splice(index, 0, object._indexArray); - this.add(object); // TODO handle render order + this.add(object); // TODO handle render order? return this; } -InstancedMesh2.prototype.getObjectLODIndexForDistance = function(distance: number): number { - const levels = this.levels; +InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, material: Material, distance = 0, hysteresis = 0): InstancedMesh2 { + // if (this._LOD) { + // console.error("Cannot create LOD for this InstancedMesh2."); + // return; + // } - for (let i = levels.length - 1; i > 0; i--) { - const level = levels[i]; - const levelDistance = level.distance - (level.distance * level.hysteresis); - if (distance >= levelDistance) return i; - } + // if (!this.levels && distance === 0) { + // console.error("Cannot set distance to 0 for the first LOD. Use 'setFirstLODDistance' before use 'addLOD'."); + // return; + // } else { + // this.setFirstLODDistance(0, hysteresis); + // } - return 0; -} \ No newline at end of file + // const levels = this.levels; + // const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param + // let index; + // distance = distance ** 2; // to avoid to use Math.sqrt every time + + // for (index = 0; index < levels.length; index++) { + // if (distance < levels[index].distance) break; + // } + + // levels.splice(index, 0, { distance, hysteresis, object }); + // this._countIndexes.push(0); + // this._indexes.splice(index, 0, object._indexArray); + + // this.add(object); // TODO handle render order? + return this; +} diff --git a/src/core/feature/Raycasting.ts b/src/core/feature/Raycasting.ts index 79ec697..96cd015 100644 --- a/src/core/feature/Raycasting.ts +++ b/src/core/feature/Raycasting.ts @@ -16,7 +16,7 @@ const _worldScale = new Vector3(); const _invMatrixWorld = new Matrix4(); const _sphere = new Sphere(); -InstancedMesh2.prototype.raycast = function(raycaster: Raycaster, result: Intersection[]): void { +InstancedMesh2.prototype.raycast = function (raycaster: Raycaster, result: Intersection[]): void { if (this.material === undefined) return; const raycastFrustum = this.raycastOnlyFrustum && this._perObjectFrustumCulled && !this.bvh; @@ -64,7 +64,7 @@ InstancedMesh2.prototype.raycast = function(raycaster: Raycaster, result: Inters raycaster.far = originalFar; } -InstancedMesh2.prototype.checkObjectIntersection = function(raycaster: Raycaster, objectIndex: number, result: Intersection[]): void { +InstancedMesh2.prototype.checkObjectIntersection = function (raycaster: Raycaster, objectIndex: number, result: Intersection[]): void { if (objectIndex > this.instancesCount || !this.getVisibilityAt(objectIndex)) return; this.getMatrixAt(objectIndex, _mesh.matrixWorld); From 058932f83a790f2ed5dbff88b632dd1ef1c534ec Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 4 Nov 2024 20:24:25 +0100 Subject: [PATCH 2/6] wip --- examples/trees.ts | 18 ++++---- src/core/InstancedMesh2.ts | 15 ++----- src/core/feature/FrustumCulling.ts | 51 +++++++++++---------- src/core/feature/LOD.ts | 72 ++++++++++++++++-------------- 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/examples/trees.ts b/examples/trees.ts index 8a3e877..9c65d39 100644 --- a/examples/trees.ts +++ b/examples/trees.ts @@ -6,8 +6,8 @@ import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { Sky } from 'three/examples/jsm/objects/Sky.js'; import { InstancedMesh2 } from '../src/index.js'; -const count = 1000000; -const terrainSize = 20000; +const count = 10; +const terrainSize = 100; const main = new Main(); // init renderer and other stuff main.renderer.toneMapping = ACESFilmicToneMapping; @@ -25,8 +25,8 @@ trees.castShadow = true; trees.cursor = 'pointer'; trees.addLOD(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100); -trees.levels[0].object.geometry.computeBoundingSphere(); // improve -trees.levels[1].object.castShadow = true; +// trees.addShadowLOD(new BoxGeometry(100, 1000, 100), undefined, 100); +trees.levels.render.levels[1].object.castShadow = true; // TODO create utility methods trees.createInstances((obj, index) => { obj.position.setX(Math.random() * terrainSize - terrainSize / 2).setZ(Math.random() * terrainSize - terrainSize / 2); @@ -62,11 +62,11 @@ scene.on('animate', (e) => scene.fog.color.setHSL(0, 0, sun.y)); const dirLight = new DirectionalLight(); dirLight.castShadow = true; -dirLight.shadow.mapSize.set(1024, 1024); -dirLight.shadow.camera.left = -300; -dirLight.shadow.camera.right = 300; -dirLight.shadow.camera.top = 300; -dirLight.shadow.camera.bottom = -300; +dirLight.shadow.mapSize.set(2048, 2048); +dirLight.shadow.camera.left = -200; +dirLight.shadow.camera.right = 200; +dirLight.shadow.camera.top = 200; +dirLight.shadow.camera.bottom = -200; dirLight.shadow.camera.far = 2000; dirLight.shadow.camera.updateProjectionMatrix(); diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index cdfebf3..423d7ab 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -4,6 +4,7 @@ import { GLInstancedBufferAttribute } from "./utils/GLInstancedBufferAttribute.j import { InstancedEntity, UniformValue, UniformValueNoNumber } from "./InstancedEntity.js"; import { InstancedMeshBVH } from "./InstancedMeshBVH.js"; import { InstancedRenderItem } from "./utils/InstancedRenderList.js"; +import { LODInfo } from "./feature/LOD.js"; // TODO: Add expand and count/maxCount when create? // TODO: partial texture update @@ -21,12 +22,6 @@ export type Entity = InstancedEntity & T; export type UpdateEntityCallback = (obj: Entity, index: number) => void; export type CustomSortCallback = (list: InstancedRenderItem[]) => void; -export interface LODLevel { - distance: number; - hysteresis: number; - object: InstancedMesh2; -} - export interface BVHParams { margin?: number; highPrecision?: boolean; @@ -53,8 +48,7 @@ export class InstancedMesh2< public customSort: CustomSortCallback = null; public raycastOnlyFrustum = false; public visibilityArray: boolean[]; - public levels: LODLevel[] = null; - public shadowLevels: LODLevel[] = null; + public levels: LODInfo = null; // TODO rename /** @internal */ public _indexArray: Uint16Array | Uint32Array; /** @internal */ public _matrixArray: Float32Array; /** @internal */ public _colorArray: Float32Array = null; @@ -62,15 +56,12 @@ export class InstancedMesh2< /** @internal */ public _perObjectFrustumCulled = true; /** @internal */ public _sortObjects = false; /** @internal */ public _maxCount: number; + /** @internal */ public _visibilityChanged = false; protected _material: TMaterial; protected _uniformsSetCallback = new Map void>(); protected _LOD: InstancedMesh2; protected readonly _instancesUseEuler: boolean; protected readonly _instance: InstancedEntity; - /** @internal */ public _visibilityChanged = false; - - protected _indexes: (Uint16Array | Uint32Array)[] = null; // TODO can be also uin16 - protected _countIndexes: number[] = null; public override customDepthMaterial = new MeshDepthMaterial({ depthPacking: RGBADepthPacking }); public override customDistanceMaterial = new MeshDistanceMaterial(); diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 5cb2ba3..6a0936e 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -2,11 +2,11 @@ import { BVHNode } from "bvh.js"; import { Camera, Frustum, Material, Matrix4, Sphere, Vector3, WebGLRenderer } from "three"; import { getMaxScaleOnAxisAt, getPositionAt } from "../../utils/MatrixUtils.js"; import { sortOpaque, sortTransparent } from "../../utils/SortingUtils.js"; -import { InstancedMesh2, LODLevel } from "../InstancedMesh2.js"; +import { InstancedMesh2 } from "../InstancedMesh2.js"; import { InstancedRenderList } from "../utils/InstancedRenderList.js"; +import { LODRenderList } from "./LOD.js"; // TODO: fix shadowMap LOD sorting objects? -// TODO SOON: set all visibility to false before compputing... if shadowLOD has different geometries is important. declare module '../InstancedMesh2.js' { interface InstancedMesh2 { @@ -18,9 +18,9 @@ declare module '../InstancedMesh2.js' { /** @internal */ BVHCulling(): void; /** @internal */ linearCulling(): void; - /** @internal */ frustumCullingLOD(levels: LODLevel[], camera: Camera, cameraLOD: Camera): void; - /** @internal */ BVHCullingLOD(levels: LODLevel[], sortObjects: boolean): void; - /** @internal */ linearCullingLOD(levels: LODLevel[], sortObjects: boolean): void; + /** @internal */ frustumCullingLOD(renderList: LODRenderList, objects: InstancedMesh2[], camera: Camera, cameraLOD: Camera): void; + /** @internal */ BVHCullingLOD(renderList: LODRenderList, sortObjects: boolean): void; + /** @internal */ linearCullingLOD(renderList: LODRenderList, sortObjects: boolean): void; } } @@ -35,10 +35,11 @@ const _position = new Vector3(); const _sphere = new Sphere(); InstancedMesh2.prototype.performFrustumCulling = function (renderer: WebGLRenderer, camera: Camera, cameraLOD = camera): void { + const info = this.levels; const isShadowRendering = camera !== cameraLOD; - const levels = !isShadowRendering ? this.levels : (this.shadowLevels ?? this.levels); + const renderList = !isShadowRendering ? info?.render : (info?.shadowRender ?? info?.render); - if (levels?.length > 0) this.frustumCullingLOD(levels, camera, cameraLOD); + if (renderList?.levels.length > 0) this.frustumCullingLOD(renderList, info.objects, camera, cameraLOD); else if (!this._LOD) this.frustumCulling(camera); this.instanceIndex.update(renderer, this._count); @@ -186,10 +187,10 @@ InstancedMesh2.prototype.linearCulling = function (): void { this._count = count; } -InstancedMesh2.prototype.frustumCullingLOD = function (levels: LODLevel[], camera: Camera, cameraLOD: Camera): void { - const count = this._countIndexes; +InstancedMesh2.prototype.frustumCullingLOD = function (renderList: LODRenderList, objects: InstancedMesh2[], camera: Camera, cameraLOD: Camera): void { + const { count, levels } = renderList; const isShadowRendering = camera !== cameraLOD; - const sortObjects = !isShadowRendering && this._sortObjects; + const sortObjects = !isShadowRendering && this._sortObjects; // sort is disabled when render shadows for (let i = 0; i < levels.length; i++) { count[i] = 0; @@ -199,18 +200,25 @@ InstancedMesh2.prototype.frustumCullingLOD = function (levels: LODLevel[], camer } } + for (let i = 0; i < objects.length; i++) { + const object = objects[i]; + + if (object === this) object._count = 0; + else object.visible = false; + } + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(this.matrixWorld); _invMatrixWorld.copy(this.matrixWorld).invert(); _cameraPos.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(_invMatrixWorld); _cameraLODPos.setFromMatrixPosition(cameraLOD.matrixWorld).applyMatrix4(_invMatrixWorld); - if (this.bvh) this.BVHCullingLOD(levels, sortObjects); - else this.linearCullingLOD(levels, sortObjects); + if (this.bvh) this.BVHCullingLOD(renderList, sortObjects); + else this.linearCullingLOD(renderList, sortObjects); if (sortObjects) { const customSort = this.customSort; - const list = _renderList.list; - const indexes = this._indexes; + const list = _renderList.list; // TODO better name... + const indexes = renderList.indexes; let levelIndex = 0; let levelDistance = levels[1].distance; @@ -229,7 +237,7 @@ InstancedMesh2.prototype.frustumCullingLOD = function (levels: LODLevel[], camer // for fixa } - indexes[levelIndex][count[levelIndex]++] = item.index; // TODO COUNT ARRAY QUI NON SERVE + indexes[levelIndex][count[levelIndex]++] = item.index; } _renderList.reset(); @@ -237,16 +245,15 @@ InstancedMesh2.prototype.frustumCullingLOD = function (levels: LODLevel[], camer for (let i = 0; i < levels.length; i++) { const object = levels[i].object; - object.visible = i === 0 || count[i] > 0; + object.visible = object === this || count[i] > 0; object._count = count[i]; } } -InstancedMesh2.prototype.BVHCullingLOD = function (levels: LODLevel[], sortObjects: boolean): void { +InstancedMesh2.prototype.BVHCullingLOD = function (renderList: LODRenderList, sortObjects: boolean): void { + const { count, indexes, levels } = renderList; const matrixArray = this._matrixArray; const instancesCount = this.instancesCount; - const count = this._countIndexes; // reuse the same? also uintarray? - const indexes = this._indexes; const visibilityArray = this.visibilityArray; if (sortObjects) { // todo refactor @@ -277,7 +284,8 @@ InstancedMesh2.prototype.BVHCullingLOD = function (levels: LODLevel[], sortObjec } } -InstancedMesh2.prototype.linearCullingLOD = function (levels: LODLevel[], sortObjects: boolean): void { +InstancedMesh2.prototype.linearCullingLOD = function (renderList: LODRenderList, sortObjects: boolean): void { + const { count, indexes, levels } = renderList; const matrixArray = this._matrixArray; const bSphere = this.geometry.boundingSphere; // TODO check se esiste? const radius = bSphere.radius; @@ -287,9 +295,6 @@ InstancedMesh2.prototype.linearCullingLOD = function (levels: LODLevel[], sortOb _frustum.setFromProjectionMatrix(_projScreenMatrix); - const count = this._countIndexes; - const indexes = this._indexes; - for (let i = 0; i < instancesCount; i++) { if (!this.visibilityArray[i]) continue; // opt anche nell'altra classe diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index 0f06a70..afc36f3 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -1,5 +1,5 @@ import { BufferGeometry, Material } from "three"; -import { InstancedMesh2, LODLevel } from "../InstancedMesh2.js"; +import { InstancedMesh2 } from "../InstancedMesh2.js"; declare module '../InstancedMesh2.js' { interface InstancedMesh2 { @@ -10,6 +10,24 @@ declare module '../InstancedMesh2.js' { } } +export interface LODInfo { + render: LODRenderList; + shadowRender: LODRenderList; + objects: InstancedMesh2[]; +} + +export interface LODRenderList { + levels: LODLevel[]; + indexes: (Uint16Array | Uint32Array)[]; + count: number[]; +} + +export interface LODLevel { + distance: number; + hysteresis: number; + object: InstancedMesh2; +} + InstancedMesh2.prototype.getObjectLODIndexForDistance = function (levels: LODLevel[], distance: number): number { for (let i = levels.length - 1; i > 0; i--) { const level = levels[i]; @@ -27,9 +45,15 @@ InstancedMesh2.prototype.setFirstLODDistance = function (distance = 0, hysteresi } if (!this.levels) { - this.levels = [{ distance, hysteresis, object: this }]; - this._countIndexes = [0]; - this._indexes = [this._indexArray]; + this.levels = { render: null, shadowRender: null, objects: [this] }; + } + + if (!this.levels.render) { + this.levels.render = { + levels: [{ distance, hysteresis, object: this }], + indexes: [this._indexArray], + count: [0] + }; } return this; @@ -41,14 +65,17 @@ InstancedMesh2.prototype.addLOD = function (geometry: BufferGeometry, material: return; } - if (!this.levels && distance === 0) { + if (!this.levels?.render && distance === 0) { console.error("Cannot set distance to 0 for the first LOD. Use 'setFirstLODDistance' before use 'addLOD'."); return; } else { this.setFirstLODDistance(0, hysteresis); } - const levels = this.levels; + const info = this.levels; + const objectsList = info.objects; + const renderList = info.render; + const levels = renderList.levels; const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param let index; distance = distance ** 2; // to avoid to use Math.sqrt every time @@ -58,39 +85,16 @@ InstancedMesh2.prototype.addLOD = function (geometry: BufferGeometry, material: } levels.splice(index, 0, { distance, hysteresis, object }); - this._countIndexes.push(0); - this._indexes.splice(index, 0, object._indexArray); + renderList.count.push(0); + renderList.indexes.splice(index, 0, object._indexArray); + + if (objectsList.indexOf(object) === -1) objectsList.push(object); this.add(object); // TODO handle render order? return this; } InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, material: Material, distance = 0, hysteresis = 0): InstancedMesh2 { - // if (this._LOD) { - // console.error("Cannot create LOD for this InstancedMesh2."); - // return; - // } - - // if (!this.levels && distance === 0) { - // console.error("Cannot set distance to 0 for the first LOD. Use 'setFirstLODDistance' before use 'addLOD'."); - // return; - // } else { - // this.setFirstLODDistance(0, hysteresis); - // } - - // const levels = this.levels; - // const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param - // let index; - // distance = distance ** 2; // to avoid to use Math.sqrt every time - - // for (index = 0; index < levels.length; index++) { - // if (distance < levels[index].distance) break; - // } - - // levels.splice(index, 0, { distance, hysteresis, object }); - // this._countIndexes.push(0); - // this._indexes.splice(index, 0, object._indexArray); - - // this.add(object); // TODO handle render order? + console.error("work in progress"); return this; } From a1dc345c21778315cdcbb7f8197892b2dbf2247c Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 4 Nov 2024 21:00:39 +0100 Subject: [PATCH 3/6] wip --- examples/trees.ts | 11 ++++++----- src/core/feature/LOD.ts | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/examples/trees.ts b/examples/trees.ts index 9c65d39..d857577 100644 --- a/examples/trees.ts +++ b/examples/trees.ts @@ -1,13 +1,13 @@ import { Asset, Main, PerspectiveCameraAuto } from '@three.ez/main'; -import { ACESFilmicToneMapping, AmbientLight, BoxGeometry, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshLambertMaterial, MeshStandardMaterial, PCFSoftShadowMap, PlaneGeometry, Scene, Vector3 } from 'three'; +import { ACESFilmicToneMapping, AmbientLight, BoxGeometry, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshBasicMaterial, MeshLambertMaterial, MeshStandardMaterial, PCFSoftShadowMap, PlaneGeometry, Scene, Vector3 } from 'three'; import { MapControls } from 'three/examples/jsm/controls/MapControls.js'; import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { Sky } from 'three/examples/jsm/objects/Sky.js'; import { InstancedMesh2 } from '../src/index.js'; -const count = 10; -const terrainSize = 100; +const count = 10000; +const terrainSize = 1000; const main = new Main(); // init renderer and other stuff main.renderer.toneMapping = ACESFilmicToneMapping; @@ -25,8 +25,9 @@ trees.castShadow = true; trees.cursor = 'pointer'; trees.addLOD(new BoxGeometry(100, 1000, 100), new MeshLambertMaterial(), 100); -// trees.addShadowLOD(new BoxGeometry(100, 1000, 100), undefined, 100); -trees.levels.render.levels[1].object.castShadow = true; // TODO create utility methods +// trees.addShadowLOD(trees.geometry, new MeshBasicMaterial()); +trees.addShadowLOD(new BoxGeometry(100, 1000, 100), new MeshBasicMaterial(), 100); +trees.levels.shadowRender.levels[0].object.castShadow = true; // TODO create utility methods trees.createInstances((obj, index) => { obj.position.setX(Math.random() * terrainSize - terrainSize / 2).setZ(Math.random() * terrainSize - terrainSize / 2); diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index afc36f3..f81c0ab 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -88,13 +88,45 @@ InstancedMesh2.prototype.addLOD = function (geometry: BufferGeometry, material: renderList.count.push(0); renderList.indexes.splice(index, 0, object._indexArray); - if (objectsList.indexOf(object) === -1) objectsList.push(object); + if (objectsList.indexOf(object) === -1) objectsList.push(object); this.add(object); // TODO handle render order? return this; } InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, material: Material, distance = 0, hysteresis = 0): InstancedMesh2 { - console.error("work in progress"); + if (this._LOD) { + console.error("Cannot create LOD for this InstancedMesh2."); + return; + } + + if (!this.levels) { + this.levels = { render: null, shadowRender: null, objects: [] }; + } + + const info = this.levels; + + if (!info.shadowRender) { + info.shadowRender = { levels: [], indexes: [], count: [] }; + } + + const objectsList = info.objects; + const renderList = info.shadowRender; + const levels = renderList.levels; + const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param + let index; + distance = distance ** 2; // to avoid to use Math.sqrt every time + + for (index = 0; index < levels.length; index++) { + if (distance < levels[index].distance) break; + } + + levels.splice(index, 0, { distance, hysteresis, object }); + renderList.count.push(0); + renderList.indexes.splice(index, 0, object._indexArray); + + if (objectsList.indexOf(object) === -1) objectsList.push(object); + + this.add(object); // TODO handle render order? return this; } From a421082881b14c5312b92535160b7a4ffec5aa88 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 4 Nov 2024 21:13:21 +0100 Subject: [PATCH 4/6] wip --- src/core/feature/LOD.ts | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index f81c0ab..de7e75c 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -7,6 +7,7 @@ declare module '../InstancedMesh2.js' { setFirstLODDistance(distance?: number, hysteresis?: number): this; addLOD(geometry: BufferGeometry, material: Material, distance?: number, hysteresis?: number): this; addShadowLOD(geometry: BufferGeometry, material: Material, distance?: number, hysteresis?: number): this; + /** @internal */ addLevel(renderList: LODRenderList, geometry: BufferGeometry, material: Material, distance: number, hysteresis: number): void; } } @@ -72,25 +73,7 @@ InstancedMesh2.prototype.addLOD = function (geometry: BufferGeometry, material: this.setFirstLODDistance(0, hysteresis); } - const info = this.levels; - const objectsList = info.objects; - const renderList = info.render; - const levels = renderList.levels; - const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param - let index; - distance = distance ** 2; // to avoid to use Math.sqrt every time - - for (index = 0; index < levels.length; index++) { - if (distance < levels[index].distance) break; - } - - levels.splice(index, 0, { distance, hysteresis, object }); - renderList.count.push(0); - renderList.indexes.splice(index, 0, object._indexArray); - - if (objectsList.indexOf(object) === -1) objectsList.push(object); - - this.add(object); // TODO handle render order? + this.addLevel(this.levels.render, geometry, material, distance, hysteresis); return this; } @@ -104,14 +87,16 @@ InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, mate this.levels = { render: null, shadowRender: null, objects: [] }; } - const info = this.levels; - - if (!info.shadowRender) { - info.shadowRender = { levels: [], indexes: [], count: [] }; + if (!this.levels.shadowRender) { + this.levels.shadowRender = { levels: [], indexes: [], count: [] }; } - const objectsList = info.objects; - const renderList = info.shadowRender; + this.addLevel(this.levels.shadowRender, geometry, material, distance, hysteresis); + return this; +} + +InstancedMesh2.prototype.addLevel = function (renderList: LODRenderList, geometry: BufferGeometry, material: Material, distance: number, hysteresis: number): void { + const objectsList = this.levels.objects; const levels = renderList.levels; const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param let index; @@ -128,5 +113,4 @@ InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, mate if (objectsList.indexOf(object) === -1) objectsList.push(object); this.add(object); // TODO handle render order? - return this; -} +} \ No newline at end of file From 950c9ac90abe99d0c26354dfdacac3ba0166d541 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 4 Nov 2024 22:23:45 +0100 Subject: [PATCH 5/6] wip --- src/core/feature/LOD.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index de7e75c..a6df416 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -98,10 +98,19 @@ InstancedMesh2.prototype.addShadowLOD = function (geometry: BufferGeometry, mate InstancedMesh2.prototype.addLevel = function (renderList: LODRenderList, geometry: BufferGeometry, material: Material, distance: number, hysteresis: number): void { const objectsList = this.levels.objects; const levels = renderList.levels; - const object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param let index; + let object: InstancedMesh2; distance = distance ** 2; // to avoid to use Math.sqrt every time + const objIndex = objectsList.indexOf(object) + if (objIndex === -1) { + object = new InstancedMesh2(undefined, this._maxCount, geometry, material, this); // TODO fix renderer param + objectsList.push(object); + this.add(object); // TODO handle render order? + } else { + object = objectsList[objIndex]; + } + for (index = 0; index < levels.length; index++) { if (distance < levels[index].distance) break; } @@ -109,8 +118,4 @@ InstancedMesh2.prototype.addLevel = function (renderList: LODRenderList, geometr levels.splice(index, 0, { distance, hysteresis, object }); renderList.count.push(0); renderList.indexes.splice(index, 0, object._indexArray); - - if (objectsList.indexOf(object) === -1) objectsList.push(object); - - this.add(object); // TODO handle render order? } \ No newline at end of file From 22eb250a36a0db74f95603c16103968396a41327 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 4 Nov 2024 22:29:33 +0100 Subject: [PATCH 6/6] wip --- src/core/feature/FrustumCulling.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 6a0936e..ac12e69 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -200,9 +200,7 @@ InstancedMesh2.prototype.frustumCullingLOD = function (renderList: LODRenderList } } - for (let i = 0; i < objects.length; i++) { - const object = objects[i]; - + for (const object of objects) { if (object === this) object._count = 0; else object.visible = false; }