|
@@ -0,0 +1,867 @@
|
|
|
+
|
|
|
+import * as THREE from "../../../../libs/three.js/build/three.module.js";
|
|
|
+
|
|
|
+import {SortWorker, createSortWorker} from './SortWorker.js'
|
|
|
+
|
|
|
+import npyjs from 'npyjs'
|
|
|
+import JSZip from 'jszip'
|
|
|
+import JSZipUtils from 'jszip-utils'
|
|
|
+
|
|
|
+//从 GaussianSplats3D 里摘过来,用于显示webcloud型的lod 3dgs
|
|
|
+
|
|
|
+
|
|
|
+let sortCount = 0
|
|
|
+
|
|
|
+const dummyGeometry = new THREE.BufferGeometry();
|
|
|
+const dummyMaterial = new THREE.MeshBasicMaterial();
|
|
|
+
|
|
|
+//只允许存在至多一个Splat
|
|
|
+
|
|
|
+export class SplatNPY extends THREE.Mesh {
|
|
|
+ constructor({adaptiveSize, splatEnableSwapOut,fileName}={}){
|
|
|
+ super(dummyGeometry, dummyMaterial)
|
|
|
+ this.fileName = fileName
|
|
|
+ this.loadedSplatCount = 0
|
|
|
+ this.adaptiveSize = adaptiveSize
|
|
|
+ this.geometry = this.buildGeometry()
|
|
|
+ this.material = this.buildMaterial()
|
|
|
+ this.frustumCulled = false //禁止视锥体剔除
|
|
|
+ this.focalAdjustment = 1
|
|
|
+ this.integerBasedSort = true
|
|
|
+ this.currentNodes = []
|
|
|
+ Potree.Utils.setObjectLayers(this, 'model');
|
|
|
+
|
|
|
+ this.pointclouds = []
|
|
|
+
|
|
|
+ const maxSplatCount = Potree.config.maxSplatCount
|
|
|
+ const PosArrayType = this.integerBasedSort ? Int32Array : Float32Array;
|
|
|
+
|
|
|
+
|
|
|
+ adaptiveSize && this.switchSizeType(true) //初始化后不支持更改
|
|
|
+
|
|
|
+ this.useSharedMemory = Potree.browser.urlHasValue('NoShareBuffer') ? false : !!self.crossOriginIsolated
|
|
|
+ if(!self.crossOriginIsolated){
|
|
|
+ alert('sharedMemory unsupported!')
|
|
|
+ }
|
|
|
+ document.title = this.useSharedMemory ? '3dgs-shareBuffer' : '3dgs-NoShareBuffer'
|
|
|
+ if(!this.useSharedMemory){
|
|
|
+ Potree.pointBudget = Potree.config.maxSplatCount = Potree.browser.isMobile() ? 1000000 : 2000000
|
|
|
+ }
|
|
|
+ if(this.useSharedMemory) splatEnableSwapOut = false //暂不支持,否则要创建太多内存
|
|
|
+
|
|
|
+ this.sortWorker = createSortWorker(this.useSharedMemory, maxSplatCount, true, false, adaptiveSize, splatEnableSwapOut)
|
|
|
+
|
|
|
+ this.sortWorker.onmessage = (e) => {
|
|
|
+ if (e.data.sortDone) {
|
|
|
+ this.sortRunning = false;
|
|
|
+ let sortWorkerDatas = this.sortWorkerDatas
|
|
|
+
|
|
|
+ if(!this.useSharedMemory){//恢复数据主权
|
|
|
+
|
|
|
+ sortWorkerDatas.sorted[0].centers = new Float32Array(e.data.sorted.centers);
|
|
|
+ sortWorkerDatas.sorted[0].covs1 = new Float32Array(e.data.sorted.covs1)
|
|
|
+ sortWorkerDatas.sorted[0].covs2 = new Float32Array(e.data.sorted.covs2)
|
|
|
+ sortWorkerDatas.sorted[0].colors = new Uint8Array(e.data.sorted.colors)
|
|
|
+
|
|
|
+ sortWorkerDatas.toSort.centersFloat = new Float32Array(e.data.toSort.centersFloat);
|
|
|
+ sortWorkerDatas.toSort.colors = new Uint8Array(e.data.toSort.colors);
|
|
|
+ sortWorkerDatas.toSort.covs = new Float32Array(e.data.toSort.covs)
|
|
|
+
|
|
|
+ if(this.integerBasedSort){
|
|
|
+ sortWorkerDatas.toSort.centersInt = new Int32Array(e.data.toSort.centersInt)
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ let {centers, colors, covs1, covs2, nodes} = sortWorkerDatas.sorted[e.data.sortedBufferIndex]
|
|
|
+ this.updateRenderIndexes( centers, colors, covs1, covs2, nodes, e.data.splatRenderCount);
|
|
|
+ //this.lastSortTime = e.data.sortTime;//sort耗时
|
|
|
+ this.sortPromiseResolver();
|
|
|
+ this.sortPromiseResolver = null;
|
|
|
+ viewer.dispatchEvent('content_changed')//this.forceRenderNextFrame();
|
|
|
+
|
|
|
+ sortCount++;
|
|
|
+
|
|
|
+ } else if (e.data.sortCanceled) {
|
|
|
+ this.sortRunning = false;
|
|
|
+ } else if (e.data.sortSetupPhase1Complete) {//sortWorker init 完毕
|
|
|
+ console.log('Sorting web worker WASM setup complete.');
|
|
|
+
|
|
|
+ const CENTERS_BYTES_PER_ENTRY = this.integerBasedSort ? (Potree.defines.gs3d.BytesPerInt * 4) : (Potree.defines.gs3d.BytesPerFloat * 4);
|
|
|
+
|
|
|
+
|
|
|
+ if(this.useSharedMemory){
|
|
|
+ this.sortWorkerDatas = {
|
|
|
+ sorted:[
|
|
|
+ {
|
|
|
+ centers: new Float32Array(e.data.wasmMemory, e.data.sortedCentersOffset, maxSplatCount * 3),
|
|
|
+ colors: new Uint8Array(e.data.wasmMemory, e.data.sortedColorsOffset, maxSplatCount * 4),
|
|
|
+ covs1: new Float32Array(e.data.wasmMemory, e.data.sortedCovs1Offset, maxSplatCount * 3),
|
|
|
+ covs2: new Float32Array(e.data.wasmMemory, e.data.sortedCovs2Offset, maxSplatCount * 3),
|
|
|
+ }
|
|
|
+
|
|
|
+ ],
|
|
|
+ toSort:{
|
|
|
+ centersFloat : new Float32Array(e.data.wasmMemory, e.data.centersFloatOffset, maxSplatCount * 3) ,
|
|
|
+ colors: new Uint8Array(e.data.wasmMemory, e.data.colorsOffset, maxSplatCount * 4),//所有类型参数均为:buffer, byteOffset, length, 第三个参数是item个数,而非字节数
|
|
|
+ covs: new Float32Array(e.data.wasmMemory, e.data.covsOffset, maxSplatCount * 6) ,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(this.integerBasedSort){
|
|
|
+ this.sortWorkerDatas.toSort.centersInt = new Int32Array(e.data.wasmMemory, e.data.centersIntOffset, maxSplatCount * 4)
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ this.sortWorkerDatas = {
|
|
|
+ sorted:[
|
|
|
+ {
|
|
|
+ centers: new Float32Array( maxSplatCount * 3),
|
|
|
+ colors: new Uint8Array( maxSplatCount * 4),
|
|
|
+ covs1: new Float32Array( maxSplatCount * 3),
|
|
|
+ covs2: new Float32Array( maxSplatCount * 3),
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ toSort:{
|
|
|
+ centersFloat : new Float32Array( maxSplatCount * 3) ,
|
|
|
+ colors: new Uint8Array( maxSplatCount * 4),//所有类型参数均为:buffer, byteOffset, length, 第三个参数是item个数,而非字节数
|
|
|
+ covs: new Float32Array( maxSplatCount * 6)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(this.integerBasedSort){
|
|
|
+ this.sortWorkerDatas.toSort.centersInt = new Int32Array( maxSplatCount * 4)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if(this.adaptiveSize){
|
|
|
+ this.sortWorkerDatas.sorted[0].nodes = new Int32Array(e.data.wasmMemory, e.data.sortedNodeOffset, maxSplatCount * 2)
|
|
|
+ this.sortWorkerDatas.toSort.nodes = new Int32Array(e.data.wasmMemory, e.data.nodeOffset, maxSplatCount * 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ if(this.splatEnableSwapOut){
|
|
|
+ this.sortWorkerDatas.sorted.push({
|
|
|
+ centers: new Float32Array(e.data.wasmMemory, e.data.sortedCentersOffset+e.data.memoryRequiredOut, maxSplatCount * 3),
|
|
|
+ colors: new Uint8Array(e.data.wasmMemory, e.data.sortedColorsOffset+e.data.memoryRequiredOut, maxSplatCount * 4),
|
|
|
+ covs1: new Float32Array(e.data.wasmMemory, e.data.sortedCovs1Offset+e.data.memoryRequiredOut, maxSplatCount * 3),
|
|
|
+ covs2: new Float32Array(e.data.wasmMemory, e.data.sortedCovs2Offset+e.data.memoryRequiredOut, maxSplatCount * 3),
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ //if gpuAcceleratedSort and dont shareMemory
|
|
|
+ //this.sortWorkerPrecomputedDistances = new PosArrayType(e.data.wasmMemory, e.data.precomputedDistancesOffset, maxSplatCount);
|
|
|
+ //this.sortWorkerTransforms = new Float32Array(e.data.wasmMemory, e.data.transformsOffset, Potree.defines.gs3d.MaxScenes * 16);
|
|
|
+
|
|
|
+ //this.sortWorker.maxSplatCount = maxSplatCount;//总点数
|
|
|
+
|
|
|
+ console.log('Sorting web worker ready.');
|
|
|
+ this.updateSort()
|
|
|
+ }else{
|
|
|
+ console.log(e.data.msg)
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.load()
|
|
|
+
|
|
|
+ viewer.addEventListener('camera_changed', e => {
|
|
|
+ var camera = e.viewport.camera
|
|
|
+ var pos = camera.position
|
|
|
+ if (e.viewport.name == 'MainView'){
|
|
|
+ this.updateSort()
|
|
|
+ if(e.changeInfo.projectionChanged){
|
|
|
+ this.updateMaterial()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ viewer.addEventListener('pointcloud_changed', e => {
|
|
|
+ this.updateSort()
|
|
|
+ })
|
|
|
+
|
|
|
+ this.updateMaterial()
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ buildGeometry(){
|
|
|
+ const baseGeometry = new THREE.BufferGeometry();
|
|
|
+ baseGeometry.setIndex([0, 1, 2, 0, 2, 3]);
|
|
|
+
|
|
|
+ // Vertices for the instanced quad
|
|
|
+ const positionsArray = new Float32Array(4 * 3);
|
|
|
+ const positions = new THREE.BufferAttribute(positionsArray, 3);
|
|
|
+ baseGeometry.setAttribute('position', positions);
|
|
|
+ positions.setXYZ(0, -1.0, -1.0, 0.0);
|
|
|
+ positions.setXYZ(1, -1.0, 1.0, 0.0);
|
|
|
+ positions.setXYZ(2, 1.0, 1.0, 0.0);
|
|
|
+ positions.setXYZ(3, 1.0, -1.0, 0.0);
|
|
|
+ positions.needsUpdate = true;
|
|
|
+
|
|
|
+ const geometry = new THREE.InstancedBufferGeometry().copy(baseGeometry);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /*
|
|
|
+ //给点加序号
|
|
|
+ const splatIndexArray = new Uint32Array(Potree.config.maxSplatCount);
|
|
|
+ const splatIndexes = new THREE.InstancedBufferAttribute(splatIndexArray, 1, false);
|
|
|
+ splatIndexes.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('splatIndex', splatIndexes);
|
|
|
+ for(let i=0;i<Potree.config.maxSplatCount;i++){
|
|
|
+ splatIndexArray[i] = i;
|
|
|
+ } */
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ const posArray = new Float32Array(Potree.config.maxSplatCount*3);
|
|
|
+ const centers = new THREE.InstancedBufferAttribute(posArray, 3, false);
|
|
|
+ centers.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('center', centers);
|
|
|
+
|
|
|
+ const colorArray = new Uint8Array(Potree.config.maxSplatCount*4);
|
|
|
+ const colors = new THREE.InstancedBufferAttribute(colorArray, 4, true);//第三个参数是normalized, 开启后 WebGL 会在内部将每个颜色分量从 [0, 255] 转换到 [0.0, 1.0] 范围
|
|
|
+ colors.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('color', colors);
|
|
|
+
|
|
|
+ const covArray1 = new Float32Array(Potree.config.maxSplatCount*3);
|
|
|
+ const covs1 = new THREE.InstancedBufferAttribute(covArray1, 3, false);
|
|
|
+ covs1.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('covRow1', covs1);
|
|
|
+ const covArray2 = new Float32Array(Potree.config.maxSplatCount*3);
|
|
|
+ const covs2 = new THREE.InstancedBufferAttribute(covArray2, 3, false);
|
|
|
+ covs2.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('covRow2', covs2);
|
|
|
+
|
|
|
+ geometry.instanceCount = 0;
|
|
|
+
|
|
|
+ if(this.adaptiveSize){
|
|
|
+ const nodeSthArr = new Int32Array(Potree.config.maxSplatCount*2);//node: vnStart level
|
|
|
+ const nodeSth = new THREE.InstancedBufferAttribute(nodeSthArr, 2, false);
|
|
|
+ nodeSth.setUsage(THREE.DynamicDrawUsage);
|
|
|
+ geometry.setAttribute('nodeLevelVNS', nodeSth);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ this.attrOrigin = {
|
|
|
+ center: posArray,
|
|
|
+ color: colorArray,
|
|
|
+ covRow1: covArray1,
|
|
|
+ covRow2: covArray2
|
|
|
+ }
|
|
|
+ return geometry;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ buildMaterial(){
|
|
|
+
|
|
|
+
|
|
|
+ const maxScreenSpaceSplatSize =/* 512// */2048 // 这啥
|
|
|
+
|
|
|
+ let vs = `
|
|
|
+ precision highp float;
|
|
|
+ #include <common>
|
|
|
+
|
|
|
+ attribute vec3 center;
|
|
|
+ attribute vec4 color;
|
|
|
+ attribute vec3 covRow1;
|
|
|
+ attribute vec3 covRow2;
|
|
|
+
|
|
|
+ //attribute uint splatIndex;
|
|
|
+ //uniform int splatCount;
|
|
|
+
|
|
|
+
|
|
|
+ uniform float uOctreeSize; //add
|
|
|
+ uniform vec2 focal;
|
|
|
+ uniform float orthoZoom;
|
|
|
+ uniform int orthographicMode;
|
|
|
+ uniform int pointCloudModeEnabled;
|
|
|
+ uniform float inverseFocalAdjustment;
|
|
|
+ uniform vec2 viewport;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ uniform float splatScale;
|
|
|
+
|
|
|
+ varying vec4 vColor;
|
|
|
+ varying vec2 vUv;
|
|
|
+
|
|
|
+ varying vec2 vPosition;
|
|
|
+
|
|
|
+ const float sqrt8 = sqrt(8.0);
|
|
|
+ const float minAlpha = 1.0 / 255.0;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ void main () {
|
|
|
+
|
|
|
+ mat4 transformModelViewMatrix = modelViewMatrix;
|
|
|
+
|
|
|
+ vec4 viewCenter = transformModelViewMatrix * vec4(center, 1.0);
|
|
|
+
|
|
|
+ vec4 clipCenter = projectionMatrix * viewCenter;
|
|
|
+
|
|
|
+ float clip = 1.2 * clipCenter.w;
|
|
|
+ if ( clipCenter.z < -clip || clipCenter.x < -clip || clipCenter.x > clip || clipCenter.y < -clip || clipCenter.y > clip) {
|
|
|
+ gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ vPosition = position.xy;
|
|
|
+ vColor = color;
|
|
|
+
|
|
|
+ //debug 测试sort是否正确. 由远及近由黑变白
|
|
|
+ //float r = float(splatIndex) / float(splatCount) ;
|
|
|
+ //vColor = vec4(r,r,r,1.0);
|
|
|
+
|
|
|
+ mat3 Vrk = mat3( // Construct the 3D covariance matrix
|
|
|
+ covRow1.x, covRow1.y, covRow1.z,
|
|
|
+ covRow1.y, covRow2.x, covRow2.y,
|
|
|
+ covRow1.z, covRow2.y, covRow2.z
|
|
|
+ );
|
|
|
+
|
|
|
+ //翻译结果:构建投影矩阵仿射近似的雅可比矩阵。它将被用来变换3D协方差矩阵,而不是直接使用实际的投影矩阵,因为使用实际投影矩阵进行变换需要包含非线性成分(透视除法),这会导致非高斯的结果。这里假设当前的投影为透视投影。
|
|
|
+
|
|
|
+ mat3 J;
|
|
|
+ if (orthographicMode == 1) {
|
|
|
+ J = transpose(mat3(orthoZoom, 0.0, 0.0,
|
|
|
+ 0.0, orthoZoom, 0.0,
|
|
|
+ 0.0, 0.0, 0.0));
|
|
|
+ } else {
|
|
|
+ float s = 1.0 / (viewCenter.z * viewCenter.z);
|
|
|
+ J = mat3(
|
|
|
+ focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s,
|
|
|
+ 0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s,
|
|
|
+ 0., 0., 0.
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Concatenate the projection approximation with the model-view transformation
|
|
|
+ mat3 W = transpose(mat3(transformModelViewMatrix));
|
|
|
+ mat3 T = W * J;
|
|
|
+
|
|
|
+ // Transform the 3D covariance matrix (Vrk) to compute the 2D covariance matrix
|
|
|
+ mat3 cov2Dm = transpose(T) * Vrk * T;
|
|
|
+
|
|
|
+
|
|
|
+ cov2Dm[0][0] += 0.3;
|
|
|
+ cov2Dm[1][1] += 0.3;
|
|
|
+ float compensation = 1.0;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ vColor.a *= compensation;
|
|
|
+
|
|
|
+ if (vColor.a < minAlpha) return;
|
|
|
+
|
|
|
+ vec3 cov2Dv = vec3(cov2Dm[0][0], cov2Dm[0][1], cov2Dm[1][1]);
|
|
|
+
|
|
|
+ vec3 ndcCenter = clipCenter.xyz / clipCenter.w;
|
|
|
+
|
|
|
+ float a = cov2Dv.x;
|
|
|
+ float d = cov2Dv.z;
|
|
|
+ float b = cov2Dv.y;
|
|
|
+ float D = a * d - b * b;
|
|
|
+ float trace = a + d; //追踪
|
|
|
+ float traceOver2 = 0.5 * trace;
|
|
|
+ float term2 = sqrt(max(0.1f, traceOver2 * traceOver2 - D));
|
|
|
+ float eigenValue1 = traceOver2 + term2; //特征值L1
|
|
|
+ float eigenValue2 = traceOver2 - term2; //特征值L1 //max( traceOver2 - term2, 0.25 );
|
|
|
+
|
|
|
+ if (pointCloudModeEnabled == 1) {// each splat is rendered as a filled circle
|
|
|
+ eigenValue1 = eigenValue2 = 0.2;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (eigenValue2 <= 0.0 ) return;
|
|
|
+
|
|
|
+ //add-----------
|
|
|
+ float splatScale_ = splatScale;
|
|
|
+
|
|
|
+ #if defined adaptive_size
|
|
|
+ //splatScale_ /= getPointSizeAttenuation();
|
|
|
+ float level = float(nodeLevelVNS.x);
|
|
|
+ float maxLevel = 5.;
|
|
|
+ float ratio1 = pow( ${ Potree.browser.urlHasValue('ratio1',true) || 1.4}, /* max(0., */maxLevel - level/* ) */);
|
|
|
+ splatScale_ *= ratio1;
|
|
|
+ //vColor.a /= ratio1;
|
|
|
+ #endif
|
|
|
+
|
|
|
+ ////-----------
|
|
|
+
|
|
|
+ vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a));
|
|
|
+ // since the eigen vectors are orthogonal, we derive the second one from the first 由于本征向量是正交的,因此我们从第一个导出第二个
|
|
|
+ vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x);
|
|
|
+
|
|
|
+ // We use sqrt(8) standard deviations偏差 instead of 3 to eliminate消除 more of the splat with a very low opacity.
|
|
|
+ vec2 basisVector1 = eigenVector1 * splatScale_ * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0);
|
|
|
+ vec2 basisVector2 = eigenVector2 * splatScale_ * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0);
|
|
|
+
|
|
|
+
|
|
|
+ vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) / viewport * 2.0 * inverseFocalAdjustment;
|
|
|
+
|
|
|
+ vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z , 1.0);
|
|
|
+ gl_Position = quadPos;
|
|
|
+
|
|
|
+ // Scale the position data we send to the fragment shader
|
|
|
+ vPosition *= sqrt8;
|
|
|
+ }
|
|
|
+ `
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ let fs = `
|
|
|
+ precision highp float;
|
|
|
+ #include <common>
|
|
|
+
|
|
|
+ uniform vec3 debugColor;
|
|
|
+
|
|
|
+ varying vec4 vColor;
|
|
|
+ varying vec2 vUv;
|
|
|
+
|
|
|
+ varying vec2 vPosition;
|
|
|
+
|
|
|
+ void main () {
|
|
|
+
|
|
|
+ float A = dot(vPosition, vPosition);
|
|
|
+ if (A > 8.0) discard; //position的范围半径为1。指一个rectangle面中的范围。椭圆外的完全透明
|
|
|
+ vec3 color = vColor.rgb;
|
|
|
+
|
|
|
+
|
|
|
+ float opacity = exp(-0.5 * A) * vColor.a;
|
|
|
+
|
|
|
+ gl_FragColor = vec4(color.rgb /* * opacity */, opacity);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ `;
|
|
|
+ const uniforms = {
|
|
|
+ 'orthographicMode': {
|
|
|
+ 'type': 'i',
|
|
|
+ 'value': 0
|
|
|
+ },
|
|
|
+
|
|
|
+ 'focal': {
|
|
|
+ 'type': 'v2',
|
|
|
+ 'value': new THREE.Vector2()
|
|
|
+ },
|
|
|
+ 'orthoZoom': {
|
|
|
+ 'type': 'f',
|
|
|
+ 'value': 1.0
|
|
|
+ },
|
|
|
+ 'inverseFocalAdjustment': {
|
|
|
+ 'type': 'f',
|
|
|
+ 'value': 1.0
|
|
|
+ },
|
|
|
+ 'viewport': {
|
|
|
+ 'type': 'v2',
|
|
|
+ 'value': new THREE.Vector2()
|
|
|
+ },
|
|
|
+
|
|
|
+ 'splatScale': {
|
|
|
+ 'type': 'f',
|
|
|
+ 'value': 1//this.adaptiveSize ? 32 : 1
|
|
|
+ },
|
|
|
+ 'pointCloudModeEnabled': {
|
|
|
+ 'type': 'i',
|
|
|
+ 'value': 0
|
|
|
+ },
|
|
|
+ uOctreeSize:{
|
|
|
+ 'type': 'f',
|
|
|
+ value:0
|
|
|
+ },
|
|
|
+ splatCount:{type:'i',value:0}
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const material = new THREE.ShaderMaterial({
|
|
|
+ uniforms: uniforms,
|
|
|
+ vertexShader: vs,
|
|
|
+ fragmentShader: fs,
|
|
|
+ transparent: true,
|
|
|
+ alphaTest: 1.0,
|
|
|
+ blending:THREE.NormalBlending,
|
|
|
+
|
|
|
+ depthTest: false,
|
|
|
+ depthWrite: true,
|
|
|
+ side: THREE.DoubleSide
|
|
|
+ });
|
|
|
+
|
|
|
+ return material;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ update(){
|
|
|
+ this.updateMesh();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ load(){//直接装载数据
|
|
|
+
|
|
|
+ const handleClick_debug_npz = async () => {
|
|
|
+ const file = Potree.resourcePath + '/npy-3dgs/' + this.fileName + '.npy'
|
|
|
+ await JSZipUtils.getBinaryContent(file, async (err, data) => {
|
|
|
+ if (err) {
|
|
|
+ throw err
|
|
|
+ // or handle the error
|
|
|
+ }
|
|
|
+ const jsZip = new JSZip()
|
|
|
+ const npzFiles = await jsZip.loadAsync(data)
|
|
|
+ for (const [npy_filename,npy_data] of Object.entries(npzFiles.files)) {
|
|
|
+ if (!npy_filename.endsWith('.npy')) {
|
|
|
+ console.error('error .npy')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const npy_array_buffer = await npzFiles.files[npy_filename].async("arraybuffer")
|
|
|
+ console.log({
|
|
|
+ npy_filename,
|
|
|
+ npy_data,
|
|
|
+ npy_array_buffer: npy_array_buffer
|
|
|
+ })
|
|
|
+
|
|
|
+ const _npyjs_ = new npyjs()
|
|
|
+ console.log(await _npyjs_.parse(npy_array_buffer))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ handleClick_debug_npz()
|
|
|
+
|
|
|
+ /* this.datas = {}
|
|
|
+ this.sortWorkerDatas.toSort */
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ gatherSceneNodesForSort = function() {
|
|
|
+
|
|
|
+ //将要渲染的点的index写入sortWorkerIndexesToSort的buffer
|
|
|
+ return function(gatherAllNodes = false) {
|
|
|
+
|
|
|
+ let time0 = performance.now()
|
|
|
+
|
|
|
+ const CENTERSInt_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerInt * 4
|
|
|
+ const CENTERSFloat_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerFloat * 3
|
|
|
+ const COLORS_BYTES_PER_ENTRY = 1*4; //4 byte
|
|
|
+ const COVS_BYTES_PER_ENTRY = Potree.defines.gs3d.BytesPerFloat * 6;
|
|
|
+ const NODES_BYTES_PER_ENTRY = 4 * 2
|
|
|
+
|
|
|
+ let currentByteOffset = {
|
|
|
+ centers:0, centersInt:0, colors:0, covs:0, nodes:0
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ let {centersFloat, centersInt, colors, covs, nodes} = this.sortWorkerDatas.toSort
|
|
|
+ let shareBuffer = centersFloat.buffer //大伙buffer都是同一个
|
|
|
+
|
|
|
+ this.currentNodes = Potree.visibleNodes.slice()
|
|
|
+
|
|
|
+ let i=0, nodeCount = Potree.visibleNodes.length , splatCount = 0;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ while(splatCount < Potree.config.maxSplatCount && i < nodeCount){
|
|
|
+
|
|
|
+
|
|
|
+ let numPoints = Potree.visibleNodes[i].geometryNode.numPoints
|
|
|
+
|
|
|
+ if(numPoints + splatCount > Potree.config.maxSplatCount)break;
|
|
|
+ splatCount += numPoints
|
|
|
+
|
|
|
+
|
|
|
+ const c0 = numPoints * 3, c1 = numPoints * 4, c2 = numPoints * 6, c3 = numPoints * 2
|
|
|
+
|
|
|
+
|
|
|
+ let geo = Potree.visibleNodes[i].geometryNode.geometry
|
|
|
+ let destView = this.useSharedMemory ? new Float32Array(shareBuffer, centersFloat.byteOffset + currentByteOffset.centers , c0)
|
|
|
+ : new Float32Array(centersFloat.buffer, currentByteOffset.centers , c0);
|
|
|
+ destView.set(geo.attributes.centersFloat.array/* .subarray(0,c0) */);
|
|
|
+
|
|
|
+ if(this.integerBasedSort){
|
|
|
+ destView = this.useSharedMemory ? new Int32Array(shareBuffer, centersInt.byteOffset + currentByteOffset.centersInt , c1)
|
|
|
+ : new Int32Array(centersInt.buffer, currentByteOffset.centersInt , c1);
|
|
|
+ destView.set(geo.attributes.centersInt.array/* .subarray(0,c1) */ );
|
|
|
+ }
|
|
|
+
|
|
|
+ destView = this.useSharedMemory ? new Uint8Array(shareBuffer, colors.byteOffset + currentByteOffset.colors, c1)
|
|
|
+ : new Uint8Array(colors.buffer, currentByteOffset.colors, c1);
|
|
|
+ destView.set(geo.attributes.rgba.array/* .subarray(0,c1) */ );
|
|
|
+
|
|
|
+ destView = this.useSharedMemory ? new Float32Array(shareBuffer, covs.byteOffset + currentByteOffset.covs, c2)
|
|
|
+ : new Float32Array(covs.buffer, currentByteOffset.covs, c2);
|
|
|
+ destView.set(geo.attributes.covs.array/* .subarray(0,c2) */ );
|
|
|
+
|
|
|
+ if(this.adaptiveSize){//效果不好,暂只支持useSharedMemory时开启
|
|
|
+ destView = new Int32Array(shareBuffer, nodes.byteOffset + currentByteOffset.nodes, c3);
|
|
|
+ let vnStart = this.visibilityTextureData.offsets.get(Potree.visibleNodes[i]);
|
|
|
+ let level = Potree.visibleNodes[i].getLevel()
|
|
|
+ for (let n = 0; n < numPoints ; n += 1) {
|
|
|
+ destView[n*2] = level;
|
|
|
+ destView[n*2 + 1] = vnStart;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ currentByteOffset.centersInt += numPoints * CENTERSInt_BYTES_PER_ENTRY
|
|
|
+ currentByteOffset.centers += numPoints * CENTERSFloat_BYTES_PER_ENTRY
|
|
|
+ currentByteOffset.colors += numPoints * COLORS_BYTES_PER_ENTRY
|
|
|
+ currentByteOffset.covs += numPoints * COVS_BYTES_PER_ENTRY
|
|
|
+ this.adaptiveSize && (currentByteOffset.nodes += numPoints * NODES_BYTES_PER_ENTRY)
|
|
|
+ i++
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ this.sortWorker.postMessage({'dataInited': true })
|
|
|
+ return {
|
|
|
+ 'splatRenderCount': splatCount
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ }();
|
|
|
+
|
|
|
+ updateSort = function() {
|
|
|
+
|
|
|
+ const mvpMatrix = new THREE.Matrix4();
|
|
|
+ const cameraPositionArray = [];
|
|
|
+ const lastSortViewDir = new THREE.Vector3(0, 0, -1);
|
|
|
+ const sortViewDir = new THREE.Vector3(0, 0, -1);
|
|
|
+ const lastSortViewPos = new THREE.Vector3();
|
|
|
+ const sortViewOffset = new THREE.Vector3();
|
|
|
+ const queuedSorts = [];
|
|
|
+ let lastTime = 0
|
|
|
+ const partialSorts = [
|
|
|
+ {
|
|
|
+ 'angleThreshold': 0.55,
|
|
|
+ 'sortFractions': [0.125, 0.33333, 0.75]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'angleThreshold': 0.65,
|
|
|
+ 'sortFractions': [0.33333, 0.66667]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'angleThreshold': 0.8,
|
|
|
+ 'sortFractions': [0.5]
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ return async function(force = false) {
|
|
|
+ // Potree.visibleNodes || console.log('updateSort')
|
|
|
+ // this.sortWorkerDatas || console.log('updateSort')
|
|
|
+ if (this.loadedSplatCount == 0 || this.sortRunning || this.unableSort || !this.sortWorkerDatas || !this.visible) return;
|
|
|
+ let camera = viewer.mainViewport.camera
|
|
|
+
|
|
|
+ let timeNow = Date.now()
|
|
|
+ let angleDiff = 0;
|
|
|
+ let positionDiff = 0;
|
|
|
+ let needsRefreshForRotation = false;
|
|
|
+ let needsRefreshForPosition = false;
|
|
|
+
|
|
|
+ if( Potree.math.closeTo(Potree.visibleNodes.length, this.currentNodes.length, 10) && (timeNow - lastTime < 1000)){//显示的nodes没太大变化的话,可能先不sort
|
|
|
+ sortViewDir.set(0, 0, -1).applyQuaternion(camera.quaternion);
|
|
|
+ angleDiff = sortViewDir.dot(lastSortViewDir);
|
|
|
+ positionDiff = sortViewOffset.copy(camera.position).sub(lastSortViewPos).length();
|
|
|
+
|
|
|
+ if (!force) {
|
|
|
+ if (!this.sortNeededForSceneChange && !this.dynamicMode && queuedSorts.length === 0) {
|
|
|
+ if (angleDiff <= 0.99) needsRefreshForRotation = true;
|
|
|
+ if (positionDiff >= 1.0) needsRefreshForPosition = true;
|
|
|
+ if (!needsRefreshForRotation && !needsRefreshForPosition) return setTimeout(this.updateSort.bind(this), 600)//console.log('no need refresh', Potree.visibleNodes.length, this.currentNodes.length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.sortRunning = true;
|
|
|
+ const { splatRenderCount } = this.gatherSceneNodesForSort();
|
|
|
+ this.splatRenderCount = splatRenderCount;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ mvpMatrix.copy(camera.matrixWorld).invert();
|
|
|
+ mvpMatrix.premultiply(camera.projectionMatrix);
|
|
|
+ mvpMatrix.multiply(this.matrixWorld );
|
|
|
+
|
|
|
+ /* if (this.gpuAcceleratedSort && (queuedSorts.length <= 1 || queuedSorts.length % 2 === 0)) {
|
|
|
+ await this.splatMesh.computeDistancesOnGPU(mvpMatrix, this.sortWorkerPrecomputedDistances);
|
|
|
+ } */
|
|
|
+
|
|
|
+
|
|
|
+ if (this.dynamicMode ) {
|
|
|
+ queuedSorts.push(this.splatRenderCount);
|
|
|
+ } else {
|
|
|
+ if (queuedSorts.length === 0) {
|
|
|
+ for (let partialSort of partialSorts) {
|
|
|
+ if (angleDiff < partialSort.angleThreshold) {
|
|
|
+ for (let sortFraction of partialSort.sortFractions) {
|
|
|
+ queuedSorts.push(Math.floor(this.splatRenderCount * sortFraction));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ queuedSorts.push(this.splatRenderCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(queuedSorts.length>1){
|
|
|
+ //console.log(queuedSorts)
|
|
|
+ queuedSorts.length = 0
|
|
|
+ }
|
|
|
+ //let sortCount = Math.min(queuedSorts.shift(), this.splatRenderCount);
|
|
|
+ //console.log('updateSort')
|
|
|
+
|
|
|
+ let sortCount = this.splatRenderCount
|
|
|
+
|
|
|
+ cameraPositionArray[0] = camera.position.x;
|
|
|
+ cameraPositionArray[1] = camera.position.y;
|
|
|
+ cameraPositionArray[2] = camera.position.z;
|
|
|
+
|
|
|
+ const sortMessage = {
|
|
|
+ sort : true,
|
|
|
+ 'modelViewProj': mvpMatrix.elements,
|
|
|
+ 'cameraPosition': cameraPositionArray,
|
|
|
+ 'splatRenderCount': this.splatRenderCount,
|
|
|
+ 'splatSortCount': sortCount,
|
|
|
+ 'usePrecomputedDistances': this.gpuAcceleratedSort //也就是不用worker算而用gpu,见 computeDistancesOnGPU
|
|
|
+ };
|
|
|
+
|
|
|
+ if(!this.useSharedMemory){ //通过 Transferable 对象转移所有权:无拷贝. 将内存控制权从主线程转移到 Worker,主线程中原 TypedArray 会变为不可用(长度变为 0)。
|
|
|
+ //把所有盆都交给worker,装满数据再回来 -,-
|
|
|
+ sortMessage.toSort = {
|
|
|
+ centersFloat : this.sortWorkerDatas.toSort.centersFloat.buffer, // worker.postMessage({ buffer: array.buffer }, [array.buffer]);
|
|
|
+ colors : this.sortWorkerDatas.toSort.colors.buffer,
|
|
|
+ covs : this.sortWorkerDatas.toSort.covs.buffer
|
|
|
+ }
|
|
|
+
|
|
|
+ this.integerBasedSort && (sortMessage.toSort.centersInt = this.sortWorkerDatas.toSort.centersInt.buffer)
|
|
|
+
|
|
|
+ sortMessage.sorted = {
|
|
|
+ centers : this.sortWorkerDatas.sorted[0].centers.buffer,
|
|
|
+ colors : this.sortWorkerDatas.sorted[0].colors.buffer,
|
|
|
+ covs1 : this.sortWorkerDatas.sorted[0].covs1.buffer,
|
|
|
+ covs2 : this.sortWorkerDatas.sorted[0].covs2.buffer
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ //如果要直接swapBuffer,内存占用太多还是算了吧
|
|
|
+
|
|
|
+ this.sortPromise = new Promise((resolve) => {
|
|
|
+ this.sortPromiseResolver = resolve;
|
|
|
+ });
|
|
|
+
|
|
|
+ if(this.useSharedMemory){
|
|
|
+ this.sortWorker.postMessage(sortMessage)
|
|
|
+ }else{
|
|
|
+ let TransferArr = [
|
|
|
+ this.sortWorkerDatas.toSort.centersFloat.buffer,
|
|
|
+ this.sortWorkerDatas.toSort.colors.buffer,
|
|
|
+ this.sortWorkerDatas.toSort.covs.buffer,
|
|
|
+ this.sortWorkerDatas.sorted[0].centers.buffer,
|
|
|
+ this.sortWorkerDatas.sorted[0].colors.buffer,
|
|
|
+ this.sortWorkerDatas.sorted[0].covs1.buffer,
|
|
|
+ this.sortWorkerDatas.sorted[0].covs2.buffer,
|
|
|
+ ]
|
|
|
+ TransferArr && TransferArr.push(this.sortWorkerDatas.toSort.centersInt.buffer)
|
|
|
+ this.sortWorker.postMessage( sortMessage, TransferArr);
|
|
|
+ }
|
|
|
+
|
|
|
+ //if (queuedSorts.length === 0) {
|
|
|
+ lastSortViewPos.copy( camera.position);
|
|
|
+ lastSortViewDir.copy(sortViewDir);
|
|
|
+ //}
|
|
|
+
|
|
|
+ this.sortNeededForSceneChange = false;
|
|
|
+ lastTime = timeNow
|
|
|
+ };
|
|
|
+
|
|
|
+ }();
|
|
|
+
|
|
|
+
|
|
|
+ updateRenderIndexes(globalCenters, globalColors,globalCovs1,globalCovs2, nodes, renderSplatCount) {
|
|
|
+
|
|
|
+ const geometry = this.geometry;
|
|
|
+ let start = performance.now()
|
|
|
+ if(!this.splatEnableSwapOut || this.splatAttrSameMemory){
|
|
|
+ geometry.attributes.center.set(globalCenters);
|
|
|
+ geometry.attributes.center.needsUpdate = true;
|
|
|
+ geometry.attributes.color.set(globalColors);
|
|
|
+ geometry.attributes.color.needsUpdate = true;
|
|
|
+ geometry.attributes.covRow1.set(globalCovs1);
|
|
|
+ geometry.attributes.covRow1.needsUpdate = true;
|
|
|
+ geometry.attributes.covRow2.set(globalCovs2);
|
|
|
+ geometry.attributes.covRow2.needsUpdate = true;
|
|
|
+
|
|
|
+ if(this.adaptiveSize){
|
|
|
+ geometry.attributes.nodeLevelVNS.set(nodes);
|
|
|
+ geometry.attributes.nodeLevelVNS.needsUpdate = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ }else{ //直接赋值速度快,需要双buffer否则会被擦写导致黑影。 splatEnableSwapOut要在sortworker init时就确定
|
|
|
+ geometry.attributes.center.array = globalCenters ;
|
|
|
+ geometry.attributes.center.needsUpdate = true; //不知为啥2025来再看并不会更流畅,可能这里时间减少了但gpu换buffer了需要时间吧? 另外流畅度更多取决于非sort时的,手机换buffer吃不消。
|
|
|
+ geometry.attributes.color.array = globalColors ;
|
|
|
+ geometry.attributes.color.needsUpdate = true;
|
|
|
+ geometry.attributes.covRow1.array = globalCovs1 ;
|
|
|
+ geometry.attributes.covRow1.needsUpdate = true;
|
|
|
+ geometry.attributes.covRow2.array = globalCovs2 ;
|
|
|
+ geometry.attributes.covRow2.needsUpdate = true;
|
|
|
+ }
|
|
|
+ if (renderSplatCount > 0 ) geometry.instanceCount = renderSplatCount;
|
|
|
+
|
|
|
+ //console.log('updateRenderIndexes time:', performance.now() - start)
|
|
|
+ //this.material.uniforms.splatCount.value = renderSplatCount
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ updateMaterial(){
|
|
|
+ if(!this.pointclouds.length)return
|
|
|
+ let camera = viewer.mainViewport.camera
|
|
|
+ //this.material.uniforms.viewport.value.copy(viewer.mainViewport.resolution2);
|
|
|
+ let viewport = viewer.mainViewport.resolution2
|
|
|
+
|
|
|
+ const focalMultiplier = camera.isOrthographicCamera ? (1.0 / window.devicePixelRatio) : 1.0;
|
|
|
+ const focalAdjustment = this.focalAdjustment * focalMultiplier;
|
|
|
+ const focalLengthX = camera.projectionMatrix.elements[0] * 0.5 * viewport.x;
|
|
|
+ const focalLengthY = camera.projectionMatrix.elements[5] * 0.5 * viewport.y;
|
|
|
+
|
|
|
+
|
|
|
+ const inverseFocalAdjustment = 1.0 / focalAdjustment;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ this.material.uniforms.viewport.value.copy(viewport);
|
|
|
+ this.material.uniforms.focal.value.set(focalLengthX * focalAdjustment, focalLengthY * focalAdjustment);
|
|
|
+ this.material.uniforms.orthographicMode.value = camera.isOrthographicCamera ? 1 : 0;
|
|
|
+ this.material.uniforms.orthoZoom.value = camera.zoom || 1.0;
|
|
|
+ this.material.uniforms.inverseFocalAdjustment.value = inverseFocalAdjustment;
|
|
|
+ if (this.dynamicMode) {
|
|
|
+ for (let i = 0; i < this.scenes.length; i++) {
|
|
|
+ this.material.uniforms.transforms.value[i].copy(this.getScene(i).transform);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.material.uniforms.uOctreeSize.value = this.pointclouds[0].pcoGeometry.boundingBox.getSize(new THREE.Vector3()).x;
|
|
|
+ this.material.uniformsNeedUpdate = true;
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|