UNPKG

@three.ez/batched-mesh-extensions

Version:
220 lines 9.48 kB
import { Frustum, Matrix4, Sphere, Vector3 } from 'three'; import { MultiDrawRenderList } from '../MultiDrawRenderList.js'; import { sortOpaque, sortTransparent } from '../../utils/SortingUtils.js'; const _frustum = new Frustum(); const _renderList = new MultiDrawRenderList(); const _projScreenMatrix = new Matrix4(); const _invMatrixWorld = new Matrix4(); const _forward = new Vector3(); const _cameraPos = new Vector3(); const _cameraLODPos = new Vector3(); const _position = new Vector3(); const _sphere = new Sphere(); // TODO move it export function onBeforeRender(renderer, scene, camera, geometry, material, group) { // TODO check if nothing changed this.frustumCulling(camera); this.uniformsTexture?.update(renderer); } export function frustumCulling(camera, cameraLOD = camera) { if (!this._visibilityChanged && !this.perObjectFrustumCulled && !this.sortObjects) { return; } this._indirectTexture.needsUpdate = true; this._visibilityChanged = false; const sortObjects = this.sortObjects; const perObjectFrustumCulled = this.perObjectFrustumCulled; if (!perObjectFrustumCulled && !sortObjects) { this.updateIndexArray(); return; } _invMatrixWorld.copy(this.matrixWorld).invert(); _cameraPos.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(_invMatrixWorld); _cameraLODPos.setFromMatrixPosition(cameraLOD.matrixWorld).applyMatrix4(_invMatrixWorld); _forward.set(0, 0, -1).transformDirection(camera.matrixWorld).transformDirection(_invMatrixWorld); if (!perObjectFrustumCulled) { this.updateRenderList(); } else { _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(this.matrixWorld); if (this.bvh) this.BVHCulling(camera, cameraLOD); else this.linearCulling(camera, cameraLOD); } if (sortObjects) { const index = this.geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const indirectArray = this._indirectTexture.image.data; const customSort = this.customSort; if (customSort === null) { _renderList.array.sort(!this.material.transparent ? sortOpaque : sortTransparent); } else { customSort(_renderList.array); // TODO fix and remove second useless parameter... make a PR on main repo } const list = _renderList.array; const count = list.length; for (let i = 0; i < count; i++) { const item = list[i]; multiDrawStarts[i] = item.start * bytesPerElement; // TODO multiply bytesPerElement in the renderList? multiDrawCounts[i] = item.count; indirectArray[i] = item.index; } _renderList.reset(); } } export function updateIndexArray() { if (!this._visibilityChanged) return; const index = this.geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const instanceInfo = this._instanceInfo; const geometryInfoList = this._geometryInfo; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const indirectArray = this._indirectTexture.image.data; let count = 0; for (let i = 0, l = instanceInfo.length; i < l; i++) { const instance = instanceInfo[i]; if (instance.visible && instance.active) { const geometryId = instance.geometryIndex; const geometryInfo = geometryInfoList[geometryId]; multiDrawStarts[count] = geometryInfo.start * bytesPerElement; multiDrawCounts[count] = geometryInfo.count; indirectArray[count] = i; count++; } } this._multiDrawCount = count; } export function updateRenderList() { const instanceInfo = this._instanceInfo; const geometryInfoList = this._geometryInfo; for (let i = 0, l = instanceInfo.length; i < l; i++) { const instance = instanceInfo[i]; if (instance.visible && instance.active) { const geometryId = instance.geometryIndex; const geometryInfo = geometryInfoList[geometryId]; const depth = this.getPositionAt(i).sub(_cameraPos).dot(_forward); // getPosition instead of _sphere.center _renderList.push(i, depth, geometryInfo.start, geometryInfo.count); } } this._multiDrawCount = _renderList.array.length; } export function BVHCulling(camera, cameraLOD) { const index = this.geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const instanceInfo = this._instanceInfo; const geometryInfoList = this._geometryInfo; const sortObjects = this.sortObjects; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const indirectArray = this._indirectTexture.image.data; const onFrustumEnter = this.onFrustumEnter; let instancesCount = 0; this.bvh.frustumCulling(_projScreenMatrix, (node) => { const index = node.object; const instance = instanceInfo[index]; // we don't check if active because we remove inactive instances from BVH if (!instance.visible) return; const geometryId = instance.geometryIndex; const geometryInfo = geometryInfoList[geometryId]; const LOD = geometryInfo.LOD; let start; let count; if (LOD) { const distance = this.getPositionAt(index).distanceToSquared(_cameraLODPos); const LODIndex = this.getLODIndex(LOD, distance); if (onFrustumEnter && !onFrustumEnter(index, camera, cameraLOD, LODIndex)) return; start = LOD[LODIndex].start; count = LOD[LODIndex].count; } else { if (onFrustumEnter && !onFrustumEnter(index, camera)) return; start = geometryInfo.start; count = geometryInfo.count; } // TODO don't reuse getPositionAt for sort // TODO LOD optimized if bvh and sort? if (sortObjects) { const depth = this.getPositionAt(index).sub(_cameraPos).dot(_forward); _renderList.push(index, depth, start, count); } else { multiDrawStarts[instancesCount] = start * bytesPerElement; multiDrawCounts[instancesCount] = count; indirectArray[instancesCount] = index; instancesCount++; } }); this._multiDrawCount = sortObjects ? _renderList.array.length : instancesCount; } export function linearCulling(camera, cameraLOD) { const index = this.geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const instanceInfo = this._instanceInfo; const geometryInfoList = this._geometryInfo; const sortObjects = this.sortObjects; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const indirectArray = this._indirectTexture.image.data; const onFrustumEnter = this.onFrustumEnter; let instancesCount = 0; _frustum.setFromProjectionMatrix(_projScreenMatrix); for (let i = 0, l = instanceInfo.length; i < l; i++) { const instance = instanceInfo[i]; if (!instance.visible || !instance.active) continue; const geometryId = instance.geometryIndex; const geometryInfo = geometryInfoList[geometryId]; const LOD = geometryInfo.LOD; let start; let count; const bSphere = geometryInfo.boundingSphere; const radius = bSphere.radius; const center = bSphere.center; const geometryCentered = center.x === 0 && center.y === 0 && center.z === 0; // TODO add to geometryInfo? if (geometryCentered) { const maxScale = this.getPositionAndMaxScaleOnAxisAt(i, _sphere.center); _sphere.radius = radius * maxScale; } else { this.applyMatrixAtToSphere(i, _sphere, center, radius); } if (!_frustum.intersectsSphere(_sphere)) continue; if (LOD) { const distance = _sphere.center.distanceToSquared(_cameraLODPos); const LODIndex = this.getLODIndex(LOD, distance); if (onFrustumEnter && !onFrustumEnter(i, camera, cameraLOD, LODIndex)) continue; start = LOD[LODIndex].start; count = LOD[LODIndex].count; } else { if (onFrustumEnter && !onFrustumEnter(i, camera)) continue; start = geometryInfo.start; count = geometryInfo.count; } // TODO LOD optimized if sort? if (sortObjects) { const depth = _position.subVectors(_sphere.center, _cameraPos).dot(_forward); _renderList.push(i, depth, start, count); } else { multiDrawStarts[instancesCount] = start * bytesPerElement; multiDrawCounts[instancesCount] = count; indirectArray[instancesCount] = i; instancesCount++; } } this._multiDrawCount = sortObjects ? _renderList.array.length : instancesCount; } //# sourceMappingURL=FrustumCulling.js.map