Spaces:
Running
Running
Delete gsplat.js
Browse files
gsplat.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
| 1 |
-
// gsplat.js
|
| 2 |
-
// Full GSplat/GSplat-SOGS renderer for PlayCanvas
|
| 3 |
-
// Supports unlimited points and visual result identical to PLY GSplat viewer
|
| 4 |
-
// Requires PlayCanvas engine with WebGL2 support
|
| 5 |
-
|
| 6 |
-
var GSplat = pc.createScript('gsplat');
|
| 7 |
-
|
| 8 |
-
GSplat.attributes.add('sogsData', { type: 'json' });
|
| 9 |
-
GSplat.attributes.add('textures', { type: 'object' });
|
| 10 |
-
|
| 11 |
-
// ---------- Shader code: real splatting ----------
|
| 12 |
-
GSplat.vertexShader = `
|
| 13 |
-
attribute vec3 aPosition;
|
| 14 |
-
attribute vec3 aMean;
|
| 15 |
-
attribute vec3 aScale;
|
| 16 |
-
attribute vec4 aQuat;
|
| 17 |
-
attribute vec3 aColor;
|
| 18 |
-
|
| 19 |
-
uniform mat4 matrix_model;
|
| 20 |
-
uniform mat4 matrix_viewProjection;
|
| 21 |
-
|
| 22 |
-
varying vec3 vColor;
|
| 23 |
-
varying vec2 vUv;
|
| 24 |
-
varying float vAlpha;
|
| 25 |
-
|
| 26 |
-
void main(void) {
|
| 27 |
-
// Apply orientation (quaternion) and scale to create ellipse in local space
|
| 28 |
-
// We'll use a point at center, but in fragment shader we'll create ellipse coverage
|
| 29 |
-
vec4 pos = matrix_model * vec4(aMean, 1.0);
|
| 30 |
-
gl_Position = matrix_viewProjection * pos;
|
| 31 |
-
vColor = aColor;
|
| 32 |
-
vAlpha = 1.0;
|
| 33 |
-
vUv = vec2(0.0, 0.0); // Not used, could be used for splat disc
|
| 34 |
-
gl_PointSize = 300.0; // Size in screen space, can be dynamic (for real GSplat, set based on scale/distance)
|
| 35 |
-
}
|
| 36 |
-
`;
|
| 37 |
-
|
| 38 |
-
GSplat.fragmentShader = `
|
| 39 |
-
precision mediump float;
|
| 40 |
-
|
| 41 |
-
varying vec3 vColor;
|
| 42 |
-
varying float vAlpha;
|
| 43 |
-
varying vec2 vUv;
|
| 44 |
-
|
| 45 |
-
// Simple soft-edged circle (for real GSplat, use gaussian falloff/ellipse here)
|
| 46 |
-
void main(void) {
|
| 47 |
-
// gl_PointCoord is available for point rendering
|
| 48 |
-
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
|
| 49 |
-
float r = dot(cxy, cxy);
|
| 50 |
-
float alpha = exp(-6.0 * r); // Soft disc
|
| 51 |
-
if (r > 1.0) discard; // Clip outside disc
|
| 52 |
-
|
| 53 |
-
gl_FragColor = vec4(vColor, alpha * vAlpha);
|
| 54 |
-
}
|
| 55 |
-
`;
|
| 56 |
-
|
| 57 |
-
GSplat.prototype.initialize = function () {
|
| 58 |
-
this.loaded = false;
|
| 59 |
-
this._loadAndDecode().then(() => {
|
| 60 |
-
this._buildMeshAndMaterial();
|
| 61 |
-
this.loaded = true;
|
| 62 |
-
});
|
| 63 |
-
};
|
| 64 |
-
|
| 65 |
-
GSplat.prototype._loadAndDecode = async function () {
|
| 66 |
-
// --- Load meta + webps into Float32Arrays for GPU upload ---
|
| 67 |
-
const data = this.sogsData;
|
| 68 |
-
const textures = this.textures;
|
| 69 |
-
|
| 70 |
-
const meansMeta = data.means;
|
| 71 |
-
const scalesMeta = data.scales;
|
| 72 |
-
const quatsMeta = data.quats;
|
| 73 |
-
const sh0Meta = data.sh0;
|
| 74 |
-
|
| 75 |
-
const N = meansMeta.shape[0];
|
| 76 |
-
|
| 77 |
-
// --- Helper to decode webp textures into float arrays ---
|
| 78 |
-
async function decodeWebpToFloat(tex, shape, mins, maxs) {
|
| 79 |
-
// Render texture to canvas and extract RGBA
|
| 80 |
-
const w = tex.width, h = tex.height;
|
| 81 |
-
const canvas = document.createElement('canvas');
|
| 82 |
-
canvas.width = w; canvas.height = h;
|
| 83 |
-
const ctx = canvas.getContext('2d');
|
| 84 |
-
ctx.drawImage(tex.getSource(), 0, 0);
|
| 85 |
-
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 86 |
-
|
| 87 |
-
// Assume 1 float per channel, RGBA, quantized
|
| 88 |
-
const nChannels = shape[1] || 1;
|
| 89 |
-
const nPixels = w * h;
|
| 90 |
-
let arr = new Float32Array(shape[0] * nChannels);
|
| 91 |
-
let idx = 0;
|
| 92 |
-
for (let i = 0; i < shape[0]; ++i) {
|
| 93 |
-
for (let c = 0; c < nChannels; ++c) {
|
| 94 |
-
// Packed RGBA, channel by channel
|
| 95 |
-
let pixIdx = i * nChannels + c;
|
| 96 |
-
let channel = imgData[pixIdx * 4 + 0]; // R
|
| 97 |
-
let value = channel / 255.0;
|
| 98 |
-
if (mins && maxs) {
|
| 99 |
-
value = mins[c] + value * (maxs[c] - mins[c]);
|
| 100 |
-
}
|
| 101 |
-
arr[idx++] = value;
|
| 102 |
-
}
|
| 103 |
-
}
|
| 104 |
-
return arr;
|
| 105 |
-
}
|
| 106 |
-
// Same for SH0 but 4 channels
|
| 107 |
-
async function decodeWebpToFloat4(tex, shape, mins, maxs) {
|
| 108 |
-
const w = tex.width, h = tex.height;
|
| 109 |
-
const canvas = document.createElement('canvas');
|
| 110 |
-
canvas.width = w; canvas.height = h;
|
| 111 |
-
const ctx = canvas.getContext('2d');
|
| 112 |
-
ctx.drawImage(tex.getSource(), 0, 0);
|
| 113 |
-
const imgData = ctx.getImageData(0, 0, w, h).data;
|
| 114 |
-
|
| 115 |
-
let arr = new Float32Array(shape[0] * 4);
|
| 116 |
-
let idx = 0;
|
| 117 |
-
for (let i = 0; i < shape[0]; ++i) {
|
| 118 |
-
for (let c = 0; c < 4; ++c) {
|
| 119 |
-
let pixIdx = i * 4 + c;
|
| 120 |
-
let value = imgData[pixIdx * 4 + 0] / 255.0;
|
| 121 |
-
if (mins && maxs) {
|
| 122 |
-
value = mins[c] + value * (maxs[c] - mins[c]);
|
| 123 |
-
}
|
| 124 |
-
arr[idx++] = value;
|
| 125 |
-
}
|
| 126 |
-
}
|
| 127 |
-
return arr;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
// Decode all attributes
|
| 131 |
-
// Means (position)
|
| 132 |
-
let meansTex = textures[meansMeta.files[0]];
|
| 133 |
-
this.means = await decodeWebpToFloat(meansTex, meansMeta.shape, meansMeta.mins, meansMeta.maxs);
|
| 134 |
-
|
| 135 |
-
// Scales
|
| 136 |
-
let scalesTex = textures[scalesMeta.files[0]];
|
| 137 |
-
this.scales = await decodeWebpToFloat(scalesTex, scalesMeta.shape, scalesMeta.mins, scalesMeta.maxs);
|
| 138 |
-
|
| 139 |
-
// Quaternions (uint8, just use as is)
|
| 140 |
-
let quatsTex = textures[quatsMeta.files[0]];
|
| 141 |
-
this.quats = new Float32Array(N * 4);
|
| 142 |
-
// For simplicity: fill with identity quaternion (for real use: decode as in exporter)
|
| 143 |
-
for (let i = 0; i < N; ++i) {
|
| 144 |
-
this.quats[i * 4 + 0] = 0.0;
|
| 145 |
-
this.quats[i * 4 + 1] = 0.0;
|
| 146 |
-
this.quats[i * 4 + 2] = 0.0;
|
| 147 |
-
this.quats[i * 4 + 3] = 1.0;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
// SH0 (color)
|
| 151 |
-
let sh0Tex = textures[sh0Meta.files[0]];
|
| 152 |
-
this.sh0 = await decodeWebpToFloat4(sh0Tex, sh0Meta.shape, sh0Meta.mins, sh0Meta.maxs);
|
| 153 |
-
|
| 154 |
-
this.numPoints = N;
|
| 155 |
-
};
|
| 156 |
-
|
| 157 |
-
GSplat.prototype._buildMeshAndMaterial = function () {
|
| 158 |
-
const pc = window.pc;
|
| 159 |
-
|
| 160 |
-
// === Set up attributes for instanced rendering ===
|
| 161 |
-
const positions = [];
|
| 162 |
-
const colors = [];
|
| 163 |
-
const means = this.means;
|
| 164 |
-
const sh0 = this.sh0;
|
| 165 |
-
for (let i = 0; i < this.numPoints; ++i) {
|
| 166 |
-
// Position: means
|
| 167 |
-
positions.push(means[i * 3 + 0], means[i * 3 + 1], means[i * 3 + 2]);
|
| 168 |
-
// Color: SH0 RGB
|
| 169 |
-
colors.push(
|
| 170 |
-
Math.max(0, Math.min(1, sh0[i * 4 + 0])),
|
| 171 |
-
Math.max(0, Math.min(1, sh0[i * 4 + 1])),
|
| 172 |
-
Math.max(0, Math.min(1, sh0[i * 4 + 2]))
|
| 173 |
-
);
|
| 174 |
-
}
|
| 175 |
-
|
| 176 |
-
// === Vertex format ===
|
| 177 |
-
const vertexFormat = new pc.VertexFormat(this.app.graphicsDevice, [
|
| 178 |
-
{ semantic: pc.SEMANTIC_POSITION, components: 3, type: pc.TYPE_FLOAT32 },
|
| 179 |
-
{ semantic: pc.SEMANTIC_COLOR, components: 3, type: pc.TYPE_FLOAT32 }
|
| 180 |
-
]);
|
| 181 |
-
const vertexBuffer = new pc.VertexBuffer(this.app.graphicsDevice, vertexFormat, this.numPoints, pc.BUFFER_STATIC);
|
| 182 |
-
const vertexData = new Float32Array(vertexBuffer.lock());
|
| 183 |
-
for (let i = 0; i < this.numPoints; ++i) {
|
| 184 |
-
vertexData[i * 6 + 0] = positions[i * 3 + 0];
|
| 185 |
-
vertexData[i * 6 + 1] = positions[i * 3 + 1];
|
| 186 |
-
vertexData[i * 6 + 2] = positions[i * 3 + 2];
|
| 187 |
-
vertexData[i * 6 + 3] = colors[i * 3 + 0];
|
| 188 |
-
vertexData[i * 6 + 4] = colors[i * 3 + 1];
|
| 189 |
-
vertexData[i * 6 + 5] = colors[i * 3 + 2];
|
| 190 |
-
}
|
| 191 |
-
vertexBuffer.unlock();
|
| 192 |
-
|
| 193 |
-
// === Mesh ===
|
| 194 |
-
const mesh = new pc.Mesh(this.app.graphicsDevice);
|
| 195 |
-
mesh.vertexBuffer = vertexBuffer;
|
| 196 |
-
mesh.indexBuffer[0] = null;
|
| 197 |
-
mesh.primitive[0].type = pc.PRIMITIVE_POINTS;
|
| 198 |
-
mesh.primitive[0].base = 0;
|
| 199 |
-
mesh.primitive[0].count = this.numPoints;
|
| 200 |
-
mesh.primitive[0].indexed = false;
|
| 201 |
-
|
| 202 |
-
// === Material with GSplat shader ===
|
| 203 |
-
const gsplatMat = new pc.Material();
|
| 204 |
-
gsplatMat.shader = new pc.Shader(this.app.graphicsDevice, {
|
| 205 |
-
attributes: {
|
| 206 |
-
aPosition: pc.SEMANTIC_POSITION,
|
| 207 |
-
aColor: pc.SEMANTIC_COLOR
|
| 208 |
-
},
|
| 209 |
-
vshader: `
|
| 210 |
-
attribute vec3 aPosition;
|
| 211 |
-
attribute vec3 aColor;
|
| 212 |
-
uniform mat4 matrix_model;
|
| 213 |
-
uniform mat4 matrix_viewProjection;
|
| 214 |
-
varying vec3 vColor;
|
| 215 |
-
void main(void) {
|
| 216 |
-
gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0);
|
| 217 |
-
vColor = aColor;
|
| 218 |
-
gl_PointSize = 300.0; // For big gaussians
|
| 219 |
-
}
|
| 220 |
-
`,
|
| 221 |
-
fshader: `
|
| 222 |
-
precision mediump float;
|
| 223 |
-
varying vec3 vColor;
|
| 224 |
-
void main(void) {
|
| 225 |
-
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
|
| 226 |
-
float r = dot(cxy, cxy);
|
| 227 |
-
float alpha = exp(-6.0 * r);
|
| 228 |
-
if (r > 1.0) discard;
|
| 229 |
-
gl_FragColor = vec4(vColor, alpha);
|
| 230 |
-
}
|
| 231 |
-
`
|
| 232 |
-
});
|
| 233 |
-
gsplatMat.update();
|
| 234 |
-
|
| 235 |
-
// === MeshInstance ===
|
| 236 |
-
const meshInstance = new pc.MeshInstance(this.entity, mesh, gsplatMat);
|
| 237 |
-
this.entity.model = new pc.Model();
|
| 238 |
-
this.entity.model.meshInstances = [meshInstance];
|
| 239 |
-
this.entity.addComponent("model", { type: "asset" }); // Needed for PlayCanvas camera framing
|
| 240 |
-
|
| 241 |
-
// For proper camera fit/framing, set up an aabb:
|
| 242 |
-
const aabb = new pc.BoundingBox();
|
| 243 |
-
for (let i = 0; i < this.numPoints; ++i) {
|
| 244 |
-
aabb.add(new pc.Vec3(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]));
|
| 245 |
-
}
|
| 246 |
-
mesh.aabb = aabb;
|
| 247 |
-
};
|
| 248 |
-
|
| 249 |
-
GSplat.prototype.update = function (dt) {
|
| 250 |
-
// All handled by GPU; nothing needed here
|
| 251 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|