@viggle/splat-engine-three 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +290 -2
- package/dist/chunks/chunk-MM6DJU6S.js +7801 -0
- package/dist/chunks/chunk-MM6DJU6S.js.map +7 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +7 -0
- package/dist/types/anim/animation-conversion.d.ts +25 -0
- package/dist/types/anim/bind-pose.d.ts +15 -0
- package/dist/types/anim/keyframe-splines.d.ts +59 -0
- package/dist/types/anim/rest-relative.d.ts +32 -0
- package/dist/types/anim/spline.d.ts +14 -0
- package/dist/types/animation.d.ts +216 -0
- package/dist/types/armature.d.ts +524 -0
- package/dist/types/character.d.ts +540 -0
- package/dist/types/engine.d.ts +166 -0
- package/dist/types/environment.d.ts +53 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/lighting/color.d.ts +13 -0
- package/dist/types/lighting/point-light.d.ts +33 -0
- package/dist/types/lighting/sky-light.d.ts +22 -0
- package/dist/types/lighting/sun-light.d.ts +34 -0
- package/dist/types/loader/animation/animation-glb.d.ts +117 -0
- package/dist/types/loader/character/index.d.ts +33 -0
- package/dist/types/loader/character/legacy/index.d.ts +34 -0
- package/dist/types/loader/character/legacy/interleaved-splat.d.ts +101 -0
- package/dist/types/loader/character/legacy/planar-splat.d.ts +139 -0
- package/dist/types/loader/character/load.d.ts +33 -0
- package/dist/types/loader/character/types.d.ts +129 -0
- package/dist/types/loader/character/v1/decode.d.ts +32 -0
- package/dist/types/loader/environment/ply.d.ts +16 -0
- package/dist/types/loader/shared/bin-cache.d.ts +19 -0
- package/dist/types/loader/skeleton/index.d.ts +19 -0
- package/dist/types/loader/skeleton/skeleton-data-441.d.ts +5 -0
- package/dist/types/loader/skeleton/skeleton-data.d.ts +6 -0
- package/dist/types/math.d.ts +82 -0
- package/dist/types/skeletal/animation-player.d.ts +185 -0
- package/dist/types/skeletal/bone-mask.d.ts +27 -0
- package/dist/types/skeletal/bone-stretch.d.ts +65 -0
- package/dist/types/skeletal/pose.d.ts +27 -0
- package/dist/types/skeletal/skeleton.d.ts +57 -0
- package/dist/types/skeletal/splat-skinning.d.ts +69 -0
- package/dist/types/splat/backend.d.ts +57 -0
- package/dist/types/splat/bone-binding.d.ts +38 -0
- package/dist/types/splat/depth-sort.d.ts +58 -0
- package/dist/types/splat/packed-splats.d.ts +36 -0
- package/dist/types/splat/splat-mesh.d.ts +71 -0
- package/dist/types/splat/splat.d.ts +186 -0
- package/dist/types/splat/transform-palette.d.ts +42 -0
- package/dist/types/splat/webgl/backend.d.ts +17 -0
- package/dist/types/splat/webgl/shaders.d.ts +2 -0
- package/dist/types/splat/webgl/splat-mesh.d.ts +52 -0
- package/dist/types/splat/webgpu/backend.d.ts +25 -0
- package/dist/types/splat/webgpu/lighting.d.ts +34 -0
- package/dist/types/splat/webgpu/radix-sort.d.ts +46 -0
- package/dist/types/splat/webgpu/splat-mesh.d.ts +71 -0
- package/dist/types/three/loaders.d.ts +77 -0
- package/dist/types/three/splat-character.d.ts +192 -0
- package/dist/types/types.d.ts +30 -0
- package/dist/types/webgpu.d.ts +5 -0
- package/dist/webgpu.js +813 -0
- package/dist/webgpu.js.map +7 -0
- package/package.json +46 -4
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Box3, DataTexture, Vector2, Vector4 } from 'three';
|
|
2
|
+
import type { Frustum } from 'three';
|
|
3
|
+
import type { SplatEngine } from '../engine';
|
|
4
|
+
import type { Mat4 } from '../math';
|
|
5
|
+
import type { SplatWeights } from '../types';
|
|
6
|
+
import type { PackedSplats } from './packed-splats';
|
|
7
|
+
import { SplatMesh } from './splat-mesh';
|
|
8
|
+
import { TransformPalette } from './transform-palette';
|
|
9
|
+
/** What a {@link Splat} is built from: packed gaussians, plus skinning data for characters. */
|
|
10
|
+
export interface SplatSource {
|
|
11
|
+
packed: PackedSplats;
|
|
12
|
+
/** Present for skinned splats. Any `K` influences per splat; the top four are used. */
|
|
13
|
+
splatWeights?: SplatWeights;
|
|
14
|
+
/** Bone count of the rig the weights index into, when it differs from the armature's. */
|
|
15
|
+
sourceBoneCount?: number;
|
|
16
|
+
}
|
|
17
|
+
/** @group Splats */
|
|
18
|
+
export interface SplatOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Depth-sort in a worker instead of synchronously. Default: `true` for
|
|
21
|
+
* unskinned splats, `false` for skinned ones (whose order must track the pose).
|
|
22
|
+
*/
|
|
23
|
+
asyncSort?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gaussian splat renderer.
|
|
27
|
+
*
|
|
28
|
+
* Owns the splat data textures, the bone index/weight textures used for
|
|
29
|
+
* skeletal deformation, the transform palette written to by the linked
|
|
30
|
+
* {@link Armature}, and the three.js mesh that draws it all. Skinning runs on
|
|
31
|
+
* the GPU when the splat has weights; unskinned splats (environments) take the
|
|
32
|
+
* same path with the identity transform.
|
|
33
|
+
*
|
|
34
|
+
* {@link Character.load} and {@link Environment.load} construct this
|
|
35
|
+
* internally. Direct construction is only needed for custom loading pipelines.
|
|
36
|
+
*
|
|
37
|
+
* @group Splats
|
|
38
|
+
*/
|
|
39
|
+
export declare class Splat {
|
|
40
|
+
readonly numSplats: number;
|
|
41
|
+
/** The packed gaussian data backing the textures. */
|
|
42
|
+
readonly packed: PackedSplats;
|
|
43
|
+
/** The three.js object that draws this splat. Parent it; do not replace it. */
|
|
44
|
+
readonly object3D: SplatMesh;
|
|
45
|
+
readonly colorTexture: DataTexture;
|
|
46
|
+
readonly transformATexture: DataTexture;
|
|
47
|
+
readonly transformBTexture: DataTexture;
|
|
48
|
+
/** Per-splat palette slot indices, four per splat. Rewritten when an armature binds. */
|
|
49
|
+
boneIndicesData?: Float32Array;
|
|
50
|
+
boneWeightsData?: Float32Array;
|
|
51
|
+
boneIndicesTexture?: DataTexture;
|
|
52
|
+
boneWeightsTexture?: DataTexture;
|
|
53
|
+
groupIdsTexture?: DataTexture;
|
|
54
|
+
variantMaskTexture?: DataTexture;
|
|
55
|
+
variantMask?: Float32Array;
|
|
56
|
+
groupIds?: Uint8Array;
|
|
57
|
+
/** Bumped whenever the group ids or the variant mask change. */
|
|
58
|
+
variantMaskVersion: number;
|
|
59
|
+
transformPalette: TransformPalette;
|
|
60
|
+
/** rgb tint colour, w tint amount. Shared with the material by reference. */
|
|
61
|
+
readonly tint: Vector4;
|
|
62
|
+
/** x contrast, y saturation. Shared with the material by reference. */
|
|
63
|
+
readonly grade: Vector2;
|
|
64
|
+
linkedArmature: any;
|
|
65
|
+
/** Skinning influences normalised to exactly four slots per splat. */
|
|
66
|
+
splatWeights?: SplatWeights;
|
|
67
|
+
sourceBoneCount?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Splat centres in local space: the rest pose until the first skinning
|
|
70
|
+
* update, the skinned pose after.
|
|
71
|
+
*
|
|
72
|
+
* Only kept current when the renderer sorts on the CPU (WebGL). Under WebGPU
|
|
73
|
+
* this lags the pose; call {@link Splat.ensureCenters} before reading it.
|
|
74
|
+
*/
|
|
75
|
+
centers: Float32Array;
|
|
76
|
+
/** Bumped whenever {@link Splat.centers} changes. */
|
|
77
|
+
centersVersion: number;
|
|
78
|
+
/**
|
|
79
|
+
* Local-space bounds used for culling and visibility tests. With CPU
|
|
80
|
+
* centres these track the pose exactly; without, they are the rest bounds
|
|
81
|
+
* padded generously enough to contain any pose.
|
|
82
|
+
*/
|
|
83
|
+
readonly localBounds: Box3;
|
|
84
|
+
/** Discard splats smaller than this many pixels on screen. `0` draws everything. */
|
|
85
|
+
minPixelSize: number;
|
|
86
|
+
private readonly _mesh;
|
|
87
|
+
private _cpuInfluences;
|
|
88
|
+
private _pendingBoneTransforms;
|
|
89
|
+
private _flatBones;
|
|
90
|
+
private _variantMaskEnabled;
|
|
91
|
+
private _nonRigidCov;
|
|
92
|
+
private _antiAlias;
|
|
93
|
+
private _destroyed;
|
|
94
|
+
private readonly _worldBounds;
|
|
95
|
+
private readonly _worldSphere;
|
|
96
|
+
private readonly _groundHist;
|
|
97
|
+
constructor(engine: SplatEngine, source: SplatSource, opts?: SplatOptions);
|
|
98
|
+
/**
|
|
99
|
+
* A new, independent splat over the same packed data, carrying this one's
|
|
100
|
+
* render options. For unskinned splats only.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
cloneStatic(engine: SplatEngine, asyncSort: boolean): Splat;
|
|
104
|
+
/** Top-4 influences per splat, zero-padded. The shader and the CPU path both assume four. */
|
|
105
|
+
private _normalizeWeights;
|
|
106
|
+
/** Re-upload the bone index/weight textures after editing their data arrays. */
|
|
107
|
+
markBoneTexturesDirty(): void;
|
|
108
|
+
get variantMaskEnabled(): boolean;
|
|
109
|
+
get nonRigidCovariance(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Toggle the non-rigid covariance shader path. When on, the palette's
|
|
112
|
+
* per-axis scale stretches each gaussian's covariance; the palette must then
|
|
113
|
+
* actually carry that scale (see `SplatSkinning.covStretch`). Off is the
|
|
114
|
+
* rigid path, where palette scale is treated as unit. Switching recompiles
|
|
115
|
+
* the shader, so toggle it rarely.
|
|
116
|
+
*/
|
|
117
|
+
setNonRigidCovariance(on: boolean): void;
|
|
118
|
+
/**
|
|
119
|
+
* Compensate each splat's opacity for the screen-space dilation applied to
|
|
120
|
+
* keep sub-pixel splats visible. Off by default; turn on for assets trained
|
|
121
|
+
* with anti-aliasing (e.g. Mip-Splatting).
|
|
122
|
+
*/
|
|
123
|
+
get antiAlias(): boolean;
|
|
124
|
+
set antiAlias(on: boolean);
|
|
125
|
+
/**
|
|
126
|
+
* Blend every splat's colour toward `color` by `intensity` (0..1).
|
|
127
|
+
* `intensity = 0` restores the authored colour.
|
|
128
|
+
*/
|
|
129
|
+
setTint(intensity: number, color?: {
|
|
130
|
+
r: number;
|
|
131
|
+
g: number;
|
|
132
|
+
b: number;
|
|
133
|
+
}): void;
|
|
134
|
+
/** Per-splat colour grade. `(1, 1)` is a true no-op. */
|
|
135
|
+
setGrade(contrast: number, saturation: number): void;
|
|
136
|
+
/**
|
|
137
|
+
* Assign each splat to a group and enable per-group visibility. All groups
|
|
138
|
+
* start visible; toggle them with {@link Splat.setVariantMask}.
|
|
139
|
+
*/
|
|
140
|
+
setSplatGroups(groupIds: Uint8Array, numGroups: number): void;
|
|
141
|
+
/** Per-group visibility, one entry per group: `>= 0.5` shows the group. */
|
|
142
|
+
setVariantMask(mask: ArrayLike<number>): void;
|
|
143
|
+
/** Whether the splat's world-space bounding sphere touches `frustum`. */
|
|
144
|
+
isInFrustum(frustum: Frustum): boolean;
|
|
145
|
+
/** World-space bounds of the splat in its current pose. */
|
|
146
|
+
getWorldAabb(out?: Box3): Box3;
|
|
147
|
+
/** World-space Y of the lowest corner of the current bounds. */
|
|
148
|
+
getWorldMinY(): number;
|
|
149
|
+
/**
|
|
150
|
+
* World-space Y of the splat's ground contact: the height below which only
|
|
151
|
+
* `quantile` of the splats lie. More robust than the bounds minimum, which a
|
|
152
|
+
* single stray gaussian can drag down.
|
|
153
|
+
*/
|
|
154
|
+
getGroundWorldY(quantile?: number): number;
|
|
155
|
+
/**
|
|
156
|
+
* Flatten rest centres and influences into one interleaved buffer for the
|
|
157
|
+
* per-frame CPU skinning pass: `[x, y, z, w0, b0, w1, b1, w2, b2, w3, b3]`.
|
|
158
|
+
*/
|
|
159
|
+
private _bakeInfluences;
|
|
160
|
+
/**
|
|
161
|
+
* Tell the splat its bone matrices changed. When the renderer sorts on the
|
|
162
|
+
* CPU the centres are re-skinned immediately; otherwise the work is deferred
|
|
163
|
+
* until something asks for them ({@link Splat.ensureCenters}).
|
|
164
|
+
*/
|
|
165
|
+
updatePositions(boneTransforms: Mat4[]): void;
|
|
166
|
+
/** Bring {@link Splat.centers} up to date with the latest bone matrices. */
|
|
167
|
+
ensureCenters(): void;
|
|
168
|
+
/**
|
|
169
|
+
* Skin the splat centres on the CPU with the same matrices the GPU uses.
|
|
170
|
+
* This is the dominant per-character CPU cost, which is why it only runs
|
|
171
|
+
* when something needs the result.
|
|
172
|
+
*/
|
|
173
|
+
private _skinCenters;
|
|
174
|
+
/** Force a re-sort on the next render. */
|
|
175
|
+
requestSort(): void;
|
|
176
|
+
/** True while an asynchronous sort is outstanding, i.e. the drawn order is stale or absent. */
|
|
177
|
+
get sortPending(): boolean;
|
|
178
|
+
/** Show or hide the splat without disturbing its bindings. */
|
|
179
|
+
setRenderingEnabled(enabled: boolean): void;
|
|
180
|
+
get renderingEnabled(): boolean;
|
|
181
|
+
get destroyed(): boolean;
|
|
182
|
+
/** Remove from the scene graph and release every GPU resource. */
|
|
183
|
+
destroy(): void;
|
|
184
|
+
/** @internal */
|
|
185
|
+
static _liveInstances: Set<Splat>;
|
|
186
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { DataTexture } from 'three';
|
|
2
|
+
import type { Mat4 } from '../math';
|
|
3
|
+
/** Transforms per texture row. The shader's palette lookup hard-codes this. */
|
|
4
|
+
export declare const PALETTE_ROW_SLOTS = 512;
|
|
5
|
+
/**
|
|
6
|
+
* The per-splat table of skinning matrices, stored as a float texture: three
|
|
7
|
+
* RGBA texels per transform, {@link PALETTE_ROW_SLOTS} transforms per row.
|
|
8
|
+
*
|
|
9
|
+
* Slot 0 is reserved for identity and is what unmapped influences point at.
|
|
10
|
+
*/
|
|
11
|
+
declare class TransformPalette {
|
|
12
|
+
private _texture;
|
|
13
|
+
private _data;
|
|
14
|
+
private _dirty;
|
|
15
|
+
private _nextIdx;
|
|
16
|
+
private readonly _identity;
|
|
17
|
+
constructor(initialSize?: number);
|
|
18
|
+
/**
|
|
19
|
+
* The backing texture. Replaced when the palette grows, so read it each
|
|
20
|
+
* frame rather than holding on to it.
|
|
21
|
+
*/
|
|
22
|
+
get texture(): DataTexture;
|
|
23
|
+
/**
|
|
24
|
+
* The backing array: twelve floats per slot, three row-major vec4 rows.
|
|
25
|
+
* Replaced when the palette grows, like {@link TransformPalette.texture}.
|
|
26
|
+
*/
|
|
27
|
+
get data(): Float32Array;
|
|
28
|
+
/** Incremented every time {@link TransformPalette.upload} reports a change. */
|
|
29
|
+
version: number;
|
|
30
|
+
/** Slots the palette can hold before it has to grow. */
|
|
31
|
+
get capacity(): number;
|
|
32
|
+
private _realloc;
|
|
33
|
+
getTransform(index: number, transform: Mat4): void;
|
|
34
|
+
setTransform(index: number, transform: Mat4): void;
|
|
35
|
+
/** Flag the texture for re-upload if anything changed. Returns whether it did. */
|
|
36
|
+
upload(): boolean;
|
|
37
|
+
/** Reserve `num` consecutive slots and return the index of the first. */
|
|
38
|
+
alloc(num?: number): number;
|
|
39
|
+
free(num?: number): void;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
}
|
|
42
|
+
export { TransformPalette };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Mesh } from 'three';
|
|
2
|
+
import type { SplatEngine } from '../../engine';
|
|
3
|
+
import type { SplatBackend, SplatRenderImpl } from '../backend';
|
|
4
|
+
import type { Splat } from '../splat';
|
|
5
|
+
/**
|
|
6
|
+
* Splat rendering for `THREE.WebGLRenderer`: GLSL ES 3.00, WebGL2.
|
|
7
|
+
*
|
|
8
|
+
* Splats are skinned and projected in the vertex stage. The depth sort runs on
|
|
9
|
+
* the CPU - synchronously for skinned characters, in a worker for static
|
|
10
|
+
* environments - which is what makes this backend's cost scale with the
|
|
11
|
+
* number of animated characters.
|
|
12
|
+
*
|
|
13
|
+
* @group Engine
|
|
14
|
+
*/
|
|
15
|
+
export declare class WebGLSplatBackend implements SplatBackend {
|
|
16
|
+
createImpl(host: Mesh, splat: Splat, engine: SplatEngine, asyncSort: boolean): SplatRenderImpl;
|
|
17
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const SPLAT_VERTEX_GLSL = "\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\n// Quad corner in [-1, 1].\nin vec3 position;\n// Index of the splat this instance draws; instances arrive back-to-front.\nin uint splatOrder;\n\nuniform mat4 modelMatrix;\nuniform mat4 viewMatrix;\nuniform mat4 projectionMatrix;\n\nuniform highp usampler2D tTransformA;\nuniform highp sampler2D tTransformB;\nuniform highp sampler2D tColor;\nuniform int uTexWidth;\n\n// Drawing-buffer size in pixels.\nuniform vec2 uViewport;\nuniform float uMinPixelSize;\n// World rotation (xyzw) and scale of the splat object.\nuniform vec4 uModelRotation;\nuniform vec3 uModelScale;\n// rgb tint colour, a tint amount.\nuniform vec4 uTint;\n// x contrast, y saturation.\nuniform vec2 uGrade;\n// 1 when the render target expects linear colour, 0 when it is display-referred.\nuniform float uOutputLinear;\n\nout mediump vec2 gaussianUV;\nout mediump vec4 gaussianColor;\n\n\nuniform vec3 uSunColor;\nuniform float uSunIntensity;\nuniform vec3 uAmbientColor;\nuniform int uPointLightCount;\nuniform highp sampler2D uPointLightTex;\n\n// Baked splat colour is multiplied by this. With no lights it is exactly 1.\nvec3 computeLitFactor(vec3 worldCenter) {\n vec3 lit = uAmbientColor + uSunColor * uSunIntensity;\n for (int i = 0; i < uPointLightCount; i++) {\n vec4 posRange = texelFetch(uPointLightTex, ivec2(i, 0), 0);\n vec4 colorInt = texelFetch(uPointLightTex, ivec2(i, 1), 0);\n vec3 toLight = posRange.xyz - worldCenter;\n float dist = length(toLight);\n float range = posRange.w;\n if (dist >= range) continue;\n float t = dist / range;\n float atten = 1.0 - t * t;\n atten = atten * atten;\n lit += colorInt.rgb * colorInt.w * atten;\n }\n return lit;\n}\n\n\nuniform highp sampler2D transformPalette;\n\n#ifdef USE_BONE_BLENDING\nuniform highp sampler2D boneIndices;\nuniform highp sampler2D boneWeights;\n#endif\n\n#ifdef USE_VARIANT_MASK\nuniform highp sampler2D splatGroupIds;\nuniform highp sampler2D splatVariantMask;\n\nbool isSplatHiddenByVariantMask(ivec2 splatUV) {\n float gidF = texelFetch(splatGroupIds, splatUV, 0).r;\n int gid = int(gidF + 0.5);\n float bit = texelFetch(splatVariantMask, ivec2(gid, 0), 0).r;\n return bit < 0.5;\n}\n#endif\n\nbool isReasonablePaletteTransform(mat4 t) {\n mat3 lin = mat3(t);\n vec3 s = vec3(length(lin[0]), length(lin[1]), length(lin[2]));\n // Reject only when EVERY column is near-zero (a truly empty palette slot).\n // Linear-blend skinning of two very different bone rotations produces a\n // candy-wrapper matrix with one short column; that is still a valid\n // mid-rotation transform. Requiring all columns to pass would collapse\n // joint-spanning splats (notably the neck) back to the bind pose.\n return any(greaterThan(s, vec3(1e-4)));\n}\n\nmat4 readTransformFromPalette(uint paletteIndex) {\n if (paletteIndex == 0u) {\n return mat4(1.0);\n }\n\n int u = int(paletteIndex % 512u) * 3;\n int v = int(paletteIndex / 512u);\n\n mat4 t;\n t[0] = texelFetch(transformPalette, ivec2(u, v), 0);\n t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);\n t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);\n t[3] = vec4(0.0, 0.0, 0.0, 1.0);\n\n mat4 result = transpose(t);\n if (!isReasonablePaletteTransform(result)) {\n return mat4(1.0);\n }\n return result;\n}\n\nmat4 getSplatPaletteTransform(ivec2 splatUV) {\n #ifdef USE_BONE_BLENDING\n vec4 boneIdxVec = texelFetch(boneIndices, splatUV, 0);\n vec4 boneWeightVec = texelFetch(boneWeights, splatUV, 0);\n float totalWeight = dot(boneWeightVec, vec4(1.0));\n if (totalWeight > 0.001) {\n mat4 blended = mat4(0.0);\n for (int i = 0; i < 4; i++) {\n float w = boneWeightVec[i];\n if (w > 0.001) {\n uint idx = uint(boneIdxVec[i] + 0.5);\n blended += readTransformFromPalette(idx) * w;\n }\n }\n mat4 result = blended / totalWeight;\n if (isReasonablePaletteTransform(result)) {\n return result;\n }\n }\n #endif\n\n return mat4(1.0);\n}\n\nvec4 mat3ToQuatXYZW(mat3 R) {\n vec4 q;\n float trace = R[0][0] + R[1][1] + R[2][2];\n if (trace > 0.0) {\n float s = sqrt(trace + 1.0) * 2.0;\n q.w = 0.25 * s;\n q.x = (R[1][2] - R[2][1]) / s;\n q.y = (R[2][0] - R[0][2]) / s;\n q.z = (R[0][1] - R[1][0]) / s;\n } else if (R[0][0] > R[1][1] && R[0][0] > R[2][2]) {\n float s = sqrt(1.0 + R[0][0] - R[1][1] - R[2][2]) * 2.0;\n q.w = (R[1][2] - R[2][1]) / s;\n q.x = 0.25 * s;\n q.y = (R[1][0] + R[0][1]) / s;\n q.z = (R[2][0] + R[0][2]) / s;\n } else if (R[1][1] > R[2][2]) {\n float s = sqrt(1.0 + R[1][1] - R[0][0] - R[2][2]) * 2.0;\n q.w = (R[2][0] - R[0][2]) / s;\n q.x = (R[1][0] + R[0][1]) / s;\n q.y = 0.25 * s;\n q.z = (R[2][1] + R[1][2]) / s;\n } else {\n float s = sqrt(1.0 + R[2][2] - R[0][0] - R[1][1]) * 2.0;\n q.w = (R[0][1] - R[1][0]) / s;\n q.x = (R[2][0] + R[0][2]) / s;\n q.y = (R[2][1] + R[1][2]) / s;\n q.z = 0.25 * s;\n }\n return q;\n}\n\n\nconst vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);\n\nmat3 quatToMat3(vec4 R) {\n vec4 R2 = R + R;\n float X = R2.x * R.w;\n vec4 Y = R2.y * R;\n vec4 Z = R2.z * R;\n float W = R2.w * R.w;\n return mat3(\n 1.0 - Z.z - W, Y.z + X, Y.w - Z.x,\n Y.z - X, 1.0 - Y.y - W, Z.w + Y.x,\n Y.w + Z.x, Z.w - Y.x, 1.0 - Y.y - Z.z\n );\n}\n\nvec4 quatMul(vec4 a, vec4 b) {\n return vec4(\n a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,\n a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,\n a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,\n a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z\n );\n}\n\nvoid computeCovariance(vec4 rotation, vec3 scale, out vec3 covA, out vec3 covB) {\n mat3 rot = quatToMat3(rotation);\n mat3 M = transpose(mat3(\n scale.x * rot[0],\n scale.y * rot[1],\n scale.z * rot[2]\n ));\n covA = vec3(dot(M[0], M[0]), dot(M[0], M[1]), dot(M[0], M[2]));\n covB = vec3(dot(M[1], M[1]), dot(M[1], M[2]), dot(M[2], M[2]));\n}\n\nvec3 sRGBToLinear(vec3 c) {\n return mix(\n pow(c * 0.9478672986 + vec3(0.0521327014), vec3(2.4)),\n c * 0.0773993808,\n vec3(lessThanEqual(c, vec3(0.04045)))\n );\n}\n\nvoid main(void) {\n ivec2 splatUV = ivec2(int(splatOrder % uint(uTexWidth)), int(splatOrder / uint(uTexWidth)));\n uvec4 tA = texelFetch(tTransformA, splatUV, 0);\n vec4 tB = texelFetch(tTransformB, splatUV, 0);\n\n vec3 modelCenter = uintBitsToFloat(tA.xyz);\n vec3 packedRot = vec3(unpackHalf2x16(tA.w), tB.w);\n // xyzw, with w recovered: quaternions are stored on the w >= 0 hemisphere.\n vec4 srcRotation = vec4(packedRot, sqrt(max(0.0, 1.0 - dot(packedRot, packedRot))));\n vec3 srcScale = tB.xyz;\n\n // ---- Skin, then lift to world space --------------------------------\n mat4 palette = getSplatPaletteTransform(splatUV);\n vec3 modelDeformed = (palette * vec4(modelCenter, 1.0)).xyz;\n vec3 worldCenter = (modelMatrix * vec4(modelDeformed, 1.0)).xyz;\n\n vec4 worldRotation = quatMul(uModelRotation, srcRotation);\n if (worldRotation.w < 0.0) worldRotation = -worldRotation;\n vec3 worldScale = uModelScale * srcScale;\n\n #ifdef USE_BONE_BLENDING\n {\n // The full world-space skinning rotation comes from modelMatrix * palette;\n // the palette's own scale is deliberately dropped (rigid skinning).\n mat3 lin = mat3(modelMatrix * palette);\n vec3 s = vec3(length(lin[0]), length(lin[1]), length(lin[2]));\n if (s.x > 1e-5 && s.y > 1e-5 && s.z > 1e-5) {\n mat3 rot = mat3(lin[0] / s.x, lin[1] / s.y, lin[2] / s.z);\n vec4 composed = normalize(quatMul(mat3ToQuatXYZW(rot), srcRotation));\n if (composed.w < 0.0) composed = -composed;\n worldRotation = composed;\n }\n #ifdef USE_NONRIGID_COV\n // Bone-length scaling: the palette keeps its per-axis scale and the\n // gaussian stretches with the bone.\n worldScale *= s;\n #endif\n }\n #endif\n\n #ifdef USE_VARIANT_MASK\n if (isSplatHiddenByVariantMask(splatUV)) {\n gl_Position = discardVec;\n return;\n }\n #endif\n\n // ---- Project the centre --------------------------------------------\n bool ortho = projectionMatrix[3][3] == 1.0;\n vec4 centerView = viewMatrix * vec4(worldCenter, 1.0);\n if (!ortho && centerView.z > 0.0) {\n gl_Position = discardVec;\n return;\n }\n vec4 centerProj = projectionMatrix * centerView;\n centerProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));\n\n // ---- Project the covariance to a screen-space ellipse ----------------\n vec3 covA, covB;\n computeCovariance(worldRotation.wxyz, worldScale, covA, covB);\n mat3 Vrk = mat3(\n covA.x, covA.y, covA.z,\n covA.y, covB.x, covB.y,\n covA.z, covB.y, covB.z\n );\n\n float focal = uViewport.x * projectionMatrix[0][0];\n vec3 v = centerView.xyz;\n vec3 vp = ortho ? vec3(0.0, 0.0, 1.0) : v;\n float J1 = focal / vp.z;\n vec2 J2 = -J1 / vp.z * vp.xy;\n mat3 J = mat3(\n J1, 0.0, J2.x,\n 0.0, J1, J2.y,\n 0.0, 0.0, 0.0\n );\n mat3 W = transpose(mat3(viewMatrix));\n mat3 T = W * J;\n mat3 cov = transpose(T) * Vrk * T;\n\n #ifdef USE_ANTIALIAS\n float detOrig = cov[0][0] * cov[1][1] - cov[0][1] * cov[0][1];\n float detBlur = (cov[0][0] + 0.3) * (cov[1][1] + 0.3) - cov[0][1] * cov[0][1];\n float aaFactor = sqrt(max(detOrig / detBlur, 0.0));\n #endif\n\n float diagonal1 = cov[0][0] + 0.3;\n float offDiagonal = cov[0][1];\n float diagonal2 = cov[1][1] + 0.3;\n float mid = 0.5 * (diagonal1 + diagonal2);\n float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));\n float lambda1 = mid + radius;\n float lambda2 = max(mid - radius, 0.1);\n\n float vmin = min(1024.0, min(uViewport.x, uViewport.y));\n float l1 = 2.0 * min(sqrt(2.0 * lambda1), vmin);\n float l2 = 2.0 * min(sqrt(2.0 * lambda2), vmin);\n if (max(l1, l2) < uMinPixelSize) {\n gl_Position = discardVec;\n return;\n }\n\n // Cull splats whose ellipse lies wholly off-screen.\n vec2 c = centerProj.ww / uViewport;\n if (any(greaterThan(abs(centerProj.xy) - vec2(max(l1, l2)) * c, centerProj.ww))) {\n gl_Position = discardVec;\n return;\n }\n\n vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));\n vec2 v1 = l1 * diagonalVector;\n vec2 v2 = l2 * vec2(diagonalVector.y, -diagonalVector.x);\n vec2 cornerUV = position.xy;\n vec2 offset = (cornerUV.x * v1 + cornerUV.y * v2) * c;\n\n // ---- Colour -----------------------------------------------------------\n vec4 clr = texelFetch(tColor, splatUV, 0);\n #ifdef USE_ANTIALIAS\n clr.a *= aaFactor;\n #endif\n\n clr.rgb *= computeLitFactor(worldCenter);\n clr.rgb = mix(clr.rgb, uTint.rgb, clamp(uTint.a, 0.0, 1.0));\n // Identity grade is a true no-op: skipping it keeps HDR splat colours unclamped.\n if (uGrade.x != 1.0 || uGrade.y != 1.0) {\n clr.rgb = (clr.rgb - 0.5) * uGrade.x + 0.5;\n float luma = dot(clr.rgb, vec3(0.2126, 0.7152, 0.0722));\n clr.rgb = mix(vec3(luma), clr.rgb, uGrade.y);\n clr.rgb = clamp(clr.rgb, 0.0, 1.0);\n }\n\n if (255.0 * clr.a <= 1.0) {\n gl_Position = discardVec;\n return;\n }\n\n // Shrink the quad to where the gaussian is still above 1/255: fewer\n // fragments, identical pixels.\n float clip = min(1.0, sqrt(log(255.0 * clr.a)) * 0.5);\n offset *= clip;\n\n gl_Position = centerProj + vec4(offset, 0.0, 0.0);\n gaussianUV = cornerUV * clip;\n\n vec3 rgb = max(clr.rgb, 0.0);\n if (uOutputLinear > 0.5) rgb = sRGBToLinear(rgb);\n gaussianColor = vec4(rgb, clr.a);\n}\n";
|
|
2
|
+
export declare const SPLAT_FRAGMENT_GLSL = "\nprecision highp float;\n\nin mediump vec2 gaussianUV;\nin mediump vec4 gaussianColor;\n\nlayout(location = 0) out highp vec4 fragColor;\n\nconst float EXP4 = exp(-4.0);\nconst float INV_EXP4 = 1.0 / (1.0 - EXP4);\n\n// exp(-4x) rescaled to hit exactly 0 at the quad edge (x = 1).\nfloat normExp(float x) {\n return (exp(x * -4.0) - EXP4) * INV_EXP4;\n}\n\nvoid main(void) {\n mediump float A = dot(gaussianUV, gaussianUV);\n if (A > 1.0) {\n discard;\n }\n mediump float alpha = normExp(A) * gaussianColor.a;\n if (alpha < 1.0 / 255.0) {\n discard;\n }\n // Premultiplied.\n fragColor = vec4(gaussianColor.rgb * alpha, alpha);\n}\n";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { InstancedBufferGeometry, RawShaderMaterial } from 'three';
|
|
2
|
+
import type { Camera, Mesh } from 'three';
|
|
3
|
+
import type { SplatEngine } from '../../engine';
|
|
4
|
+
import type { SplatRenderImpl } from '../backend';
|
|
5
|
+
import type { Splat } from '../splat';
|
|
6
|
+
/**
|
|
7
|
+
* How one {@link Splat} is drawn under `WebGLRenderer`: one instanced quad per
|
|
8
|
+
* gaussian, drawn back-to-front with premultiplied alpha.
|
|
9
|
+
*
|
|
10
|
+
* The instance attribute holds splat indices in draw order, so sorting only
|
|
11
|
+
* ever rewrites that one buffer; all splat data stays put in textures.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare class WebGLSplatImpl implements SplatRenderImpl {
|
|
16
|
+
readonly geometry: InstancedBufferGeometry;
|
|
17
|
+
readonly material: RawShaderMaterial;
|
|
18
|
+
/** The depth sort runs on the CPU, so it needs CPU-side centres. */
|
|
19
|
+
readonly needsCpuCenters = true;
|
|
20
|
+
private readonly _host;
|
|
21
|
+
private readonly _splat;
|
|
22
|
+
private readonly _engine;
|
|
23
|
+
private readonly _order;
|
|
24
|
+
private readonly _syncSorter;
|
|
25
|
+
private readonly _asyncSorter;
|
|
26
|
+
private _sortedVersion;
|
|
27
|
+
private _sortRequested;
|
|
28
|
+
private _lastDx;
|
|
29
|
+
private _lastDy;
|
|
30
|
+
private _lastDz;
|
|
31
|
+
/**
|
|
32
|
+
* @param asyncSort - Sort in a worker and keep drawing the previous order
|
|
33
|
+
* meanwhile. Right for large static splats; wrong for skinned ones, whose
|
|
34
|
+
* order would lag the pose.
|
|
35
|
+
*/
|
|
36
|
+
constructor(host: Mesh, splat: Splat, engine: SplatEngine, asyncSort: boolean);
|
|
37
|
+
/** True while a worker sort is in flight (always false for synchronous sorting). */
|
|
38
|
+
get sortPending(): boolean;
|
|
39
|
+
/** Force a re-sort on the next render even if the view direction is unchanged. */
|
|
40
|
+
requestSort(): void;
|
|
41
|
+
/** Rebuild the shader variant after a feature toggle on the splat. */
|
|
42
|
+
syncDefines(): void;
|
|
43
|
+
/** The bone textures are the splat's own; flagging them is all there is to do. */
|
|
44
|
+
syncBoneData(): void;
|
|
45
|
+
/** Group ids live in the splat's own texture, which it flags itself. */
|
|
46
|
+
syncVariantGroups(): void;
|
|
47
|
+
/** Refresh the culling sphere from the splat's current local bounds. */
|
|
48
|
+
syncBounds(): void;
|
|
49
|
+
beforeRender(renderer: unknown, camera: Camera): void;
|
|
50
|
+
private _sortFor;
|
|
51
|
+
dispose(): void;
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SplatEngine } from '../../engine';
|
|
2
|
+
import type { Mesh } from 'three/webgpu';
|
|
3
|
+
import type { SplatBackend, SplatRenderImpl } from '../backend';
|
|
4
|
+
import type { Splat } from '../splat';
|
|
5
|
+
/**
|
|
6
|
+
* Splat rendering for `WebGPURenderer` (`three/webgpu`), running on WebGPU.
|
|
7
|
+
*
|
|
8
|
+
* Skinning, projection and the depth sort all run in compute shaders, so the
|
|
9
|
+
* CPU does no per-splat work: adding an animated character costs a pose
|
|
10
|
+
* evaluation and a palette upload, not a pass over its splats.
|
|
11
|
+
*
|
|
12
|
+
* Importing `@viggle/splat-engine-three/webgpu` registers this backend, and
|
|
13
|
+
* splats then use it whenever a `WebGPURenderer` draws them; there is nothing
|
|
14
|
+
* to construct or pass.
|
|
15
|
+
*
|
|
16
|
+
* Requires the renderer's WebGPU backend. `WebGPURenderer`'s WebGL2 fallback
|
|
17
|
+
* (`forceWebGL`, or a browser without WebGPU) has no compute shaders; use
|
|
18
|
+
* `THREE.WebGLRenderer` there instead.
|
|
19
|
+
*
|
|
20
|
+
* @group Engine
|
|
21
|
+
*/
|
|
22
|
+
export declare class WebGPUSplatBackend implements SplatBackend {
|
|
23
|
+
private readonly _lighting;
|
|
24
|
+
createImpl(host: Mesh, splat: Splat, engine: SplatEngine): SplatRenderImpl;
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SplatEngine } from '../../engine';
|
|
2
|
+
type Node = any;
|
|
3
|
+
/** Point lights the WebGPU backend evaluates per splat. Extra lights are ignored. */
|
|
4
|
+
export declare const MAX_POINT_LIGHTS = 16;
|
|
5
|
+
/**
|
|
6
|
+
* The engine's lighting registry as TSL uniforms, shared by every WebGPU splat
|
|
7
|
+
* drawable of one engine.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare class WebGPULighting {
|
|
12
|
+
private readonly _engine;
|
|
13
|
+
private readonly _sunColor;
|
|
14
|
+
private readonly _sunIntensity;
|
|
15
|
+
private readonly _ambient;
|
|
16
|
+
private readonly _pointCount;
|
|
17
|
+
private readonly _pointPosRange;
|
|
18
|
+
private readonly _pointColor;
|
|
19
|
+
private readonly _pointPosRangeNode;
|
|
20
|
+
private readonly _pointColorNode;
|
|
21
|
+
private _version;
|
|
22
|
+
constructor(engine: SplatEngine);
|
|
23
|
+
/**
|
|
24
|
+
* Refresh the uniforms from the engine's lighting registry.
|
|
25
|
+
* @returns A number that changes whenever the lighting does.
|
|
26
|
+
*/
|
|
27
|
+
sync(): number;
|
|
28
|
+
/**
|
|
29
|
+
* The multiplier applied to a splat's baked colour: ambient, plus sun, plus
|
|
30
|
+
* range-attenuated point lights. With no lights it is exactly 1.
|
|
31
|
+
*/
|
|
32
|
+
litFactor(worldCenter: Node): Node;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
type Node = any;
|
|
2
|
+
/** Sort keys are 16 bits, sorted as two 8-bit digits. */
|
|
3
|
+
export declare const SORT_KEY_BITS = 16;
|
|
4
|
+
/**
|
|
5
|
+
* A stable least-significant-digit radix sort over 16-bit keys, in compute.
|
|
6
|
+
*
|
|
7
|
+
* Stability is the point. The obvious GPU counting sort - histogram, prefix
|
|
8
|
+
* sum, then every element claiming its slot with an atomic add - leaves the
|
|
9
|
+
* order inside a bucket to whichever thread wins the race, and that changes
|
|
10
|
+
* from frame to frame. For splats sharing a depth bucket (a surface seen
|
|
11
|
+
* face-on is full of them) the result is visible shimmer on a perfectly still
|
|
12
|
+
* frame. Here ties always resolve in element-index order, exactly as a CPU
|
|
13
|
+
* counting sort does, so a still frame is bit-stable.
|
|
14
|
+
*
|
|
15
|
+
* Each digit takes three steps:
|
|
16
|
+
*
|
|
17
|
+
* histogram count digits per block of {@link BLOCK} consecutive elements
|
|
18
|
+
* (atomic adds; counts do not depend on thread order).
|
|
19
|
+
* prefix for each digit, in parallel, walk the blocks turning counts
|
|
20
|
+
* into starting offsets; then one thread turns the 256 digit
|
|
21
|
+
* totals into digit bases.
|
|
22
|
+
* scatter one invocation per block walks its elements in order, placing
|
|
23
|
+
* each at its digit's running offset.
|
|
24
|
+
*
|
|
25
|
+
* The first digit's histogram is folded into the caller's own per-element
|
|
26
|
+
* pass through {@link GpuRadixSort.countFirstDigit}, saving a dispatch.
|
|
27
|
+
*/
|
|
28
|
+
export declare class GpuRadixSort {
|
|
29
|
+
readonly count: number;
|
|
30
|
+
/** Write each element's key here (`uint`, low 16 bits significant) before {@link GpuRadixSort.sortNodes} run. */
|
|
31
|
+
readonly keysWrite: Node;
|
|
32
|
+
/** Element indices in ascending key order, valid after {@link GpuRadixSort.sortNodes} have run. */
|
|
33
|
+
readonly orderRead: Node;
|
|
34
|
+
/** Run before the pass that writes keys: clears both digit histograms. */
|
|
35
|
+
readonly resetNode: Node;
|
|
36
|
+
/** Run, in order, after the pass that writes keys. */
|
|
37
|
+
readonly sortNodes: Node[];
|
|
38
|
+
private readonly _firstHistogram;
|
|
39
|
+
constructor(count: number);
|
|
40
|
+
/**
|
|
41
|
+
* Count `key`'s low digit for the element at `index`. Call from inside the
|
|
42
|
+
* compute pass that writes the keys.
|
|
43
|
+
*/
|
|
44
|
+
countFirstDigit(index: Node, key: Node): void;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { InstancedBufferGeometry, NodeMaterial } from 'three/webgpu';
|
|
2
|
+
import type { Camera, Mesh } from 'three/webgpu';
|
|
3
|
+
import type { SplatRenderImpl } from '../backend';
|
|
4
|
+
import type { Splat } from '../splat';
|
|
5
|
+
import type { WebGPULighting } from './lighting';
|
|
6
|
+
/** Groups a variant mask can address. The mask travels as a uniform array of this size. */
|
|
7
|
+
export declare const MAX_VARIANT_GROUPS = 64;
|
|
8
|
+
/**
|
|
9
|
+
* How one {@link Splat} is drawn under `WebGPURenderer`.
|
|
10
|
+
*
|
|
11
|
+
* Everything per-splat happens in compute, once per splat per frame:
|
|
12
|
+
*
|
|
13
|
+
* 1. project - skin, lift to world space, project the covariance to a
|
|
14
|
+
* screen-space ellipse, shade; write the result to a work
|
|
15
|
+
* buffer along with a 16-bit depth key.
|
|
16
|
+
* 2. sort - a stable radix sort of the keys ({@link GpuRadixSort}),
|
|
17
|
+
* giving splat indices in back-to-front order.
|
|
18
|
+
*
|
|
19
|
+
* Buffers are interleaved so no stage binds more than seven storage buffers:
|
|
20
|
+
* WebGPU only guarantees eight per stage, and some browsers stop there.
|
|
21
|
+
*
|
|
22
|
+
* The vertex stage then only expands each work-buffer entry into a quad. No
|
|
23
|
+
* per-splat work happens on the CPU, which is what lets this backend carry
|
|
24
|
+
* many animated characters: the WebGL backend re-skins every centre on the
|
|
25
|
+
* main thread each frame just to feed its sort.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare class WebGPUSplatImpl implements SplatRenderImpl {
|
|
30
|
+
readonly geometry: InstancedBufferGeometry;
|
|
31
|
+
readonly material: NodeMaterial;
|
|
32
|
+
/** Skinning for the sort happens in compute; the CPU never touches a splat. */
|
|
33
|
+
readonly needsCpuCenters = false;
|
|
34
|
+
private readonly _host;
|
|
35
|
+
private readonly _splat;
|
|
36
|
+
private readonly _lighting;
|
|
37
|
+
private _paletteArray;
|
|
38
|
+
private _paletteAttribute;
|
|
39
|
+
private _paletteVersion;
|
|
40
|
+
private _boneAttribute;
|
|
41
|
+
private _groupAttribute;
|
|
42
|
+
private readonly _variantMask;
|
|
43
|
+
/** [sort reset, project] - everything that depends on the camera or the pose. */
|
|
44
|
+
private _projectNodes;
|
|
45
|
+
/** The sort proper - needed only when the depth ORDER may have changed. */
|
|
46
|
+
private _sortNodes;
|
|
47
|
+
private readonly _lastInputs;
|
|
48
|
+
private readonly _inputs;
|
|
49
|
+
private readonly _lastSortDirection;
|
|
50
|
+
private _buildId;
|
|
51
|
+
private _sortedBuildId;
|
|
52
|
+
private _allNodes;
|
|
53
|
+
private readonly _u;
|
|
54
|
+
constructor(host: Mesh, splat: Splat, lighting: WebGPULighting);
|
|
55
|
+
/** The sort runs in the same frame as the draw; the order is never stale. */
|
|
56
|
+
get sortPending(): boolean;
|
|
57
|
+
/** Force the next frame to re-project and re-sort even if nothing appears to have changed. */
|
|
58
|
+
requestSort(): void;
|
|
59
|
+
syncDefines(): void;
|
|
60
|
+
syncBounds(): void;
|
|
61
|
+
syncBoneData(): void;
|
|
62
|
+
syncVariantGroups(): void;
|
|
63
|
+
/**
|
|
64
|
+
* (Re)create the storage buffers, compute nodes and material nodes. Runs at
|
|
65
|
+
* construction, and again when a feature toggle changes the shader or the
|
|
66
|
+
* palette outgrows its buffer.
|
|
67
|
+
*/
|
|
68
|
+
private _build;
|
|
69
|
+
beforeRender(renderer: any, camera: Camera): void;
|
|
70
|
+
dispose(): void;
|
|
71
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Loader } from 'three';
|
|
2
|
+
import type { LoadingManager } from 'three';
|
|
3
|
+
import { Animation } from '../animation';
|
|
4
|
+
import { SplatEngine } from '../engine';
|
|
5
|
+
import type { SplatMesh } from '../splat/splat-mesh';
|
|
6
|
+
import { SplatCharacter } from './splat-character';
|
|
7
|
+
/**
|
|
8
|
+
* Shared plumbing: fetch bytes the three.js way - through a `FileLoader`, so
|
|
9
|
+
* the `LoadingManager`, base path, request headers and credentials mode all
|
|
10
|
+
* apply - then hand them to an asynchronous `parseAsync`.
|
|
11
|
+
*/
|
|
12
|
+
declare abstract class SplatLoaderBase<T> extends Loader<T> {
|
|
13
|
+
/**
|
|
14
|
+
* The engine loaded objects belong to: the skeleton library, clip registry
|
|
15
|
+
* and lights they share. Leave unset to use {@link SplatEngine.shared}.
|
|
16
|
+
*/
|
|
17
|
+
engine: SplatEngine | null;
|
|
18
|
+
constructor(manager?: LoadingManager);
|
|
19
|
+
setEngine(engine: SplatEngine | null): this;
|
|
20
|
+
/** Decode bytes already in memory. `name` labels the result; for a loaded file it is the file's base name. */
|
|
21
|
+
abstract parseAsync(data: ArrayBuffer, name?: string): Promise<T>;
|
|
22
|
+
load(url: string, onLoad: (result: T) => void, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): void;
|
|
23
|
+
protected get resolvedEngine(): SplatEngine;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Loads a `.vsplat` character bundle (or a legacy `.bin` one - the container
|
|
27
|
+
* is recognised from the file's own bytes) as a {@link SplatCharacter}.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* const character = await new VSplatLoader().loadAsync('/assets/hero.vsplat');
|
|
31
|
+
* scene.add(character);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @group Loaders
|
|
35
|
+
*/
|
|
36
|
+
export declare class VSplatLoader extends SplatLoaderBase<SplatCharacter> {
|
|
37
|
+
parseAsync(data: ArrayBuffer, name?: string): Promise<SplatCharacter>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Loads an animation clip for {@link SplatCharacter.play}: a MetaHuman-rig
|
|
41
|
+
* `.glb`, or an `.anim` file.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const run = await new SplatClipLoader().loadAsync('/assets/run.glb');
|
|
45
|
+
* character.play(run);
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* A clip is not tied to one character; play the same one on as many as you like.
|
|
49
|
+
*
|
|
50
|
+
* @group Loaders
|
|
51
|
+
*/
|
|
52
|
+
export declare class SplatClipLoader extends SplatLoaderBase<Animation> {
|
|
53
|
+
/** For a `.glb` holding several animations, the one to take. Default: the first. */
|
|
54
|
+
animationName: string | undefined;
|
|
55
|
+
parseAsync(data: ArrayBuffer, name?: string): Promise<Animation>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Loads an uncompressed 3D Gaussian Splatting `.ply` as a static
|
|
59
|
+
* {@link SplatMesh} - an environment, a prop, a scanned object.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const room = await new SplatPLYLoader().loadAsync('/assets/room.ply');
|
|
63
|
+
* room.rotation.x = Math.PI; // many captures are authored Y-down
|
|
64
|
+
* scene.add(room);
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* The mesh is given `renderOrder = -1` so it draws before characters, which
|
|
68
|
+
* then always composite on top of it. Splats do not write depth, so without
|
|
69
|
+
* that a large environment whose origin happens to be nearer the camera would
|
|
70
|
+
* be drawn over a character standing in it.
|
|
71
|
+
*
|
|
72
|
+
* @group Loaders
|
|
73
|
+
*/
|
|
74
|
+
export declare class SplatPLYLoader extends SplatLoaderBase<SplatMesh> {
|
|
75
|
+
parseAsync(data: ArrayBuffer, name?: string): Promise<SplatMesh>;
|
|
76
|
+
}
|
|
77
|
+
export {};
|