@myned-ai/gsplat-flame-avatar-renderer 1.0.1 → 1.0.4
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/README.md +4 -29
- package/dist/gsplat-flame-avatar-renderer.cjs.js +12875 -0
- package/dist/{gsplat-flame-avatar-renderer.umd.js.map → gsplat-flame-avatar-renderer.cjs.js.map} +1 -1
- package/dist/gsplat-flame-avatar-renderer.esm.js +1 -1
- package/package.json +5 -3
- package/src/api/index.js +7 -0
- package/src/buffers/SplatBuffer.js +1394 -0
- package/src/buffers/SplatBufferGenerator.js +41 -0
- package/src/buffers/SplatPartitioner.js +110 -0
- package/src/buffers/UncompressedSplatArray.js +106 -0
- package/src/buffers/index.js +11 -0
- package/src/core/SplatGeometry.js +48 -0
- package/src/core/SplatMesh.js +2620 -0
- package/src/core/SplatScene.js +43 -0
- package/src/core/SplatTree.js +200 -0
- package/src/core/Viewer.js +2895 -0
- package/src/core/index.js +13 -0
- package/src/enums/EngineConstants.js +58 -0
- package/src/enums/LogLevel.js +13 -0
- package/src/enums/RenderMode.js +11 -0
- package/src/enums/SceneFormat.js +21 -0
- package/src/enums/SceneRevealMode.js +11 -0
- package/src/enums/SplatRenderMode.js +10 -0
- package/src/enums/index.js +13 -0
- package/src/flame/FlameAnimator.js +271 -0
- package/src/flame/FlameConstants.js +21 -0
- package/src/flame/FlameTextureManager.js +293 -0
- package/src/flame/index.js +22 -0
- package/src/flame/utils.js +50 -0
- package/src/index.js +39 -0
- package/src/loaders/DirectLoadError.js +14 -0
- package/src/loaders/INRIAV1PlyParser.js +223 -0
- package/src/loaders/PlyLoader.js +261 -0
- package/src/loaders/PlyParser.js +19 -0
- package/src/loaders/PlyParserUtils.js +311 -0
- package/src/loaders/index.js +13 -0
- package/src/materials/SplatMaterial.js +1065 -0
- package/src/materials/SplatMaterial2D.js +358 -0
- package/src/materials/SplatMaterial3D.js +278 -0
- package/src/materials/index.js +11 -0
- package/src/raycaster/Hit.js +37 -0
- package/src/raycaster/Ray.js +123 -0
- package/src/raycaster/Raycaster.js +175 -0
- package/src/raycaster/index.js +10 -0
- package/src/renderer/AnimationManager.js +574 -0
- package/src/renderer/AppConstants.js +101 -0
- package/src/renderer/GaussianSplatRenderer.js +695 -0
- package/src/renderer/index.js +24 -0
- package/src/utils/LoaderUtils.js +65 -0
- package/src/utils/Util.js +375 -0
- package/src/utils/index.js +9 -0
- package/src/worker/SortWorker.js +284 -0
- package/src/worker/index.js +8 -0
- package/dist/gsplat-flame-avatar-renderer.esm.min.js +0 -2
- package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +0 -1
- package/dist/gsplat-flame-avatar-renderer.umd.js +0 -12876
- package/dist/gsplat-flame-avatar-renderer.umd.min.js +0 -2
- package/dist/gsplat-flame-avatar-renderer.umd.min.js.map +0 -1
- package/dist/gsplat-flame-avatar.esm.js +0 -12755
- package/dist/gsplat-flame-avatar.esm.js.map +0 -1
- package/dist/gsplat-flame-avatar.esm.min.js +0 -2
- package/dist/gsplat-flame-avatar.esm.min.js.map +0 -1
- package/dist/gsplat-flame-avatar.umd.js +0 -12876
- package/dist/gsplat-flame-avatar.umd.js.map +0 -1
- package/dist/gsplat-flame-avatar.umd.min.js +0 -2
- package/dist/gsplat-flame-avatar.umd.min.js.map +0 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SplatMaterial2D
|
|
3
|
+
*
|
|
4
|
+
* Based on @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
+
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
+
*
|
|
7
|
+
* Modified for FLAME avatar support:
|
|
8
|
+
* - Extended vertex shader for FLAME integration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DoubleSide, NormalBlending, ShaderMaterial, Vector2 } from 'three';
|
|
12
|
+
import { SplatMaterial } from './SplatMaterial.js';
|
|
13
|
+
|
|
14
|
+
export class SplatMaterial2D {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build the Three.js material that is used to render the splats.
|
|
18
|
+
* @param {number} dynamicMode If true, it means the scene geometry represented by this splat mesh is not stationary or
|
|
19
|
+
* that the splat count might change
|
|
20
|
+
* @param {boolean} enableOptionalEffects When true, allows for usage of extra properties and attributes in the shader for effects
|
|
21
|
+
* such as opacity adjustment. Default is false for performance reasons.
|
|
22
|
+
* @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0)
|
|
23
|
+
* @param {number} pointCloudModeEnabled Render all splats as screen-space circles
|
|
24
|
+
* @param {number} maxSphericalHarmonicsDegree Degree of spherical harmonics to utilize in rendering splats
|
|
25
|
+
* @return {THREE.ShaderMaterial}
|
|
26
|
+
*/
|
|
27
|
+
static build(dynamicMode = false, enableOptionalEffects = false, splatScale = 1.0,
|
|
28
|
+
pointCloudModeEnabled = false, maxSphericalHarmonicsDegree = 0) {
|
|
29
|
+
|
|
30
|
+
const customVertexVars = `
|
|
31
|
+
uniform vec2 scaleRotationsTextureSize;
|
|
32
|
+
uniform highp sampler2D scaleRotationsTexture;
|
|
33
|
+
varying mat3 vT;
|
|
34
|
+
varying vec2 vQuadCenter;
|
|
35
|
+
varying vec2 vFragCoord;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
let vertexShaderSource = SplatMaterial.buildVertexShaderBase(dynamicMode, enableOptionalEffects,
|
|
39
|
+
maxSphericalHarmonicsDegree, customVertexVars);
|
|
40
|
+
vertexShaderSource += SplatMaterial2D.buildVertexShaderProjection();
|
|
41
|
+
const fragmentShaderSource = SplatMaterial2D.buildFragmentShader();
|
|
42
|
+
|
|
43
|
+
const uniforms = SplatMaterial.getUniforms(dynamicMode, enableOptionalEffects,
|
|
44
|
+
maxSphericalHarmonicsDegree, splatScale, pointCloudModeEnabled);
|
|
45
|
+
|
|
46
|
+
uniforms['scaleRotationsTexture'] = {
|
|
47
|
+
'type': 't',
|
|
48
|
+
'value': null
|
|
49
|
+
};
|
|
50
|
+
uniforms['scaleRotationsTextureSize'] = {
|
|
51
|
+
'type': 'v2',
|
|
52
|
+
'value': new Vector2(1024, 1024)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const material = new ShaderMaterial({
|
|
56
|
+
uniforms: uniforms,
|
|
57
|
+
vertexShader: vertexShaderSource,
|
|
58
|
+
fragmentShader: fragmentShaderSource,
|
|
59
|
+
transparent: true,
|
|
60
|
+
alphaTest: 1.0,
|
|
61
|
+
blending: NormalBlending,
|
|
62
|
+
depthTest: true,
|
|
63
|
+
depthWrite: false,
|
|
64
|
+
side: DoubleSide
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return material;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static buildVertexShaderProjection() {
|
|
71
|
+
|
|
72
|
+
// Original CUDA code for calculating splat-to-screen transformation, for reference
|
|
73
|
+
/*
|
|
74
|
+
glm::mat3 R = quat_to_rotmat(rot);
|
|
75
|
+
glm::mat3 S = scale_to_mat(scale, mod);
|
|
76
|
+
glm::mat3 L = R * S;
|
|
77
|
+
|
|
78
|
+
// center of Gaussians in the camera coordinate
|
|
79
|
+
glm::mat3x4 splat2world = glm::mat3x4(
|
|
80
|
+
glm::vec4(L[0], 0.0),
|
|
81
|
+
glm::vec4(L[1], 0.0),
|
|
82
|
+
glm::vec4(p_orig.x, p_orig.y, p_orig.z, 1)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
glm::mat4 world2ndc = glm::mat4(
|
|
86
|
+
projmatrix[0], projmatrix[4], projmatrix[8], projmatrix[12],
|
|
87
|
+
projmatrix[1], projmatrix[5], projmatrix[9], projmatrix[13],
|
|
88
|
+
projmatrix[2], projmatrix[6], projmatrix[10], projmatrix[14],
|
|
89
|
+
projmatrix[3], projmatrix[7], projmatrix[11], projmatrix[15]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
glm::mat3x4 ndc2pix = glm::mat3x4(
|
|
93
|
+
glm::vec4(float(W) / 2.0, 0.0, 0.0, float(W-1) / 2.0),
|
|
94
|
+
glm::vec4(0.0, float(H) / 2.0, 0.0, float(H-1) / 2.0),
|
|
95
|
+
glm::vec4(0.0, 0.0, 0.0, 1.0)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
T = glm::transpose(splat2world) * world2ndc * ndc2pix;
|
|
99
|
+
normal = transformVec4x3({L[2].x, L[2].y, L[2].z}, viewmatrix);
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
// Compute a 2D-to-2D mapping matrix from a tangent plane into a image plane
|
|
103
|
+
// given a 2D gaussian parameters. T = WH (from the paper: https://arxiv.org/pdf/2403.17888)
|
|
104
|
+
let vertexShaderSource = `
|
|
105
|
+
|
|
106
|
+
vec4 scaleRotationA = texture(scaleRotationsTexture, getDataUVF(nearestEvenIndex, 1.5,
|
|
107
|
+
oddOffset, scaleRotationsTextureSize));
|
|
108
|
+
vec4 scaleRotationB = texture(scaleRotationsTexture, getDataUVF(nearestEvenIndex, 1.5,
|
|
109
|
+
oddOffset + uint(1), scaleRotationsTextureSize));
|
|
110
|
+
|
|
111
|
+
vec3 scaleRotation123 = vec3(scaleRotationA.rgb) * (1.0 - fOddOffset) +
|
|
112
|
+
vec3(scaleRotationA.ba, scaleRotationB.r) * fOddOffset;
|
|
113
|
+
vec3 scaleRotation456 = vec3(scaleRotationA.a, scaleRotationB.rg) * (1.0 - fOddOffset) +
|
|
114
|
+
vec3(scaleRotationB.gba) * fOddOffset;
|
|
115
|
+
|
|
116
|
+
float missingW = sqrt(1.0 - scaleRotation456.x * scaleRotation456.x - scaleRotation456.y *
|
|
117
|
+
scaleRotation456.y - scaleRotation456.z * scaleRotation456.z);
|
|
118
|
+
mat3 R = quaternionToRotationMatrix(scaleRotation456.r, scaleRotation456.g, scaleRotation456.b, missingW);
|
|
119
|
+
mat3 S = mat3(scaleRotation123.r, 0.0, 0.0,
|
|
120
|
+
0.0, scaleRotation123.g, 0.0,
|
|
121
|
+
0.0, 0.0, scaleRotation123.b);
|
|
122
|
+
|
|
123
|
+
mat3 L = R * S;
|
|
124
|
+
|
|
125
|
+
mat3x4 splat2World = mat3x4(vec4(L[0], 0.0),
|
|
126
|
+
vec4(L[1], 0.0),
|
|
127
|
+
vec4(splatCenter.x, splatCenter.y, splatCenter.z, 1.0));
|
|
128
|
+
|
|
129
|
+
mat4 world2ndc = transpose(projectionMatrix * transformModelViewMatrix);
|
|
130
|
+
|
|
131
|
+
mat3x4 ndc2pix = mat3x4(vec4(viewport.x / 2.0, 0.0, 0.0, (viewport.x - 1.0) / 2.0),
|
|
132
|
+
vec4(0.0, viewport.y / 2.0, 0.0, (viewport.y - 1.0) / 2.0),
|
|
133
|
+
vec4(0.0, 0.0, 0.0, 1.0));
|
|
134
|
+
|
|
135
|
+
mat3 T = transpose(splat2World) * world2ndc * ndc2pix;
|
|
136
|
+
vec3 normal = vec3(viewMatrix * vec4(L[0][2], L[1][2], L[2][2], 0.0));
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
// Original CUDA code for projection to 2D, for reference
|
|
140
|
+
/*
|
|
141
|
+
float3 T0 = {T[0][0], T[0][1], T[0][2]};
|
|
142
|
+
float3 T1 = {T[1][0], T[1][1], T[1][2]};
|
|
143
|
+
float3 T3 = {T[2][0], T[2][1], T[2][2]};
|
|
144
|
+
|
|
145
|
+
// Compute AABB
|
|
146
|
+
float3 temp_point = {1.0f, 1.0f, -1.0f};
|
|
147
|
+
float distance = sumf3(T3 * T3 * temp_point);
|
|
148
|
+
float3 f = (1 / distance) * temp_point;
|
|
149
|
+
if (distance == 0.0) return false;
|
|
150
|
+
|
|
151
|
+
point_image = {
|
|
152
|
+
sumf3(f * T0 * T3),
|
|
153
|
+
sumf3(f * T1 * T3)
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
float2 temp = {
|
|
157
|
+
sumf3(f * T0 * T0),
|
|
158
|
+
sumf3(f * T1 * T1)
|
|
159
|
+
};
|
|
160
|
+
float2 half_extend = point_image * point_image - temp;
|
|
161
|
+
extent = sqrtf2(maxf2(1e-4, half_extend));
|
|
162
|
+
return true;
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
// Computing the bounding box of the 2D Gaussian and its center
|
|
166
|
+
// The center of the bounding box is used to create a low pass filter.
|
|
167
|
+
// This code is based off the reference implementation and creates an AABB aligned
|
|
168
|
+
// with the screen for the quad to be rendered.
|
|
169
|
+
const referenceQuadGeneration = `
|
|
170
|
+
vec3 T0 = vec3(T[0][0], T[0][1], T[0][2]);
|
|
171
|
+
vec3 T1 = vec3(T[1][0], T[1][1], T[1][2]);
|
|
172
|
+
vec3 T3 = vec3(T[2][0], T[2][1], T[2][2]);
|
|
173
|
+
|
|
174
|
+
vec3 tempPoint = vec3(1.0, 1.0, -1.0);
|
|
175
|
+
float distance = (T3.x * T3.x * tempPoint.x) + (T3.y * T3.y * tempPoint.y) + (T3.z * T3.z * tempPoint.z);
|
|
176
|
+
vec3 f = (1.0 / distance) * tempPoint;
|
|
177
|
+
if (abs(distance) < 0.00001) return;
|
|
178
|
+
|
|
179
|
+
float pointImageX = (T0.x * T3.x * f.x) + (T0.y * T3.y * f.y) + (T0.z * T3.z * f.z);
|
|
180
|
+
float pointImageY = (T1.x * T3.x * f.x) + (T1.y * T3.y * f.y) + (T1.z * T3.z * f.z);
|
|
181
|
+
vec2 pointImage = vec2(pointImageX, pointImageY);
|
|
182
|
+
|
|
183
|
+
float tempX = (T0.x * T0.x * f.x) + (T0.y * T0.y * f.y) + (T0.z * T0.z * f.z);
|
|
184
|
+
float tempY = (T1.x * T1.x * f.x) + (T1.y * T1.y * f.y) + (T1.z * T1.z * f.z);
|
|
185
|
+
vec2 temp = vec2(tempX, tempY);
|
|
186
|
+
|
|
187
|
+
vec2 halfExtend = pointImage * pointImage - temp;
|
|
188
|
+
vec2 extent = sqrt(max(vec2(0.0001), halfExtend));
|
|
189
|
+
float radius = max(extent.x, extent.y);
|
|
190
|
+
|
|
191
|
+
vec2 ndcOffset = ((position.xy * radius * 3.0) * basisViewport * 2.0);
|
|
192
|
+
|
|
193
|
+
vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
|
|
194
|
+
gl_Position = quadPos;
|
|
195
|
+
|
|
196
|
+
vT = T;
|
|
197
|
+
vQuadCenter = pointImage;
|
|
198
|
+
vFragCoord = (quadPos.xy * 0.5 + 0.5) * viewport;
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const useRefImplementation = false;
|
|
202
|
+
if (useRefImplementation) {
|
|
203
|
+
vertexShaderSource += referenceQuadGeneration;
|
|
204
|
+
} else {
|
|
205
|
+
// Create a quad that is aligned with the eigen vectors of the projected gaussian for rendering.
|
|
206
|
+
// This is a different approach than the reference implementation, similar to how the rendering of
|
|
207
|
+
// 3D gaussians in this viewer differs from the reference implementation. If the quad is too small
|
|
208
|
+
// (smaller than a pixel), then revert to the reference implementation.
|
|
209
|
+
vertexShaderSource += `
|
|
210
|
+
|
|
211
|
+
mat4 splat2World4 = mat4(vec4(L[0], 0.0),
|
|
212
|
+
vec4(L[1], 0.0),
|
|
213
|
+
vec4(L[2], 0.0),
|
|
214
|
+
vec4(splatCenter.x, splatCenter.y, splatCenter.z, 1.0));
|
|
215
|
+
|
|
216
|
+
mat4 Tt = transpose(transpose(splat2World4) * world2ndc);
|
|
217
|
+
|
|
218
|
+
vec4 tempPoint1 = Tt * vec4(1.0, 0.0, 0.0, 1.0);
|
|
219
|
+
tempPoint1 /= tempPoint1.w;
|
|
220
|
+
|
|
221
|
+
vec4 tempPoint2 = Tt * vec4(0.0, 1.0, 0.0, 1.0);
|
|
222
|
+
tempPoint2 /= tempPoint2.w;
|
|
223
|
+
|
|
224
|
+
vec4 center = Tt * vec4(0.0, 0.0, 0.0, 1.0);
|
|
225
|
+
center /= center.w;
|
|
226
|
+
|
|
227
|
+
vec2 basisVector1 = tempPoint1.xy - center.xy;
|
|
228
|
+
vec2 basisVector2 = tempPoint2.xy - center.xy;
|
|
229
|
+
|
|
230
|
+
vec2 basisVector1Screen = basisVector1 * 0.5 * viewport;
|
|
231
|
+
vec2 basisVector2Screen = basisVector2 * 0.5 * viewport;
|
|
232
|
+
|
|
233
|
+
const float minPix = 1.;
|
|
234
|
+
if (length(basisVector1Screen) < minPix || length(basisVector2Screen) < minPix) {
|
|
235
|
+
${referenceQuadGeneration}
|
|
236
|
+
} else {
|
|
237
|
+
vec2 ndcOffset = vec2(position.x * basisVector1 + position.y * basisVector2) * 3.0 * inverseFocalAdjustment;
|
|
238
|
+
vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
|
|
239
|
+
gl_Position = quadPos;
|
|
240
|
+
|
|
241
|
+
vT = T;
|
|
242
|
+
vQuadCenter = center.xy;
|
|
243
|
+
vFragCoord = (quadPos.xy * 0.5 + 0.5) * viewport;
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
vertexShaderSource += SplatMaterial.getVertexShaderFadeIn();
|
|
249
|
+
vertexShaderSource += `}`;
|
|
250
|
+
|
|
251
|
+
return vertexShaderSource;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static buildFragmentShader() {
|
|
255
|
+
|
|
256
|
+
// Original CUDA code for splat intersection, for reference
|
|
257
|
+
/*
|
|
258
|
+
const float2 xy = collected_xy[j];
|
|
259
|
+
const float3 Tu = collected_Tu[j];
|
|
260
|
+
const float3 Tv = collected_Tv[j];
|
|
261
|
+
const float3 Tw = collected_Tw[j];
|
|
262
|
+
float3 k = pix.x * Tw - Tu;
|
|
263
|
+
float3 l = pix.y * Tw - Tv;
|
|
264
|
+
float3 p = cross(k, l);
|
|
265
|
+
if (p.z == 0.0) continue;
|
|
266
|
+
float2 s = {p.x / p.z, p.y / p.z};
|
|
267
|
+
float rho3d = (s.x * s.x + s.y * s.y);
|
|
268
|
+
float2 d = {xy.x - pixf.x, xy.y - pixf.y};
|
|
269
|
+
float rho2d = FilterInvSquare * (d.x * d.x + d.y * d.y);
|
|
270
|
+
|
|
271
|
+
// compute intersection and depth
|
|
272
|
+
float rho = min(rho3d, rho2d);
|
|
273
|
+
float depth = (rho3d <= rho2d) ? (s.x * Tw.x + s.y * Tw.y) + Tw.z : Tw.z;
|
|
274
|
+
if (depth < near_n) continue;
|
|
275
|
+
float4 nor_o = collected_normal_opacity[j];
|
|
276
|
+
float normal[3] = {nor_o.x, nor_o.y, nor_o.z};
|
|
277
|
+
float opa = nor_o.w;
|
|
278
|
+
|
|
279
|
+
float power = -0.5f * rho;
|
|
280
|
+
if (power > 0.0f)
|
|
281
|
+
continue;
|
|
282
|
+
|
|
283
|
+
// Eq. (2) from 3D Gaussian splatting paper.
|
|
284
|
+
// Obtain alpha by multiplying with Gaussian opacity
|
|
285
|
+
// and its exponential falloff from mean.
|
|
286
|
+
// Avoid numerical instabilities (see paper appendix).
|
|
287
|
+
float alpha = min(0.99f, opa * exp(power));
|
|
288
|
+
if (alpha < 1.0f / 255.0f)
|
|
289
|
+
continue;
|
|
290
|
+
float test_T = T * (1 - alpha);
|
|
291
|
+
if (test_T < 0.0001f)
|
|
292
|
+
{
|
|
293
|
+
done = true;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
float w = alpha * T;
|
|
298
|
+
*/
|
|
299
|
+
let fragmentShaderSource = `
|
|
300
|
+
precision highp float;
|
|
301
|
+
#include <common>
|
|
302
|
+
|
|
303
|
+
uniform vec3 debugColor;
|
|
304
|
+
|
|
305
|
+
varying vec4 vColor;
|
|
306
|
+
varying vec2 vUv;
|
|
307
|
+
varying vec2 vPosition;
|
|
308
|
+
varying mat3 vT;
|
|
309
|
+
varying vec2 vQuadCenter;
|
|
310
|
+
varying vec2 vFragCoord;
|
|
311
|
+
|
|
312
|
+
void main () {
|
|
313
|
+
|
|
314
|
+
const float FilterInvSquare = 2.0;
|
|
315
|
+
const float near_n = 0.2;
|
|
316
|
+
const float T = 1.0;
|
|
317
|
+
|
|
318
|
+
vec2 xy = vQuadCenter;
|
|
319
|
+
vec3 Tu = vT[0];
|
|
320
|
+
vec3 Tv = vT[1];
|
|
321
|
+
vec3 Tw = vT[2];
|
|
322
|
+
vec3 k = vFragCoord.x * Tw - Tu;
|
|
323
|
+
vec3 l = vFragCoord.y * Tw - Tv;
|
|
324
|
+
vec3 p = cross(k, l);
|
|
325
|
+
if (p.z == 0.0) discard;
|
|
326
|
+
vec2 s = vec2(p.x / p.z, p.y / p.z);
|
|
327
|
+
float rho3d = (s.x * s.x + s.y * s.y);
|
|
328
|
+
vec2 d = vec2(xy.x - vFragCoord.x, xy.y - vFragCoord.y);
|
|
329
|
+
float rho2d = FilterInvSquare * (d.x * d.x + d.y * d.y);
|
|
330
|
+
|
|
331
|
+
// compute intersection and depth
|
|
332
|
+
float rho = min(rho3d, rho2d);
|
|
333
|
+
float depth = (rho3d <= rho2d) ? (s.x * Tw.x + s.y * Tw.y) + Tw.z : Tw.z;
|
|
334
|
+
if (depth < near_n) discard;
|
|
335
|
+
// vec4 nor_o = collected_normal_opacity[j];
|
|
336
|
+
// float normal[3] = {nor_o.x, nor_o.y, nor_o.z};
|
|
337
|
+
float opa = vColor.a;
|
|
338
|
+
|
|
339
|
+
float power = -0.5f * rho;
|
|
340
|
+
if (power > 0.0f) discard;
|
|
341
|
+
|
|
342
|
+
// Eq. (2) from 3D Gaussian splatting paper.
|
|
343
|
+
// Obtain alpha by multiplying with Gaussian opacity
|
|
344
|
+
// and its exponential falloff from mean.
|
|
345
|
+
// Avoid numerical instabilities (see paper appendix).
|
|
346
|
+
float alpha = min(0.99f, opa * exp(power));
|
|
347
|
+
if (alpha < 1.0f / 255.0f) discard;
|
|
348
|
+
float test_T = T * (1.0 - alpha);
|
|
349
|
+
if (test_T < 0.0001)discard;
|
|
350
|
+
|
|
351
|
+
float w = alpha * T;
|
|
352
|
+
gl_FragColor = vec4(vColor.rgb, w);
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
return fragmentShaderSource;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SplatMaterial3D
|
|
3
|
+
*
|
|
4
|
+
* Based on @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
+
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
+
*
|
|
7
|
+
* Modified for FLAME avatar support:
|
|
8
|
+
* - Extended vertex shader for FLAME integration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DoubleSide, NormalBlending, ShaderMaterial, Vector2 } from 'three';
|
|
12
|
+
import { SplatMaterial } from './SplatMaterial.js';
|
|
13
|
+
|
|
14
|
+
export class SplatMaterial3D {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build the Three.js material that is used to render the splats.
|
|
18
|
+
* @param {number} dynamicMode If true, it means the scene geometry represented by this splat mesh is not stationary or
|
|
19
|
+
* that the splat count might change
|
|
20
|
+
* @param {boolean} enableOptionalEffects When true, allows for usage of extra properties and attributes in the shader for effects
|
|
21
|
+
* such as opacity adjustment. Default is false for performance reasons.
|
|
22
|
+
* @param {boolean} antialiased If true, calculate compensation factor to deal with gaussians being rendered at a significantly
|
|
23
|
+
* different resolution than that of their training
|
|
24
|
+
* @param {number} maxScreenSpaceSplatSize The maximum clip space splat size
|
|
25
|
+
* @param {number} splatScale Value by which all splats are scaled in screen-space (default is 1.0)
|
|
26
|
+
* @param {number} pointCloudModeEnabled Render all splats as screen-space circles
|
|
27
|
+
* @param {number} maxSphericalHarmonicsDegree Degree of spherical harmonics to utilize in rendering splats
|
|
28
|
+
* @return {THREE.ShaderMaterial}
|
|
29
|
+
*/
|
|
30
|
+
static build(dynamicMode = false, enableOptionalEffects = false, antialiased = false, maxScreenSpaceSplatSize = 2048,
|
|
31
|
+
splatScale = 1.0, pointCloudModeEnabled = false, maxSphericalHarmonicsDegree = 0, kernel2DSize = 0.3, useFlame = true) {
|
|
32
|
+
|
|
33
|
+
const customVertexVars = `
|
|
34
|
+
uniform vec2 covariancesTextureSize;
|
|
35
|
+
uniform highp sampler2D covariancesTexture;
|
|
36
|
+
uniform highp usampler2D covariancesTextureHalfFloat;
|
|
37
|
+
uniform int covariancesAreHalfFloat;
|
|
38
|
+
|
|
39
|
+
void fromCovarianceHalfFloatV4(uvec4 val, out vec4 first, out vec4 second) {
|
|
40
|
+
vec2 r = unpackHalf2x16(val.r);
|
|
41
|
+
vec2 g = unpackHalf2x16(val.g);
|
|
42
|
+
vec2 b = unpackHalf2x16(val.b);
|
|
43
|
+
|
|
44
|
+
first = vec4(r.x, r.y, g.x, g.y);
|
|
45
|
+
second = vec4(b.x, b.y, 0.0, 0.0);
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
let vertexShaderSource = SplatMaterial.buildVertexShaderBase(dynamicMode, enableOptionalEffects,
|
|
50
|
+
maxSphericalHarmonicsDegree, customVertexVars, useFlame);
|
|
51
|
+
vertexShaderSource += SplatMaterial3D.buildVertexShaderProjection(antialiased, enableOptionalEffects,
|
|
52
|
+
maxScreenSpaceSplatSize, kernel2DSize);
|
|
53
|
+
const fragmentShaderSource = SplatMaterial3D.buildFragmentShader();
|
|
54
|
+
|
|
55
|
+
const uniforms = SplatMaterial.getUniforms(dynamicMode, enableOptionalEffects,
|
|
56
|
+
maxSphericalHarmonicsDegree, splatScale, pointCloudModeEnabled);
|
|
57
|
+
|
|
58
|
+
uniforms['covariancesTextureSize'] = {
|
|
59
|
+
'type': 'v2',
|
|
60
|
+
'value': new Vector2(1024, 1024)
|
|
61
|
+
};
|
|
62
|
+
uniforms['covariancesTexture'] = {
|
|
63
|
+
'type': 't',
|
|
64
|
+
'value': null
|
|
65
|
+
};
|
|
66
|
+
uniforms['covariancesTextureHalfFloat'] = {
|
|
67
|
+
'type': 't',
|
|
68
|
+
'value': null
|
|
69
|
+
};
|
|
70
|
+
uniforms['covariancesAreHalfFloat'] = {
|
|
71
|
+
'type': 'i',
|
|
72
|
+
'value': 0
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const material = new ShaderMaterial({
|
|
76
|
+
uniforms: uniforms,
|
|
77
|
+
vertexShader: vertexShaderSource,
|
|
78
|
+
fragmentShader: fragmentShaderSource,
|
|
79
|
+
transparent: true,
|
|
80
|
+
alphaTest: 1.0,
|
|
81
|
+
blending: NormalBlending,
|
|
82
|
+
depthTest: true,
|
|
83
|
+
depthWrite: false,
|
|
84
|
+
side: DoubleSide
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return material;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static buildVertexShaderProjection(antialiased, enableOptionalEffects, maxScreenSpaceSplatSize, kernel2DSize) {
|
|
91
|
+
let vertexShaderSource = `
|
|
92
|
+
|
|
93
|
+
vec4 sampledCovarianceA;
|
|
94
|
+
vec4 sampledCovarianceB;
|
|
95
|
+
vec3 cov3D_M11_M12_M13;
|
|
96
|
+
vec3 cov3D_M22_M23_M33;
|
|
97
|
+
if (covariancesAreHalfFloat == 0) {
|
|
98
|
+
sampledCovarianceA = texture(covariancesTexture, getDataUVF(nearestEvenIndex, 1.5, oddOffset,
|
|
99
|
+
covariancesTextureSize));
|
|
100
|
+
sampledCovarianceB = texture(covariancesTexture, getDataUVF(nearestEvenIndex, 1.5, oddOffset + uint(1),
|
|
101
|
+
covariancesTextureSize));
|
|
102
|
+
|
|
103
|
+
cov3D_M11_M12_M13 = vec3(sampledCovarianceA.rgb) * (1.0 - fOddOffset) +
|
|
104
|
+
vec3(sampledCovarianceA.ba, sampledCovarianceB.r) * fOddOffset;
|
|
105
|
+
cov3D_M22_M23_M33 = vec3(sampledCovarianceA.a, sampledCovarianceB.rg) * (1.0 - fOddOffset) +
|
|
106
|
+
vec3(sampledCovarianceB.gba) * fOddOffset;
|
|
107
|
+
} else {
|
|
108
|
+
uvec4 sampledCovarianceU = texture(covariancesTextureHalfFloat, getDataUV(1, 0, covariancesTextureSize));
|
|
109
|
+
fromCovarianceHalfFloatV4(sampledCovarianceU, sampledCovarianceA, sampledCovarianceB);
|
|
110
|
+
cov3D_M11_M12_M13 = sampledCovarianceA.rgb;
|
|
111
|
+
cov3D_M22_M23_M33 = vec3(sampledCovarianceA.a, sampledCovarianceB.rg);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Construct the 3D covariance matrix
|
|
115
|
+
mat3 Vrk = mat3(
|
|
116
|
+
cov3D_M11_M12_M13.x, cov3D_M11_M12_M13.y, cov3D_M11_M12_M13.z,
|
|
117
|
+
cov3D_M11_M12_M13.y, cov3D_M22_M23_M33.x, cov3D_M22_M23_M33.y,
|
|
118
|
+
cov3D_M11_M12_M13.z, cov3D_M22_M23_M33.y, cov3D_M22_M23_M33.z
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
mat3 J;
|
|
122
|
+
if (orthographicMode == 1) {
|
|
123
|
+
// Since the projection is linear, we don't need an approximation
|
|
124
|
+
J = transpose(mat3(orthoZoom, 0.0, 0.0,
|
|
125
|
+
0.0, orthoZoom, 0.0,
|
|
126
|
+
0.0, 0.0, 0.0));
|
|
127
|
+
} else {
|
|
128
|
+
// Construct the Jacobian of the affine approximation of the projection matrix. It will be used to transform the
|
|
129
|
+
// 3D covariance matrix instead of using the actual projection matrix because that transformation would
|
|
130
|
+
// require a non-linear component (perspective division) which would yield a non-gaussian result.
|
|
131
|
+
float s = 1.0 / (viewCenter.z * viewCenter.z);
|
|
132
|
+
J = mat3(
|
|
133
|
+
focal.x / viewCenter.z, 0., -(focal.x * viewCenter.x) * s,
|
|
134
|
+
0., focal.y / viewCenter.z, -(focal.y * viewCenter.y) * s,
|
|
135
|
+
0., 0., 0.
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Concatenate the projection approximation with the model-view transformation
|
|
140
|
+
mat3 W = transpose(mat3(transformModelViewMatrix));
|
|
141
|
+
mat3 T = W * J;
|
|
142
|
+
|
|
143
|
+
// Transform the 3D covariance matrix (Vrk) to compute the 2D covariance matrix
|
|
144
|
+
mat3 cov2Dm = transpose(T) * Vrk * T;
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
if (antialiased) {
|
|
148
|
+
vertexShaderSource += `
|
|
149
|
+
float detOrig = cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1];
|
|
150
|
+
cov2Dm[0][0] += ${kernel2DSize};
|
|
151
|
+
cov2Dm[1][1] += ${kernel2DSize};
|
|
152
|
+
float detBlur = cov2Dm[0][0] * cov2Dm[1][1] - cov2Dm[0][1] * cov2Dm[0][1];
|
|
153
|
+
vColor.a *= sqrt(max(detOrig / detBlur, 0.0));
|
|
154
|
+
if (vColor.a < minAlpha) return;
|
|
155
|
+
`;
|
|
156
|
+
} else {
|
|
157
|
+
vertexShaderSource += `
|
|
158
|
+
cov2Dm[0][0] += ${kernel2DSize};
|
|
159
|
+
cov2Dm[1][1] += ${kernel2DSize};
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
vertexShaderSource += `
|
|
164
|
+
|
|
165
|
+
// We are interested in the upper-left 2x2 portion of the projected 3D covariance matrix because
|
|
166
|
+
// we only care about the X and Y values. We want the X-diagonal, cov2Dm[0][0],
|
|
167
|
+
// the Y-diagonal, cov2Dm[1][1], and the correlation between the two cov2Dm[0][1]. We don't
|
|
168
|
+
// need cov2Dm[1][0] because it is a symetric matrix.
|
|
169
|
+
vec3 cov2Dv = vec3(cov2Dm[0][0], cov2Dm[0][1], cov2Dm[1][1]);
|
|
170
|
+
|
|
171
|
+
// We now need to solve for the eigen-values and eigen vectors of the 2D covariance matrix
|
|
172
|
+
// so that we can determine the 2D basis for the splat. This is done using the method described
|
|
173
|
+
// here: https://people.math.harvard.edu/~knill/teaching/math21b2004/exhibits/2dmatrices/index.html
|
|
174
|
+
// After calculating the eigen-values and eigen-vectors, we calculate the basis for rendering the splat
|
|
175
|
+
// by normalizing the eigen-vectors and then multiplying them by (sqrt(8) * sqrt(eigen-value)), which is
|
|
176
|
+
// equal to scaling them by sqrt(8) standard deviations.
|
|
177
|
+
//
|
|
178
|
+
// This is a different approach than in the original work at INRIA. In that work they compute the
|
|
179
|
+
// max extents of the projected splat in screen space to form a screen-space aligned bounding rectangle
|
|
180
|
+
// which forms the geometry that is actually rasterized. The dimensions of that bounding box are 3.0
|
|
181
|
+
// times the square root of the maximum eigen-value, or 3 standard deviations. They then use the inverse
|
|
182
|
+
// 2D covariance matrix (called 'conic') in the CUDA rendering thread to determine fragment opacity by
|
|
183
|
+
// calculating the full gaussian: exp(-0.5 * (X - mean) * conic * (X - mean)) * splat opacity
|
|
184
|
+
float a = cov2Dv.x;
|
|
185
|
+
float d = cov2Dv.z;
|
|
186
|
+
float b = cov2Dv.y;
|
|
187
|
+
float D = a * d - b * b;
|
|
188
|
+
float trace = a + d;
|
|
189
|
+
float traceOver2 = 0.5 * trace;
|
|
190
|
+
float term2 = sqrt(max(0.1f, traceOver2 * traceOver2 - D));
|
|
191
|
+
float eigenValue1 = traceOver2 + term2;
|
|
192
|
+
float eigenValue2 = traceOver2 - term2;
|
|
193
|
+
|
|
194
|
+
if (pointCloudModeEnabled == 1) {
|
|
195
|
+
eigenValue1 = eigenValue2 = 0.2;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (eigenValue2 <= 0.0) return;
|
|
199
|
+
|
|
200
|
+
vec2 eigenVector1 = normalize(vec2(b, eigenValue1 - a));
|
|
201
|
+
// since the eigen vectors are orthogonal, we derive the second one from the first
|
|
202
|
+
vec2 eigenVector2 = vec2(eigenVector1.y, -eigenVector1.x);
|
|
203
|
+
|
|
204
|
+
// We use sqrt(8) standard deviations instead of 3 to eliminate more of the splat with a very low opacity.
|
|
205
|
+
vec2 basisVector1 = eigenVector1 * splatScale * min(sqrt8 * sqrt(eigenValue1), ${parseInt(maxScreenSpaceSplatSize)}.0);
|
|
206
|
+
vec2 basisVector2 = eigenVector2 * splatScale * min(sqrt8 * sqrt(eigenValue2), ${parseInt(maxScreenSpaceSplatSize)}.0);
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
if (enableOptionalEffects) {
|
|
210
|
+
vertexShaderSource += `
|
|
211
|
+
vColor.a *= splatOpacityFromScene;
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
vertexShaderSource += `
|
|
216
|
+
vec2 ndcOffset = vec2(vPosition.x * basisVector1 + vPosition.y * basisVector2) *
|
|
217
|
+
basisViewport * 2.0 * inverseFocalAdjustment;
|
|
218
|
+
|
|
219
|
+
vec4 quadPos = vec4(ndcCenter.xy + ndcOffset, ndcCenter.z, 1.0);
|
|
220
|
+
gl_Position = quadPos;
|
|
221
|
+
|
|
222
|
+
// Scale the position data we send to the fragment shader
|
|
223
|
+
vPosition *= sqrt8;
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
vertexShaderSource += SplatMaterial.getVertexShaderFadeIn();
|
|
227
|
+
vertexShaderSource += `}`;
|
|
228
|
+
|
|
229
|
+
return vertexShaderSource;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static buildFragmentShader() {
|
|
233
|
+
let fragmentShaderSource = `
|
|
234
|
+
precision highp float;
|
|
235
|
+
#include <common>
|
|
236
|
+
|
|
237
|
+
uniform vec3 debugColor;
|
|
238
|
+
|
|
239
|
+
varying vec4 vColor;
|
|
240
|
+
varying vec2 vUv;
|
|
241
|
+
varying vec2 vPosition;
|
|
242
|
+
varying vec2 vSplatIndex;
|
|
243
|
+
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
fragmentShaderSource += `
|
|
247
|
+
void main () {
|
|
248
|
+
// Compute the positional squared distance from the center of the splat to the current fragment.
|
|
249
|
+
float A = dot(vPosition, vPosition);
|
|
250
|
+
// Since the positional data in vPosition has been scaled by sqrt(8), the squared result will be
|
|
251
|
+
// scaled by a factor of 8. If the squared result is larger than 8, it means it is outside the ellipse
|
|
252
|
+
// defined by the rectangle formed by vPosition. It also means it's farther
|
|
253
|
+
// away than sqrt(8) standard deviations from the mean.
|
|
254
|
+
|
|
255
|
+
// if(vSplatIndex.x > 20000.0) discard;
|
|
256
|
+
// if (A > 8.0) discard;
|
|
257
|
+
vec3 color = vColor.rgb;
|
|
258
|
+
|
|
259
|
+
// Since the rendered splat is scaled by sqrt(8), the inverse covariance matrix that is part of
|
|
260
|
+
// the gaussian formula becomes the identity matrix. We're then left with (X - mean) * (X - mean),
|
|
261
|
+
// and since 'mean' is zero, we have X * X, which is the same as A:
|
|
262
|
+
float opacity = exp( -0.5*A) * vColor.a;
|
|
263
|
+
if(opacity < 1.0 / 255.0)
|
|
264
|
+
discard;
|
|
265
|
+
|
|
266
|
+
// uint a = uint(255);
|
|
267
|
+
// vec3 c = vec3(vSplatIndex.x / 256.0 / 256.0, float(uint(vSplatIndex.x / 256.0 )% a) / 256.0, float(uint(vSplatIndex.x)% a) / 256.0);
|
|
268
|
+
// gl_FragColor = vec4(c, 1.0);
|
|
269
|
+
gl_FragColor = vec4(color, opacity);
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
return fragmentShaderSource;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsplat-flame-avatar - Materials Module
|
|
3
|
+
* WebGL shaders and materials for Gaussian Splat rendering.
|
|
4
|
+
*
|
|
5
|
+
* Based on @mkkellogg/gaussian-splats-3d (MIT License)
|
|
6
|
+
* Extended with FLAME skinning shaders.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { SplatMaterial } from './SplatMaterial.js';
|
|
10
|
+
export { SplatMaterial2D } from './SplatMaterial2D.js';
|
|
11
|
+
export { SplatMaterial3D } from './SplatMaterial3D.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hit
|
|
3
|
+
*
|
|
4
|
+
* Derived from @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
+
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
+
*
|
|
7
|
+
* This file is functionally identical to the original.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as THREE from 'three';
|
|
11
|
+
|
|
12
|
+
export class Hit {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.origin = new THREE.Vector3();
|
|
16
|
+
this.normal = new THREE.Vector3();
|
|
17
|
+
this.distance = 0;
|
|
18
|
+
this.splatIndex = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set(origin, normal, distance, splatIndex) {
|
|
22
|
+
this.origin.copy(origin);
|
|
23
|
+
this.normal.copy(normal);
|
|
24
|
+
this.distance = distance;
|
|
25
|
+
this.splatIndex = splatIndex;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
clone() {
|
|
29
|
+
const hitClone = new Hit();
|
|
30
|
+
hitClone.origin.copy(this.origin);
|
|
31
|
+
hitClone.normal.copy(this.normal);
|
|
32
|
+
hitClone.distance = this.distance;
|
|
33
|
+
hitClone.splatIndex = this.splatIndex;
|
|
34
|
+
return hitClone;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|