@d5techs/3dgs-lib 1.4.7 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3dgs-lib.cjs +325 -115
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +325 -115
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/App.d.ts +27 -0
- package/dist/core/Renderer.d.ts +19 -0
- package/dist/gs/GSSplatRendererMobile.d.ts +7 -14
- package/dist/gs/TextureCompressor.d.ts +4 -0
- package/package.json +1 -1
package/dist/3dgs-lib.js
CHANGED
|
@@ -37,7 +37,7 @@ function isMobileDevice() {
|
|
|
37
37
|
}
|
|
38
38
|
function getRecommendedDPR() {
|
|
39
39
|
const isMobile = isMobileDevice();
|
|
40
|
-
const maxDpr = isMobile ?
|
|
40
|
+
const maxDpr = isMobile ? 2 : 2;
|
|
41
41
|
return Math.min(window.devicePixelRatio || 1, maxDpr);
|
|
42
42
|
}
|
|
43
43
|
function isWebGPUSupported() {
|
|
@@ -252,6 +252,13 @@ class Renderer {
|
|
|
252
252
|
__publicField(this, "renderPassEncoder");
|
|
253
253
|
// ResizeObserver 引用(用于清理)
|
|
254
254
|
__publicField(this, "resizeObserver", null);
|
|
255
|
+
// 渲染缩放:0.5 = 半分辨率,1.0 = 正常,用于性能/质量权衡
|
|
256
|
+
__publicField(this, "_renderScale", 1);
|
|
257
|
+
// 自定义 DPR 覆盖:-1 表示使用自动推荐值
|
|
258
|
+
__publicField(this, "_customDPR", -1);
|
|
259
|
+
// 缓存最后一次的 CSS 尺寸,用于 renderScale 变更时重算
|
|
260
|
+
__publicField(this, "_lastCSSWidth", 0);
|
|
261
|
+
__publicField(this, "_lastCSSHeight", 0);
|
|
255
262
|
// 背景颜色
|
|
256
263
|
__publicField(this, "_clearColor", { r: 0.15, g: 0.15, b: 0.15, a: 1 });
|
|
257
264
|
this.canvas = canvas;
|
|
@@ -362,6 +369,45 @@ class Renderer {
|
|
|
362
369
|
});
|
|
363
370
|
this._depthTextureView = this._depthTexture.createView();
|
|
364
371
|
}
|
|
372
|
+
getEffectiveDPR() {
|
|
373
|
+
const baseDPR = this._customDPR > 0 ? this._customDPR : getRecommendedDPR();
|
|
374
|
+
return baseDPR * this._renderScale;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* 设置渲染缩放比例,用于性能/质量权衡
|
|
378
|
+
* 0.5 = 半分辨率(性能提升约 4 倍),1.0 = 正常分辨率
|
|
379
|
+
* 内部等效于降低 DPR,不影响 CSS 布局尺寸
|
|
380
|
+
*/
|
|
381
|
+
setRenderScale(scale) {
|
|
382
|
+
this._renderScale = Math.max(0.25, Math.min(2, scale));
|
|
383
|
+
if (this._lastCSSWidth > 0 && this._lastCSSHeight > 0) {
|
|
384
|
+
this.applySize(this._lastCSSWidth, this._lastCSSHeight);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
getRenderScale() {
|
|
388
|
+
return this._renderScale;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* 覆盖自动 DPR 推荐值
|
|
392
|
+
* 传入 -1 恢复自动模式
|
|
393
|
+
*/
|
|
394
|
+
setDPR(dpr) {
|
|
395
|
+
this._customDPR = dpr;
|
|
396
|
+
if (this._lastCSSWidth > 0 && this._lastCSSHeight > 0) {
|
|
397
|
+
this.applySize(this._lastCSSWidth, this._lastCSSHeight);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
getDPR() {
|
|
401
|
+
return this._customDPR > 0 ? this._customDPR : getRecommendedDPR();
|
|
402
|
+
}
|
|
403
|
+
applySize(cssWidth, cssHeight) {
|
|
404
|
+
this._lastCSSWidth = cssWidth;
|
|
405
|
+
this._lastCSSHeight = cssHeight;
|
|
406
|
+
const dpr = this.getEffectiveDPR();
|
|
407
|
+
this.canvas.width = Math.max(1, Math.floor(cssWidth * dpr));
|
|
408
|
+
this.canvas.height = Math.max(1, Math.floor(cssHeight * dpr));
|
|
409
|
+
this.createDepthTexture();
|
|
410
|
+
}
|
|
365
411
|
/**
|
|
366
412
|
* 设置 resize 监听
|
|
367
413
|
*/
|
|
@@ -369,10 +415,7 @@ class Renderer {
|
|
|
369
415
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
370
416
|
for (const entry of entries) {
|
|
371
417
|
const { width, height } = entry.contentRect;
|
|
372
|
-
|
|
373
|
-
this.canvas.width = Math.floor(width * dpr);
|
|
374
|
-
this.canvas.height = Math.floor(height * dpr);
|
|
375
|
-
this.createDepthTexture();
|
|
418
|
+
this.applySize(width, height);
|
|
376
419
|
}
|
|
377
420
|
});
|
|
378
421
|
this.resizeObserver.observe(this.canvas);
|
|
@@ -8434,6 +8477,65 @@ function compressSplatsToTextures(device, data) {
|
|
|
8434
8477
|
{ bytesPerRow: width * 4 },
|
|
8435
8478
|
{ width, height }
|
|
8436
8479
|
);
|
|
8480
|
+
let shBasis0Texture = null;
|
|
8481
|
+
let shBasis1Texture = null;
|
|
8482
|
+
let shBasis2Texture = null;
|
|
8483
|
+
const hasSH = !!(data.shCoeffs && data.shCoeffs.length >= count * 45);
|
|
8484
|
+
if (hasSH) {
|
|
8485
|
+
const shCoeffs = data.shCoeffs;
|
|
8486
|
+
const sh0Data = new Float32Array(totalPixels * 4);
|
|
8487
|
+
const sh1Data = new Float32Array(totalPixels * 4);
|
|
8488
|
+
const sh2Data = new Float32Array(totalPixels * 4);
|
|
8489
|
+
for (let i = 0; i < count; i++) {
|
|
8490
|
+
const base = i * 45;
|
|
8491
|
+
const px = i * 4;
|
|
8492
|
+
sh0Data[px + 0] = shCoeffs[base + 0];
|
|
8493
|
+
sh0Data[px + 1] = shCoeffs[base + 1];
|
|
8494
|
+
sh0Data[px + 2] = shCoeffs[base + 2];
|
|
8495
|
+
sh1Data[px + 0] = shCoeffs[base + 3];
|
|
8496
|
+
sh1Data[px + 1] = shCoeffs[base + 4];
|
|
8497
|
+
sh1Data[px + 2] = shCoeffs[base + 5];
|
|
8498
|
+
sh2Data[px + 0] = shCoeffs[base + 6];
|
|
8499
|
+
sh2Data[px + 1] = shCoeffs[base + 7];
|
|
8500
|
+
sh2Data[px + 2] = shCoeffs[base + 8];
|
|
8501
|
+
}
|
|
8502
|
+
shBasis0Texture = device.createTexture({
|
|
8503
|
+
size: { width, height },
|
|
8504
|
+
format: "rgba32float",
|
|
8505
|
+
usage: textureUsage,
|
|
8506
|
+
label: "sh-basis0"
|
|
8507
|
+
});
|
|
8508
|
+
shBasis1Texture = device.createTexture({
|
|
8509
|
+
size: { width, height },
|
|
8510
|
+
format: "rgba32float",
|
|
8511
|
+
usage: textureUsage,
|
|
8512
|
+
label: "sh-basis1"
|
|
8513
|
+
});
|
|
8514
|
+
shBasis2Texture = device.createTexture({
|
|
8515
|
+
size: { width, height },
|
|
8516
|
+
format: "rgba32float",
|
|
8517
|
+
usage: textureUsage,
|
|
8518
|
+
label: "sh-basis2"
|
|
8519
|
+
});
|
|
8520
|
+
device.queue.writeTexture(
|
|
8521
|
+
{ texture: shBasis0Texture },
|
|
8522
|
+
sh0Data,
|
|
8523
|
+
{ bytesPerRow: width * 16 },
|
|
8524
|
+
{ width, height }
|
|
8525
|
+
);
|
|
8526
|
+
device.queue.writeTexture(
|
|
8527
|
+
{ texture: shBasis1Texture },
|
|
8528
|
+
sh1Data,
|
|
8529
|
+
{ bytesPerRow: width * 16 },
|
|
8530
|
+
{ width, height }
|
|
8531
|
+
);
|
|
8532
|
+
device.queue.writeTexture(
|
|
8533
|
+
{ texture: shBasis2Texture },
|
|
8534
|
+
sh2Data,
|
|
8535
|
+
{ bytesPerRow: width * 16 },
|
|
8536
|
+
{ width, height }
|
|
8537
|
+
);
|
|
8538
|
+
}
|
|
8437
8539
|
return {
|
|
8438
8540
|
width,
|
|
8439
8541
|
height,
|
|
@@ -8442,6 +8544,10 @@ function compressSplatsToTextures(device, data) {
|
|
|
8442
8544
|
scaleRotTexture1,
|
|
8443
8545
|
scaleRotTexture2,
|
|
8444
8546
|
colorTexture,
|
|
8547
|
+
shBasis0Texture,
|
|
8548
|
+
shBasis1Texture,
|
|
8549
|
+
shBasis2Texture,
|
|
8550
|
+
hasSH,
|
|
8445
8551
|
boundingBox
|
|
8446
8552
|
};
|
|
8447
8553
|
}
|
|
@@ -8450,9 +8556,12 @@ function destroyCompressedTextures(textures) {
|
|
|
8450
8556
|
textures.scaleRotTexture1.destroy();
|
|
8451
8557
|
textures.scaleRotTexture2.destroy();
|
|
8452
8558
|
textures.colorTexture.destroy();
|
|
8559
|
+
if (textures.shBasis0Texture) textures.shBasis0Texture.destroy();
|
|
8560
|
+
if (textures.shBasis1Texture) textures.shBasis1Texture.destroy();
|
|
8561
|
+
if (textures.shBasis2Texture) textures.shBasis2Texture.destroy();
|
|
8453
8562
|
}
|
|
8454
8563
|
const DEFAULT_NUM_BUCKETS = 65536;
|
|
8455
|
-
const IOS_NUM_BUCKETS =
|
|
8564
|
+
const IOS_NUM_BUCKETS = 16384;
|
|
8456
8565
|
const WORKGROUP_SIZE = 256;
|
|
8457
8566
|
function isIOSDevice() {
|
|
8458
8567
|
if (typeof navigator === "undefined") return false;
|
|
@@ -8956,9 +9065,17 @@ class GSSplatSorterMobile {
|
|
|
8956
9065
|
this.drawIndirectBuffer.destroy();
|
|
8957
9066
|
}
|
|
8958
9067
|
}
|
|
8959
|
-
const
|
|
9068
|
+
const shaderCodeMobile = (
|
|
8960
9069
|
/* wgsl */
|
|
8961
9070
|
`
|
|
9071
|
+
|
|
9072
|
+
const SH_C1: f32 = 0.4886025119029199;
|
|
9073
|
+
const GAUSSIAN_K: f32 = 4.0;
|
|
9074
|
+
const EXP_NEG_K: f32 = 0.01831563888873418;
|
|
9075
|
+
const INV_ONE_MINUS_EXP_NEG_K: f32 = 1.01865736036377408;
|
|
9076
|
+
const ALPHA_CULL_THRESHOLD: f32 = 0.00392156863;
|
|
9077
|
+
const LOW_PASS_FILTER: f32 = 0.3;
|
|
9078
|
+
|
|
8962
9079
|
struct Uniforms {
|
|
8963
9080
|
view: mat4x4<f32>,
|
|
8964
9081
|
proj: mat4x4<f32>,
|
|
@@ -8967,18 +9084,21 @@ struct Uniforms {
|
|
|
8967
9084
|
_pad: f32,
|
|
8968
9085
|
screenSize: vec2<f32>,
|
|
8969
9086
|
_pad2: vec2<f32>,
|
|
8970
|
-
textureSize: vec2<f32>,
|
|
8971
|
-
|
|
9087
|
+
textureSize: vec2<f32>,
|
|
9088
|
+
shEnabled: f32,
|
|
9089
|
+
_pad3: f32,
|
|
8972
9090
|
}
|
|
8973
9091
|
|
|
8974
9092
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
8975
9093
|
@group(0) @binding(1) var<storage, read> sortedIndices: array<u32>;
|
|
8976
9094
|
|
|
8977
|
-
|
|
8978
|
-
@group(1) @binding(
|
|
8979
|
-
@group(1) @binding(
|
|
8980
|
-
@group(1) @binding(
|
|
8981
|
-
@group(1) @binding(
|
|
9095
|
+
@group(1) @binding(0) var positionTex: texture_2d<f32>;
|
|
9096
|
+
@group(1) @binding(1) var scaleRotTex1: texture_2d<f32>;
|
|
9097
|
+
@group(1) @binding(2) var scaleRotTex2: texture_2d<f32>;
|
|
9098
|
+
@group(1) @binding(3) var colorTex: texture_2d<f32>;
|
|
9099
|
+
@group(1) @binding(4) var shBasis0Tex: texture_2d<f32>;
|
|
9100
|
+
@group(1) @binding(5) var shBasis1Tex: texture_2d<f32>;
|
|
9101
|
+
@group(1) @binding(6) var shBasis2Tex: texture_2d<f32>;
|
|
8982
9102
|
|
|
8983
9103
|
struct VertexOutput {
|
|
8984
9104
|
@builtin(position) position: vec4<f32>,
|
|
@@ -8996,7 +9116,6 @@ const QUAD_POSITIONS = array<vec2<f32>, 4>(
|
|
|
8996
9116
|
|
|
8997
9117
|
const ELLIPSE_SCALE: f32 = 3.0;
|
|
8998
9118
|
|
|
8999
|
-
// 将索引转换为纹理坐标
|
|
9000
9119
|
fn indexToTexCoord(index: u32) -> vec2<u32> {
|
|
9001
9120
|
let texWidth = u32(uniforms.textureSize.x);
|
|
9002
9121
|
let x = index % texWidth;
|
|
@@ -9004,7 +9123,6 @@ fn indexToTexCoord(index: u32) -> vec2<u32> {
|
|
|
9004
9123
|
return vec2<u32>(x, y);
|
|
9005
9124
|
}
|
|
9006
9125
|
|
|
9007
|
-
// 四元数转旋转矩阵
|
|
9008
9126
|
fn quatToMat3(q: vec4<f32>) -> mat3x3<f32> {
|
|
9009
9127
|
let w = q[0]; let x = q[1]; let y = q[2]; let z = q[3];
|
|
9010
9128
|
let x2 = x + x; let y2 = y + y; let z2 = z + z;
|
|
@@ -9018,15 +9136,12 @@ fn quatToMat3(q: vec4<f32>) -> mat3x3<f32> {
|
|
|
9018
9136
|
);
|
|
9019
9137
|
}
|
|
9020
9138
|
|
|
9021
|
-
// 从模型矩阵提取统一缩放因子(取 X 轴向量长度)
|
|
9022
9139
|
fn getModelScale(model: mat4x4<f32>) -> f32 {
|
|
9023
9140
|
return length(model[0].xyz);
|
|
9024
9141
|
}
|
|
9025
9142
|
|
|
9026
|
-
// 计算 2D 协方差
|
|
9027
9143
|
fn computeCov2D(mean: vec3<f32>, scale: vec3<f32>, rotation: vec4<f32>, modelView: mat4x4<f32>, proj: mat4x4<f32>, modelScale: f32) -> vec3<f32> {
|
|
9028
9144
|
let R = quatToMat3(rotation);
|
|
9029
|
-
// 应用模型缩放到 splat scale
|
|
9030
9145
|
let scaledScale = scale * modelScale;
|
|
9031
9146
|
let s2 = scaledScale * scaledScale;
|
|
9032
9147
|
let M = mat3x3<f32>(R[0] * s2.x, R[1] * s2.y, R[2] * s2.z);
|
|
@@ -9038,8 +9153,6 @@ fn computeCov2D(mean: vec3<f32>, scale: vec3<f32>, rotation: vec4<f32>, modelVie
|
|
|
9038
9153
|
let z = -viewPos.z;
|
|
9039
9154
|
let z_clamped = max(z, 0.001);
|
|
9040
9155
|
let z2 = z_clamped * z_clamped;
|
|
9041
|
-
// 雅可比矩阵: 从相机坐标 (x_cam, y_cam, z_cam) 到 NDC 的偏导数
|
|
9042
|
-
// x_ndc = fx * x_cam / (-z_cam), 所以 dx_ndc/dz_cam = fx * x_cam / z_cam^2 (正号!)
|
|
9043
9156
|
let j1 = vec3<f32>(fx / z_clamped, 0.0, fx * viewPos.x / z2);
|
|
9044
9157
|
let j2 = vec3<f32>(0.0, fy / z_clamped, fy * viewPos.y / z2);
|
|
9045
9158
|
let Sj1 = SigmaView * j1;
|
|
@@ -9047,17 +9160,22 @@ fn computeCov2D(mean: vec3<f32>, scale: vec3<f32>, rotation: vec4<f32>, modelVie
|
|
|
9047
9160
|
return vec3<f32>(dot(j1, Sj1), dot(j1, Sj2), dot(j2, Sj2));
|
|
9048
9161
|
}
|
|
9049
9162
|
|
|
9050
|
-
// 计算椭圆轴
|
|
9051
9163
|
fn computeEllipseAxes(cov2D: vec3<f32>) -> mat2x2<f32> {
|
|
9052
|
-
|
|
9164
|
+
var cov = cov2D;
|
|
9165
|
+
cov.x += LOW_PASS_FILTER;
|
|
9166
|
+
cov.z += LOW_PASS_FILTER;
|
|
9167
|
+
let a = cov.x; let b = cov.y; let c = cov.z;
|
|
9053
9168
|
let trace = a + c;
|
|
9054
9169
|
let det = a * c - b * b;
|
|
9055
9170
|
let disc = trace * trace - 4.0 * det;
|
|
9056
9171
|
let sqrtDisc = sqrt(max(disc, 0.0));
|
|
9057
9172
|
let lambda1 = max((trace + sqrtDisc) * 0.5, 0.0);
|
|
9058
9173
|
let lambda2 = max((trace - sqrtDisc) * 0.5, 0.0);
|
|
9059
|
-
|
|
9060
|
-
|
|
9174
|
+
if (lambda2 <= 0.0) {
|
|
9175
|
+
return mat2x2<f32>(vec2<f32>(0.0), vec2<f32>(0.0));
|
|
9176
|
+
}
|
|
9177
|
+
let r1 = min(2.0 * sqrt(2.0 * lambda1), 1024.0);
|
|
9178
|
+
let r2 = min(2.0 * sqrt(2.0 * lambda2), 1024.0);
|
|
9061
9179
|
var axis1: vec2<f32>; var axis2: vec2<f32>;
|
|
9062
9180
|
if (abs(b) > 1e-6) {
|
|
9063
9181
|
axis1 = normalize(vec2<f32>(b, lambda1 - a));
|
|
@@ -9073,59 +9191,88 @@ fn computeEllipseAxes(cov2D: vec3<f32>) -> mat2x2<f32> {
|
|
|
9073
9191
|
fn vs_main(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {
|
|
9074
9192
|
var output: VertexOutput;
|
|
9075
9193
|
|
|
9076
|
-
// 获取排序后的索引
|
|
9077
9194
|
let splatIndex = sortedIndices[instanceIndex];
|
|
9078
9195
|
let texCoord = indexToTexCoord(splatIndex);
|
|
9079
9196
|
|
|
9080
|
-
// 从纹理采样位置数据(RGBA32Float,直接读取)
|
|
9081
9197
|
let posSample = textureLoad(positionTex, texCoord, 0);
|
|
9082
9198
|
let mean = posSample.xyz;
|
|
9083
9199
|
|
|
9084
|
-
// 从纹理采样缩放和旋转(RGBA16Float,GPU 自动转换为 f32)
|
|
9085
9200
|
let scaleRot1 = textureLoad(scaleRotTex1, texCoord, 0);
|
|
9086
9201
|
let scaleRot2 = textureLoad(scaleRotTex2, texCoord, 0);
|
|
9087
|
-
|
|
9088
9202
|
let scale = scaleRot1.xyz;
|
|
9089
9203
|
let rotation = vec4<f32>(scaleRot1.w, scaleRot2.x, scaleRot2.y, scaleRot2.z);
|
|
9090
9204
|
|
|
9091
|
-
// 从纹理采样颜色(RGBA8Unorm,GPU 自动归一化到 0-1)
|
|
9092
9205
|
let colorSample = textureLoad(colorTex, texCoord, 0);
|
|
9093
|
-
|
|
9206
|
+
var color = colorSample.rgb;
|
|
9094
9207
|
let opacity = colorSample.a;
|
|
9208
|
+
|
|
9209
|
+
if (opacity < ALPHA_CULL_THRESHOLD) {
|
|
9210
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
|
9211
|
+
return output;
|
|
9212
|
+
}
|
|
9095
9213
|
|
|
9096
|
-
// 计算顶点位置
|
|
9097
9214
|
let quadPos = QUAD_POSITIONS[vertexIndex];
|
|
9098
9215
|
output.localUV = quadPos;
|
|
9099
9216
|
|
|
9100
|
-
// 计算 modelView 矩阵和模型缩放
|
|
9101
9217
|
let modelView = uniforms.view * uniforms.model;
|
|
9102
9218
|
let modelScale = getModelScale(uniforms.model);
|
|
9103
9219
|
|
|
9104
9220
|
let cov2D = computeCov2D(mean, scale, rotation, modelView, uniforms.proj, modelScale);
|
|
9105
9221
|
let axes = computeEllipseAxes(cov2D);
|
|
9106
|
-
|
|
9222
|
+
|
|
9223
|
+
if (axes[0].x == 0.0 && axes[0].y == 0.0 && axes[1].x == 0.0 && axes[1].y == 0.0) {
|
|
9224
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
|
9225
|
+
return output;
|
|
9226
|
+
}
|
|
9227
|
+
|
|
9228
|
+
let basisViewport = vec2<f32>(1.0 / uniforms.screenSize.x, 1.0 / uniforms.screenSize.y);
|
|
9229
|
+
let ndcOffset = (quadPos.x * axes[0] + quadPos.y * axes[1]) * basisViewport * 2.0;
|
|
9107
9230
|
|
|
9108
|
-
// 应用 model 变换到 splat 位置
|
|
9109
9231
|
let worldPos = uniforms.model * vec4<f32>(mean, 1.0);
|
|
9110
9232
|
let viewPos = uniforms.view * worldPos;
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9233
|
+
let clipPos = uniforms.proj * viewPos;
|
|
9234
|
+
let pW = 1.0 / (clipPos.w + 0.0000001);
|
|
9235
|
+
let ndcPos = clipPos * pW;
|
|
9236
|
+
|
|
9237
|
+
if (abs(ndcPos.x) > 1.3 || abs(ndcPos.y) > 1.3 || ndcPos.z < -0.2 || ndcPos.z > 1.0) {
|
|
9238
|
+
output.position = vec4<f32>(0.0, 0.0, 2.0, 1.0);
|
|
9239
|
+
return output;
|
|
9240
|
+
}
|
|
9241
|
+
|
|
9242
|
+
output.position = vec4<f32>(ndcPos.xy + ndcOffset, ndcPos.z, 1.0);
|
|
9243
|
+
|
|
9244
|
+
// L1 SH evaluation
|
|
9245
|
+
if (uniforms.shEnabled > 0.5) {
|
|
9246
|
+
let sh_b0 = textureLoad(shBasis0Tex, texCoord, 0).xyz;
|
|
9247
|
+
let sh_b1 = textureLoad(shBasis1Tex, texCoord, 0).xyz;
|
|
9248
|
+
let sh_b2 = textureLoad(shBasis2Tex, texCoord, 0).xyz;
|
|
9249
|
+
|
|
9250
|
+
let viewDir = worldPos.xyz - uniforms.cameraPos;
|
|
9251
|
+
let shDir = normalize(vec3<f32>(
|
|
9252
|
+
dot(viewDir, uniforms.model[0].xyz),
|
|
9253
|
+
dot(viewDir, uniforms.model[1].xyz),
|
|
9254
|
+
dot(viewDir, uniforms.model[2].xyz)
|
|
9255
|
+
));
|
|
9256
|
+
|
|
9257
|
+
color += (-SH_C1 * shDir.y) * sh_b0
|
|
9258
|
+
+ ( SH_C1 * shDir.z) * sh_b1
|
|
9259
|
+
+ (-SH_C1 * shDir.x) * sh_b2;
|
|
9260
|
+
}
|
|
9261
|
+
|
|
9115
9262
|
output.color = color;
|
|
9116
9263
|
output.opacity = opacity;
|
|
9117
|
-
|
|
9118
9264
|
return output;
|
|
9119
9265
|
}
|
|
9120
9266
|
|
|
9121
9267
|
@fragment
|
|
9122
9268
|
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
let
|
|
9127
|
-
|
|
9128
|
-
|
|
9269
|
+
if (input.opacity <= 0.0) { discard; }
|
|
9270
|
+
let A = dot(input.localUV, input.localUV);
|
|
9271
|
+
if (A > 1.0) { discard; }
|
|
9272
|
+
let weight = (exp(-GAUSSIAN_K * A) - EXP_NEG_K) * INV_ONE_MINUS_EXP_NEG_K;
|
|
9273
|
+
let alpha = input.opacity * weight;
|
|
9274
|
+
if (alpha < ALPHA_CULL_THRESHOLD) { discard; }
|
|
9275
|
+
let color = max(input.color, vec3<f32>(0.0));
|
|
9129
9276
|
return vec4<f32>(color * alpha, alpha);
|
|
9130
9277
|
}
|
|
9131
9278
|
`
|
|
@@ -9155,6 +9302,10 @@ class GSSplatRendererMobile {
|
|
|
9155
9302
|
// 帧计数(用于排序频率控制)
|
|
9156
9303
|
__publicField(this, "frameCount", 0);
|
|
9157
9304
|
__publicField(this, "sortEveryNFrames", 1);
|
|
9305
|
+
// 相机静止检测:跳过不必要的排序
|
|
9306
|
+
__publicField(this, "lastSortViewMatrix", new Float32Array(16));
|
|
9307
|
+
__publicField(this, "lastSortProjMatrix", new Float32Array(16));
|
|
9308
|
+
__publicField(this, "sortStateInitialized", false);
|
|
9158
9309
|
// ============================================
|
|
9159
9310
|
// 变换相关 (position, rotation, scale)
|
|
9160
9311
|
// ============================================
|
|
@@ -9165,6 +9316,8 @@ class GSSplatRendererMobile {
|
|
|
9165
9316
|
__publicField(this, "pivot", [0, 0, 0]);
|
|
9166
9317
|
// 旋转/缩放中心点
|
|
9167
9318
|
__publicField(this, "modelMatrix", new Float32Array(16));
|
|
9319
|
+
// 1x1 dummy SH 纹理(当无 SH 数据时使用)
|
|
9320
|
+
__publicField(this, "dummySHTexture", null);
|
|
9168
9321
|
this.renderer = renderer;
|
|
9169
9322
|
this.camera = camera;
|
|
9170
9323
|
this.createPipeline();
|
|
@@ -9286,7 +9439,7 @@ class GSSplatRendererMobile {
|
|
|
9286
9439
|
createPipeline() {
|
|
9287
9440
|
const device = this.renderer.device;
|
|
9288
9441
|
const shaderModule = device.createShaderModule({
|
|
9289
|
-
code:
|
|
9442
|
+
code: shaderCodeMobile,
|
|
9290
9443
|
label: "mobile-splat-shader"
|
|
9291
9444
|
});
|
|
9292
9445
|
this.uniformBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -9303,32 +9456,27 @@ class GSSplatRendererMobile {
|
|
|
9303
9456
|
}
|
|
9304
9457
|
]
|
|
9305
9458
|
});
|
|
9459
|
+
const texEntry = (binding) => ({
|
|
9460
|
+
binding,
|
|
9461
|
+
visibility: GPUShaderStage.VERTEX,
|
|
9462
|
+
texture: { sampleType: "unfilterable-float" }
|
|
9463
|
+
});
|
|
9306
9464
|
this.textureBindGroupLayout = device.createBindGroupLayout({
|
|
9307
9465
|
entries: [
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
binding: 2,
|
|
9323
|
-
visibility: GPUShaderStage.VERTEX,
|
|
9324
|
-
texture: { sampleType: "unfilterable-float" }
|
|
9325
|
-
},
|
|
9326
|
-
{
|
|
9327
|
-
// colorTex (RGBA8Unorm)
|
|
9328
|
-
binding: 3,
|
|
9329
|
-
visibility: GPUShaderStage.VERTEX,
|
|
9330
|
-
texture: { sampleType: "unfilterable-float" }
|
|
9331
|
-
}
|
|
9466
|
+
texEntry(0),
|
|
9467
|
+
// positionTex
|
|
9468
|
+
texEntry(1),
|
|
9469
|
+
// scaleRotTex1
|
|
9470
|
+
texEntry(2),
|
|
9471
|
+
// scaleRotTex2
|
|
9472
|
+
texEntry(3),
|
|
9473
|
+
// colorTex
|
|
9474
|
+
texEntry(4),
|
|
9475
|
+
// shBasis0Tex
|
|
9476
|
+
texEntry(5),
|
|
9477
|
+
// shBasis1Tex
|
|
9478
|
+
texEntry(6)
|
|
9479
|
+
// shBasis2Tex
|
|
9332
9480
|
]
|
|
9333
9481
|
});
|
|
9334
9482
|
const pipelineLayout = device.createPipelineLayout({
|
|
@@ -9372,10 +9520,16 @@ class GSSplatRendererMobile {
|
|
|
9372
9520
|
depthCompare: "always"
|
|
9373
9521
|
}
|
|
9374
9522
|
});
|
|
9523
|
+
this.dummySHTexture = device.createTexture({
|
|
9524
|
+
size: { width: 1, height: 1 },
|
|
9525
|
+
format: "rgba32float",
|
|
9526
|
+
usage: GPUTextureUsage.TEXTURE_BINDING,
|
|
9527
|
+
label: "dummy-sh"
|
|
9528
|
+
});
|
|
9375
9529
|
}
|
|
9376
9530
|
/**
|
|
9377
9531
|
* 创建 uniform buffer
|
|
9378
|
-
* 布局: view
|
|
9532
|
+
* 布局: view(64) + proj(64) + model(64) + cameraPos(12)+pad(4) + screenSize(8)+pad(8) + textureSize(8)+shEnabled(4)+pad(4) = 240 bytes
|
|
9379
9533
|
*/
|
|
9380
9534
|
createUniformBuffer() {
|
|
9381
9535
|
this.uniformBuffer = this.renderer.device.createBuffer({
|
|
@@ -9436,35 +9590,22 @@ class GSSplatRendererMobile {
|
|
|
9436
9590
|
this.uniformBindGroup = device.createBindGroup({
|
|
9437
9591
|
layout: this.uniformBindGroupLayout,
|
|
9438
9592
|
entries: [
|
|
9439
|
-
{
|
|
9440
|
-
|
|
9441
|
-
resource: { buffer: this.uniformBuffer }
|
|
9442
|
-
},
|
|
9443
|
-
{
|
|
9444
|
-
binding: 1,
|
|
9445
|
-
resource: { buffer: this.sorter.getIndicesBuffer() }
|
|
9446
|
-
}
|
|
9593
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer } },
|
|
9594
|
+
{ binding: 1, resource: { buffer: this.sorter.getIndicesBuffer() } }
|
|
9447
9595
|
]
|
|
9448
9596
|
});
|
|
9597
|
+
const dummyView = this.dummySHTexture.createView();
|
|
9598
|
+
const tex = this.compressedTextures;
|
|
9449
9599
|
this.textureBindGroup = device.createBindGroup({
|
|
9450
9600
|
layout: this.textureBindGroupLayout,
|
|
9451
9601
|
entries: [
|
|
9452
|
-
{
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
},
|
|
9456
|
-
{
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
},
|
|
9460
|
-
{
|
|
9461
|
-
binding: 2,
|
|
9462
|
-
resource: this.compressedTextures.scaleRotTexture2.createView()
|
|
9463
|
-
},
|
|
9464
|
-
{
|
|
9465
|
-
binding: 3,
|
|
9466
|
-
resource: this.compressedTextures.colorTexture.createView()
|
|
9467
|
-
}
|
|
9602
|
+
{ binding: 0, resource: tex.positionTexture.createView() },
|
|
9603
|
+
{ binding: 1, resource: tex.scaleRotTexture1.createView() },
|
|
9604
|
+
{ binding: 2, resource: tex.scaleRotTexture2.createView() },
|
|
9605
|
+
{ binding: 3, resource: tex.colorTexture.createView() },
|
|
9606
|
+
{ binding: 4, resource: tex.shBasis0Texture ? tex.shBasis0Texture.createView() : dummyView },
|
|
9607
|
+
{ binding: 5, resource: tex.shBasis1Texture ? tex.shBasis1Texture.createView() : dummyView },
|
|
9608
|
+
{ binding: 6, resource: tex.shBasis2Texture ? tex.shBasis2Texture.createView() : dummyView }
|
|
9468
9609
|
]
|
|
9469
9610
|
});
|
|
9470
9611
|
}
|
|
@@ -9519,10 +9660,11 @@ class GSSplatRendererMobile {
|
|
|
9519
9660
|
208,
|
|
9520
9661
|
new Float32Array([this.renderer.width, this.renderer.height, 0, 0])
|
|
9521
9662
|
);
|
|
9663
|
+
const shEnabled = this.compressedTextures.hasSH ? 1 : 0;
|
|
9522
9664
|
device.queue.writeBuffer(
|
|
9523
9665
|
this.uniformBuffer,
|
|
9524
9666
|
224,
|
|
9525
|
-
new Float32Array([this.compressedTextures.width, this.compressedTextures.height,
|
|
9667
|
+
new Float32Array([this.compressedTextures.width, this.compressedTextures.height, shEnabled, 0])
|
|
9526
9668
|
);
|
|
9527
9669
|
this.sorter.setScreenSize(this.renderer.width, this.renderer.height);
|
|
9528
9670
|
this.sorter.setCullingOptions({
|
|
@@ -9531,7 +9673,8 @@ class GSSplatRendererMobile {
|
|
|
9531
9673
|
pixelThreshold: 1
|
|
9532
9674
|
});
|
|
9533
9675
|
const isFirstFrame = this.frameCount === 1;
|
|
9534
|
-
const
|
|
9676
|
+
const cameraChanged = this.hasCameraChanged();
|
|
9677
|
+
const shouldSort = isFirstFrame || cameraChanged && this.frameCount % this.sortEveryNFrames === 0;
|
|
9535
9678
|
if (shouldSort) {
|
|
9536
9679
|
this.sorter.sort();
|
|
9537
9680
|
}
|
|
@@ -9562,36 +9705,42 @@ class GSSplatRendererMobile {
|
|
|
9562
9705
|
setSortFrequency(n) {
|
|
9563
9706
|
this.sortEveryNFrames = Math.max(1, n);
|
|
9564
9707
|
}
|
|
9708
|
+
hasCameraChanged() {
|
|
9709
|
+
const view = this.camera.viewMatrix;
|
|
9710
|
+
const proj = this.camera.projectionMatrix;
|
|
9711
|
+
if (!this.sortStateInitialized) {
|
|
9712
|
+
this.lastSortViewMatrix.set(view);
|
|
9713
|
+
this.lastSortProjMatrix.set(proj);
|
|
9714
|
+
this.sortStateInitialized = true;
|
|
9715
|
+
return true;
|
|
9716
|
+
}
|
|
9717
|
+
for (let i = 0; i < 16; i++) {
|
|
9718
|
+
if (Math.abs(view[i] - this.lastSortViewMatrix[i]) > 1e-6 || Math.abs(proj[i] - this.lastSortProjMatrix[i]) > 1e-6) {
|
|
9719
|
+
this.lastSortViewMatrix.set(view);
|
|
9720
|
+
this.lastSortProjMatrix.set(proj);
|
|
9721
|
+
return true;
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
return false;
|
|
9725
|
+
}
|
|
9565
9726
|
// ============================================
|
|
9566
9727
|
// IGSSplatRenderer 接口实现 - SH 模式
|
|
9567
9728
|
// ============================================
|
|
9568
|
-
|
|
9569
|
-
* 设置 SH 模式(移动端仅支持 L0)
|
|
9570
|
-
*/
|
|
9571
|
-
setSHMode(mode) {
|
|
9729
|
+
setSHMode(_mode) {
|
|
9572
9730
|
}
|
|
9573
|
-
/**
|
|
9574
|
-
* 获取当前 SH 模式
|
|
9575
|
-
*/
|
|
9576
9731
|
getSHMode() {
|
|
9577
|
-
|
|
9732
|
+
var _a2;
|
|
9733
|
+
return ((_a2 = this.compressedTextures) == null ? void 0 : _a2.hasSH) ? SHMode.L1 : SHMode.L0;
|
|
9578
9734
|
}
|
|
9579
|
-
/**
|
|
9580
|
-
* 是否支持指定的 SH 模式
|
|
9581
|
-
*/
|
|
9582
9735
|
supportsSHMode(mode) {
|
|
9583
|
-
return mode === SHMode.L0;
|
|
9736
|
+
return mode === SHMode.L0 || mode === SHMode.L1;
|
|
9584
9737
|
}
|
|
9585
|
-
/**
|
|
9586
|
-
* 获取渲染器能力
|
|
9587
|
-
*/
|
|
9588
9738
|
getCapabilities() {
|
|
9589
9739
|
return {
|
|
9590
|
-
maxSHMode: SHMode.
|
|
9740
|
+
maxSHMode: SHMode.L1,
|
|
9591
9741
|
supportsRawData: false,
|
|
9592
9742
|
isMobileOptimized: true,
|
|
9593
9743
|
maxSplatCount: 0
|
|
9594
|
-
// 无限制(受 GPU 内存限制)
|
|
9595
9744
|
};
|
|
9596
9745
|
}
|
|
9597
9746
|
/**
|
|
@@ -9620,6 +9769,10 @@ class GSSplatRendererMobile {
|
|
|
9620
9769
|
*/
|
|
9621
9770
|
destroy() {
|
|
9622
9771
|
this.destroyInternal();
|
|
9772
|
+
if (this.dummySHTexture) {
|
|
9773
|
+
this.dummySHTexture.destroy();
|
|
9774
|
+
this.dummySHTexture = null;
|
|
9775
|
+
}
|
|
9623
9776
|
}
|
|
9624
9777
|
}
|
|
9625
9778
|
class SceneManager {
|
|
@@ -16898,6 +17051,63 @@ class App {
|
|
|
16898
17051
|
getSceneAidsRenderer() {
|
|
16899
17052
|
return this.sceneAids;
|
|
16900
17053
|
}
|
|
17054
|
+
// ============================================
|
|
17055
|
+
// 渲染性能控制
|
|
17056
|
+
// ============================================
|
|
17057
|
+
/**
|
|
17058
|
+
* 设置渲染缩放比例(影响内部分辨率)
|
|
17059
|
+
* 0.5 = 半分辨率(性能提升约 4 倍),1.0 = 正常
|
|
17060
|
+
* 适用于移动端提质或桌面端降负载
|
|
17061
|
+
*/
|
|
17062
|
+
setRenderScale(scale) {
|
|
17063
|
+
this.renderer.setRenderScale(scale);
|
|
17064
|
+
}
|
|
17065
|
+
getRenderScale() {
|
|
17066
|
+
return this.renderer.getRenderScale();
|
|
17067
|
+
}
|
|
17068
|
+
/**
|
|
17069
|
+
* 覆盖自动 DPR,传 -1 恢复自动推荐
|
|
17070
|
+
*/
|
|
17071
|
+
setDPR(dpr) {
|
|
17072
|
+
this.renderer.setDPR(dpr);
|
|
17073
|
+
}
|
|
17074
|
+
getDPR() {
|
|
17075
|
+
return this.renderer.getDPR();
|
|
17076
|
+
}
|
|
17077
|
+
/**
|
|
17078
|
+
* 设置桌面端亚像素剔除阈值(默认 1.0)
|
|
17079
|
+
* 值越大剔除越激进,近距离性能越好,但远处细节可能丢失
|
|
17080
|
+
*/
|
|
17081
|
+
setPixelCullThreshold(threshold) {
|
|
17082
|
+
const gsRenderer = this.getGSRenderer();
|
|
17083
|
+
if (gsRenderer) {
|
|
17084
|
+
gsRenderer.setPixelCullThreshold(threshold);
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
/**
|
|
17088
|
+
* 设置桌面端最大可见 splat 数(0 = 不限制)
|
|
17089
|
+
* 限制绘制数量是应对近距离卡顿最直接的手段
|
|
17090
|
+
*/
|
|
17091
|
+
setMaxVisibleSplats(count) {
|
|
17092
|
+
const gsRenderer = this.getGSRenderer();
|
|
17093
|
+
if (gsRenderer) {
|
|
17094
|
+
gsRenderer.setMaxVisibleSplats(count);
|
|
17095
|
+
}
|
|
17096
|
+
}
|
|
17097
|
+
/**
|
|
17098
|
+
* 设置排序频率(1 = 每帧,2 = 每两帧,以此类推)
|
|
17099
|
+
* 降低排序频率可提升帧率,代价是移动时短暂排序瑕疵
|
|
17100
|
+
*/
|
|
17101
|
+
setSortFrequency(frequency) {
|
|
17102
|
+
const gsRenderer = this.getGSRenderer();
|
|
17103
|
+
if (gsRenderer) {
|
|
17104
|
+
gsRenderer.setSortFrequency(frequency);
|
|
17105
|
+
}
|
|
17106
|
+
const mobileRenderer = this.getGSRendererMobile();
|
|
17107
|
+
if (mobileRenderer) {
|
|
17108
|
+
mobileRenderer.setSortFrequency(frequency);
|
|
17109
|
+
}
|
|
17110
|
+
}
|
|
16901
17111
|
/**
|
|
16902
17112
|
* 销毁应用及所有资源
|
|
16903
17113
|
*/
|