MikaFil commited on
Commit
61cac23
·
verified ·
1 Parent(s): 3c7eddf

Create gsplat.js

Browse files
Files changed (1) hide show
  1. gsplat.js +190 -0
gsplat.js ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // gsplat.js
2
+ // ==============================
3
+ // Basic PlayCanvas script for visualizing SOGS/GSplat points from meta.json and WebP data
4
+
5
+ var GSplat = pc.createScript('gsplat');
6
+
7
+ /**
8
+ * attributes:
9
+ * sogsData: parsed meta.json (from SOGS export)
10
+ * textures: dictionary of {name: pc.Texture}
11
+ */
12
+ GSplat.attributes.add('sogsData', { type: 'json' });
13
+ GSplat.attributes.add('textures', { type: 'object' });
14
+
15
+ GSplat.prototype.initialize = async function () {
16
+ // Prepare storage for points
17
+ this.points = [];
18
+ this.loaded = false;
19
+ this.material = null;
20
+ this.billboardMat = null;
21
+
22
+ // Load and decode all blobs/textures from sogsData
23
+ await this._loadAndDecode();
24
+
25
+ // Make a basic material for billboarding/dots
26
+ this.billboardMat = new pc.StandardMaterial();
27
+ this.billboardMat.diffuse = new pc.Color(1, 1, 0);
28
+ this.billboardMat.update();
29
+
30
+ this.loaded = true;
31
+ };
32
+
33
+ GSplat.prototype._loadAndDecode = async function () {
34
+ const data = this.sogsData;
35
+ const textures = this.textures;
36
+
37
+ // Helper to read float32 RGBA data from a texture
38
+ function decodeFloat32Texture(tex, count, channels, mins, maxs) {
39
+ // Render texture to canvas to extract RGBA
40
+ const w = tex.width;
41
+ const h = tex.height;
42
+ const canvas = document.createElement('canvas');
43
+ canvas.width = w;
44
+ canvas.height = h;
45
+ const ctx = canvas.getContext('2d');
46
+ // Draw texture to canvas
47
+ ctx.drawImage(tex.getSource(), 0, 0);
48
+ const imgData = ctx.getImageData(0, 0, w, h).data;
49
+
50
+ // Float32 in RGBA8888 encoding
51
+ // Get per-channel min/max for denormalization
52
+ // Assume channels <= 4 (RGBA)
53
+ let result = new Float32Array(count * channels);
54
+ let idx = 0;
55
+ for (let i = 0; i < count; ++i) {
56
+ for (let c = 0; c < channels; ++c) {
57
+ // Each float is 4 bytes = 4 pixels channels
58
+ const px = i * channels + c;
59
+ const pxIdx = px * 4;
60
+ // Read RGBA 4 bytes as uint32, then float32 decode
61
+ const r = imgData[pxIdx + 0];
62
+ const g = imgData[pxIdx + 1];
63
+ const b = imgData[pxIdx + 2];
64
+ const a = imgData[pxIdx + 3];
65
+ const u32 = (r << 24) | (g << 16) | (b << 8) | a;
66
+ // Unpack as float (simulate)
67
+ // Most SOGS encoders quantize to [0,255] and need rescale to [min,max]
68
+ let v = r / 255.0;
69
+ if (mins && maxs) {
70
+ v = mins[c] + v * (maxs[c] - mins[c]);
71
+ }
72
+ result[idx++] = v;
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+
78
+ // Helper to decode quaternions from uint8 webp (just as placeholder)
79
+ function decodeUint8Texture(tex, count, channels) {
80
+ const w = tex.width;
81
+ const h = tex.height;
82
+ const canvas = document.createElement('canvas');
83
+ canvas.width = w;
84
+ canvas.height = h;
85
+ const ctx = canvas.getContext('2d');
86
+ ctx.drawImage(tex.getSource(), 0, 0);
87
+ const imgData = ctx.getImageData(0, 0, w, h).data;
88
+
89
+ let result = new Uint8Array(count * channels);
90
+ let idx = 0;
91
+ for (let i = 0; i < count * channels; ++i) {
92
+ const pxIdx = i * 4;
93
+ // Use R channel
94
+ result[idx++] = imgData[pxIdx];
95
+ }
96
+ return result;
97
+ }
98
+
99
+ // Extract meta
100
+ const meansMeta = data.means;
101
+ const scalesMeta = data.scales;
102
+ const quatsMeta = data.quats;
103
+ const sh0Meta = data.sh0;
104
+
105
+ // We will show positions (means) as yellow spheres.
106
+ let numPoints = meansMeta.shape[0];
107
+
108
+ // Means: (could be split into two webp)
109
+ let means = [];
110
+ if (meansMeta.files.length === 2) {
111
+ // Combine two webp for double precision
112
+ let texL = textures[meansMeta.files[0]];
113
+ let texU = textures[meansMeta.files[1]];
114
+ if (!texL || !texU) return;
115
+ // Only using texL for now for simplicity!
116
+ means = decodeFloat32Texture(texL, numPoints, 3, meansMeta.mins, meansMeta.maxs);
117
+ } else {
118
+ let texL = textures[meansMeta.files[0]];
119
+ means = decodeFloat32Texture(texL, numPoints, 3, meansMeta.mins, meansMeta.maxs);
120
+ }
121
+
122
+ // Scales (not visualized in this demo)
123
+ // let scalesTex = textures[scalesMeta.files[0]];
124
+ // let scales = decodeFloat32Texture(scalesTex, numPoints, 3, scalesMeta.mins, scalesMeta.maxs);
125
+
126
+ // SH0 (color, first 3 channels)
127
+ let sh0Tex = textures[sh0Meta.files[0]];
128
+ let sh0 = decodeFloat32Texture(sh0Tex, numPoints, 4, sh0Meta.mins, sh0Meta.maxs);
129
+
130
+ // Compose points for visualization
131
+ this.points = [];
132
+ for (let i = 0; i < numPoints; ++i) {
133
+ // Means is a flat array [x0, y0, z0, x1, y1, z1, ...]
134
+ let px = means[i * 3 + 0];
135
+ let py = means[i * 3 + 1];
136
+ let pz = means[i * 3 + 2];
137
+
138
+ // SH0 is flat array of 4 channels: [r,g,b,a]
139
+ let r = sh0[i * 4 + 0], g = sh0[i * 4 + 1], b = sh0[i * 4 + 2];
140
+ // Splat color may need gamma correction
141
+ this.points.push({
142
+ pos: new pc.Vec3(px, py, pz),
143
+ color: new pc.Color(r, g, b)
144
+ });
145
+ }
146
+ };
147
+
148
+ // Basic draw call (draws each point as a small sphere, no instancing)
149
+ GSplat.prototype.update = function (dt) {
150
+ if (!this.loaded || !this.points.length) return;
151
+
152
+ // Only draw at most 10k points for performance in this demo
153
+ const MAX_POINTS = Math.min(10000, this.points.length);
154
+
155
+ for (let i = 0; i < MAX_POINTS; ++i) {
156
+ const pt = this.points[i];
157
+ // Draw billboard or sphere at pt.pos with color pt.color
158
+ // Replace this with instanced splatting as needed!
159
+ this._drawBillboard(pt.pos, pt.color, 0.0025);
160
+ }
161
+ };
162
+
163
+ // Helper to draw billboarded quads (replace with custom shader for real splatting)
164
+ GSplat.prototype._drawBillboard = function (pos, color, size) {
165
+ if (!this.entity || !this.entity.enabled) return;
166
+ if (!this.billboardMat) return;
167
+
168
+ // You can implement actual billboard rendering here with pc.MeshInstance
169
+ // or, for prototyping, add many child spheres (slow!)
170
+ // For fast real SOGS/gsplat: use a custom shader + single draw call.
171
+ // This is just a demonstration:
172
+
173
+ let app = this.app;
174
+ let sphere = new pc.Entity();
175
+ sphere.addComponent("model", {
176
+ type: "sphere"
177
+ });
178
+ sphere.setLocalScale(size, size, size);
179
+ sphere.setPosition(pos);
180
+ sphere.model.material = this.billboardMat.clone();
181
+ sphere.model.material.diffuse = color;
182
+ sphere.model.material.update();
183
+
184
+ this.entity.addChild(sphere);
185
+
186
+ // Clean up to avoid adding thousands of spheres per frame
187
+ setTimeout(() => {
188
+ if (sphere && sphere.destroy) sphere.destroy();
189
+ }, 2000); // Display for a short time only
190
+ };