babylon.quarks 0.26.0 → 0.27.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/CHANGELOG.md +769 -8
- package/README.md +10 -8
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.cts +6683 -0
- package/dist/types/index.d.ts +1959 -588
- package/package.json +12 -6
- package/src/BatchedRenderer.ts +204 -101
- package/src/EffectPool.ts +4 -1
- package/src/MeshSurfaceEmitter.ts +29 -4
- package/src/ParticleEmitter.ts +45 -15
- package/src/ParticleSystem.ts +909 -2061
- package/src/QuarksLoader.ts +421 -67
- package/src/QuarksPrefab.ts +91 -4
- package/src/QuarksSerializer.ts +484 -0
- package/src/QuarksUtil.ts +15 -0
- package/src/SpriteBatch.ts +106 -246
- package/src/TrailBatch.ts +533 -342
- package/src/VFXBatch.ts +236 -136
- package/src/batchKey.ts +237 -0
- package/src/effectResources.ts +79 -0
- package/src/envAtlas.ts +119 -17
- package/src/index.ts +32 -33
- package/src/nodeId.ts +13 -0
- package/src/shaderProgram.ts +325 -0
- package/src/shaderVariants.ts +225 -0
- package/src/shaders/chunks/fog.ts +4 -2
- package/src/shaders/chunks/sheet.ts +111 -0
- package/src/shaders/chunks/soft.ts +84 -0
- package/src/shaders/chunks/surface.ts +10 -0
- package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
- package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
- package/src/shaders/particle_frag.glsl.ts +3 -22
- package/src/shaders/particle_frag.wgsl.ts +3 -23
- package/src/shaders/particle_physics_frag.glsl.ts +12 -25
- package/src/shaders/particle_physics_frag.wgsl.ts +12 -26
- package/src/shaders/particle_vert.glsl.ts +5 -27
- package/src/shaders/particle_vert.wgsl.ts +5 -26
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
- package/src/shaders/trail_frag.glsl.ts +11 -1
- package/src/shaders/trail_frag.wgsl.ts +11 -0
- package/src/shaders/trail_vert.glsl.ts +45 -36
- package/src/shaders/trail_vert.wgsl.ts +46 -40
- package/src/softDepth.ts +136 -0
- package/src/system/BehaviorPlan.ts +85 -0
- package/src/system/BoundsTracker.ts +181 -0
- package/src/system/EmissionController.ts +360 -0
- package/src/system/EventDispatcher.ts +44 -0
- package/src/system/ParticleSpawner.ts +274 -0
- package/src/system/SimulationClock.ts +130 -0
- package/src/system/geometry.ts +111 -0
- package/src/system/integrate.ts +428 -0
- package/src/system/materialResolve.ts +116 -0
- package/src/system/normalize.ts +95 -0
- package/src/system/serialization.ts +401 -0
- package/src/system/settingsSchema.ts +317 -0
- package/src/system/types.ts +410 -0
- package/src/textureSource.ts +30 -0
- package/src/trailSettings.ts +51 -0
- package/src/BatchedParticleRenderer.ts +0 -3
- package/src/materials/ParticleMaterials.ts +0 -23
- package/src/materials/index.ts +0 -1
- package/src/shaders/chunks/index.ts +0 -1
- package/src/shaders/chunks/register-shader-chunks.ts +0 -32
- package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
- package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
- package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
- package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
- package/src/shaders/index.ts +0 -9
- package/src/shaders/local_particle_vert.glsl.ts +0 -50
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
|
|
2
|
+
import {sheetVertexDeclarationGLSL, sheetVertexGLSL} from './chunks/sheet';
|
|
2
3
|
|
|
3
4
|
export default /* glsl */ `
|
|
4
5
|
// Per-vertex attributes
|
|
@@ -17,12 +18,7 @@ attribute vec4 velocity;
|
|
|
17
18
|
uniform mat4 world;
|
|
18
19
|
uniform mat4 view;
|
|
19
20
|
uniform mat4 projection;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#ifdef UV_TILE
|
|
23
|
-
uniform float tileCountX;
|
|
24
|
-
uniform float tileCountY;
|
|
25
|
-
#endif
|
|
21
|
+
${sheetVertexDeclarationGLSL}
|
|
26
22
|
|
|
27
23
|
#ifdef PIVOT
|
|
28
24
|
// Streak offset in particle-size units: x from head to tail, y across.
|
|
@@ -36,10 +32,6 @@ varying vec4 vColor;
|
|
|
36
32
|
// The particle's own UV, never moved by the sheet (UV channel 1).
|
|
37
33
|
varying vec2 vUV1;
|
|
38
34
|
#endif
|
|
39
|
-
#ifdef TILE_BLEND
|
|
40
|
-
varying vec2 vUV2;
|
|
41
|
-
varying float vTileBlend;
|
|
42
|
-
#endif
|
|
43
35
|
#ifdef SOFT_PARTICLES
|
|
44
36
|
varying vec4 projPosition;
|
|
45
37
|
varying float linearDepth;
|
|
@@ -61,15 +53,11 @@ void main() {
|
|
|
61
53
|
float vlength = length(viewVelocity);
|
|
62
54
|
// Stretch direction is the velocity direction; only a genuinely motionless particle has none.
|
|
63
55
|
// Deriving it via normalize keeps the stretch aligned even when the speed contribution is ~0
|
|
64
|
-
// (e.g.
|
|
56
|
+
// (e.g. a speed factor of 0), instead of collapsing the whole burst onto a fixed screen axis.
|
|
65
57
|
vec3 vdir = vlength > 0.000001 ? viewVelocity / vlength : vec3(0.0, 0.0, 1.0);
|
|
66
|
-
|
|
67
|
-
// Seen straight along its length a beam has no side to face; any will do.
|
|
58
|
+
// Seen straight along its length a streak has no side to face; any will do.
|
|
68
59
|
vec3 sideRaw = cross(mvPosition.xyz, vdir);
|
|
69
60
|
vec3 side = dot(sideRaw, sideRaw) > 1e-12 ? normalize(sideRaw) : vec3(1.0, 0.0, 0.0);
|
|
70
|
-
#else
|
|
71
|
-
vec3 side = normalize(cross(mvPosition.xyz, vdir));
|
|
72
|
-
#endif
|
|
73
61
|
#ifdef SIZE_LIMITS
|
|
74
62
|
// The streak's width on screen as a fraction of the viewport height, held
|
|
75
63
|
// between the limits; a perspective projection divides by the view depth.
|
|
@@ -98,25 +86,11 @@ ${fogVertexGLSL('mvPosition.xyz')}
|
|
|
98
86
|
vec2 quadUV = vec2(size.x < 0.0 ? 1.0 - uv.x : uv.x, size.y < 0.0 ? 1.0 - uv.y : uv.y);
|
|
99
87
|
#ifdef SOFT_PARTICLES
|
|
100
88
|
projPosition = gl_Position;
|
|
101
|
-
|
|
89
|
+
// View depth is +z ahead of a left-handed camera and -z ahead of a right-handed one.
|
|
90
|
+
linearDepth = abs(mvPosition.z);
|
|
102
91
|
#endif
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
vec2 tc = vec2(tileCountX, tileCountY);
|
|
106
|
-
float baseTile = floor(uvTile);
|
|
107
|
-
float tileU = mod(baseTile, tc.x) / tc.x;
|
|
108
|
-
float tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
109
|
-
vUV = quadUV / tc + vec2(tileU, tileV);
|
|
110
|
-
#ifdef TILE_BLEND
|
|
111
|
-
float nextTile = ceil(uvTile);
|
|
112
|
-
float nextU = mod(nextTile, tc.x) / tc.x;
|
|
113
|
-
float nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
114
|
-
vUV2 = quadUV / tc + vec2(nextU, nextV);
|
|
115
|
-
vTileBlend = fract(uvTile);
|
|
116
|
-
#endif
|
|
117
|
-
#else
|
|
118
|
-
vUV = quadUV;
|
|
119
|
-
#endif
|
|
93
|
+
${sheetVertexGLSL('quadUV')}
|
|
120
94
|
|
|
121
95
|
#ifdef UV1
|
|
122
96
|
vUV1 = quadUV;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {fogVertexDeclarationWGSL, fogVertexWGSL} from './chunks/fog';
|
|
2
|
+
import {sheetVertexDeclarationWGSL, sheetVertexWGSL} from './chunks/sheet';
|
|
2
3
|
|
|
3
4
|
export default /* wgsl */ `
|
|
4
5
|
// Per-vertex attributes
|
|
@@ -17,12 +18,7 @@ attribute velocity: vec4f;
|
|
|
17
18
|
uniform world: mat4x4f;
|
|
18
19
|
uniform view: mat4x4f;
|
|
19
20
|
uniform projection: mat4x4f;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#ifdef UV_TILE
|
|
23
|
-
uniform tileCountX: f32;
|
|
24
|
-
uniform tileCountY: f32;
|
|
25
|
-
#endif
|
|
21
|
+
${sheetVertexDeclarationWGSL}
|
|
26
22
|
|
|
27
23
|
#ifdef PIVOT
|
|
28
24
|
// Streak offset in particle-size units: x from head to tail, y across.
|
|
@@ -36,10 +32,6 @@ varying vColor: vec4f;
|
|
|
36
32
|
// The particle's own UV, never moved by the sheet (UV channel 1).
|
|
37
33
|
varying vUV1: vec2f;
|
|
38
34
|
#endif
|
|
39
|
-
#ifdef TILE_BLEND
|
|
40
|
-
varying vUV2: vec2f;
|
|
41
|
-
varying vTileBlend: f32;
|
|
42
|
-
#endif
|
|
43
35
|
#ifdef SOFT_PARTICLES
|
|
44
36
|
varying projPosition: vec4f;
|
|
45
37
|
varying linearDepth: f32;
|
|
@@ -64,21 +56,17 @@ fn main(input: VertexInputs) -> FragmentInputs {
|
|
|
64
56
|
let vlength = length(viewVelocity);
|
|
65
57
|
// Stretch direction is the velocity direction; only a genuinely motionless particle has none.
|
|
66
58
|
// Deriving it via normalize keeps the stretch aligned even when the speed contribution is ~0
|
|
67
|
-
// (e.g.
|
|
59
|
+
// (e.g. a speed factor of 0), instead of collapsing the whole burst onto a fixed screen axis.
|
|
68
60
|
var vdir = vec3f(0.0, 0.0, 1.0);
|
|
69
61
|
if (vlength > 0.000001) {
|
|
70
62
|
vdir = viewVelocity / vlength;
|
|
71
63
|
}
|
|
72
|
-
|
|
73
|
-
// Seen straight along its length a beam has no side to face; any will do.
|
|
64
|
+
// Seen straight along its length a streak has no side to face; any will do.
|
|
74
65
|
let sideRaw = cross(mvPosition.xyz, vdir);
|
|
75
66
|
var side = vec3f(1.0, 0.0, 0.0);
|
|
76
67
|
if (dot(sideRaw, sideRaw) > 1e-12) {
|
|
77
68
|
side = normalize(sideRaw);
|
|
78
69
|
}
|
|
79
|
-
#else
|
|
80
|
-
let side = normalize(cross(mvPosition.xyz, vdir));
|
|
81
|
-
#endif
|
|
82
70
|
#ifdef SIZE_LIMITS
|
|
83
71
|
// The streak's width on screen as a fraction of the viewport height, held
|
|
84
72
|
// between the limits; a perspective projection divides by the view depth.
|
|
@@ -113,25 +101,11 @@ ${fogVertexWGSL('mvPosition.xyz')}
|
|
|
113
101
|
let quadUV = select(vertexInputs.uv, 1.0 - vertexInputs.uv, vertexInputs.size.xy < vec2f(0.0));
|
|
114
102
|
#ifdef SOFT_PARTICLES
|
|
115
103
|
vertexOutputs.projPosition = clipPosition;
|
|
116
|
-
|
|
104
|
+
// View depth is +z ahead of a left-handed camera and -z ahead of a right-handed one.
|
|
105
|
+
vertexOutputs.linearDepth = abs(mvPosition.z);
|
|
117
106
|
#endif
|
|
118
107
|
|
|
119
|
-
|
|
120
|
-
let tc = vec2f(uniforms.tileCountX, uniforms.tileCountY);
|
|
121
|
-
let baseTile = floor(vertexInputs.uvTile);
|
|
122
|
-
let tileU = (baseTile % tc.x) / tc.x;
|
|
123
|
-
let tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
124
|
-
vertexOutputs.vUV = quadUV / tc + vec2f(tileU, tileV);
|
|
125
|
-
#ifdef TILE_BLEND
|
|
126
|
-
let nextTile = ceil(vertexInputs.uvTile);
|
|
127
|
-
let nextU = (nextTile % tc.x) / tc.x;
|
|
128
|
-
let nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
129
|
-
vertexOutputs.vUV2 = quadUV / tc + vec2f(nextU, nextV);
|
|
130
|
-
vertexOutputs.vTileBlend = fract(vertexInputs.uvTile);
|
|
131
|
-
#endif
|
|
132
|
-
#else
|
|
133
|
-
vertexOutputs.vUV = quadUV;
|
|
134
|
-
#endif
|
|
108
|
+
${sheetVertexWGSL('quadUV')}
|
|
135
109
|
|
|
136
110
|
#ifdef UV1
|
|
137
111
|
vertexOutputs.vUV1 = quadUV;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
|
|
2
|
+
import {softFragmentDeclarationGLSL, softFragmentGLSL} from './chunks/soft';
|
|
2
3
|
import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
|
|
3
4
|
|
|
4
5
|
export default /* glsl */ `
|
|
@@ -12,13 +13,22 @@ varying vec2 vUV1;
|
|
|
12
13
|
uniform sampler2D map;
|
|
13
14
|
#endif
|
|
14
15
|
|
|
16
|
+
#ifdef USE_ALPHATEST
|
|
17
|
+
uniform float alphaTest;
|
|
18
|
+
#endif
|
|
19
|
+
|
|
15
20
|
${fogFragmentDeclarationGLSL}
|
|
21
|
+
${softFragmentDeclarationGLSL}
|
|
16
22
|
${surfaceFragmentDeclarationGLSL}
|
|
17
23
|
void main() {
|
|
18
24
|
${surfaceColorGLSL(false)}
|
|
25
|
+
#ifdef USE_ALPHATEST
|
|
26
|
+
if (baseColor.a < alphaTest) discard;
|
|
27
|
+
#else
|
|
19
28
|
if (baseColor.a < 0.01) discard;
|
|
29
|
+
#endif
|
|
20
30
|
|
|
21
31
|
${fogFragmentGLSL('baseColor.rgb')}
|
|
22
32
|
gl_FragColor = baseColor;
|
|
23
|
-
}
|
|
33
|
+
${softFragmentGLSL('gl_FragColor')}}
|
|
24
34
|
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
|
|
2
|
+
import {softFragmentDeclarationWGSL, softFragmentWGSL} from './chunks/soft';
|
|
2
3
|
import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
|
|
3
4
|
|
|
4
5
|
export default /* wgsl */ `
|
|
@@ -13,14 +14,24 @@ var mapSampler: sampler;
|
|
|
13
14
|
var map: texture_2d<f32>;
|
|
14
15
|
#endif
|
|
15
16
|
|
|
17
|
+
#ifdef USE_ALPHATEST
|
|
18
|
+
uniform alphaTest: f32;
|
|
19
|
+
#endif
|
|
20
|
+
|
|
16
21
|
${fogFragmentDeclarationWGSL}
|
|
22
|
+
${softFragmentDeclarationWGSL}
|
|
17
23
|
${surfaceFragmentDeclarationWGSL}
|
|
18
24
|
@fragment
|
|
19
25
|
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
20
26
|
${surfaceColorWGSL(false)}
|
|
27
|
+
#ifdef USE_ALPHATEST
|
|
28
|
+
if (baseColor.a < uniforms.alphaTest) { discard; }
|
|
29
|
+
#else
|
|
21
30
|
if (baseColor.a < 0.01) { discard; }
|
|
31
|
+
#endif
|
|
22
32
|
|
|
23
33
|
${fogFragmentWGSL('baseColor')}
|
|
34
|
+
${softFragmentWGSL('baseColor')}
|
|
24
35
|
fragmentOutputs.color = baseColor;
|
|
25
36
|
}
|
|
26
37
|
`;
|
|
@@ -11,9 +11,6 @@ attribute vec4 color;
|
|
|
11
11
|
|
|
12
12
|
uniform mat4 view;
|
|
13
13
|
uniform mat4 projection;
|
|
14
|
-
uniform float lineWidth;
|
|
15
|
-
uniform vec2 resolution;
|
|
16
|
-
uniform float sizeAttenuation;
|
|
17
14
|
|
|
18
15
|
varying vec2 vUV;
|
|
19
16
|
varying vec4 vColor;
|
|
@@ -23,44 +20,56 @@ attribute vec2 uv2;
|
|
|
23
20
|
varying vec2 vUV1;
|
|
24
21
|
#endif
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
#ifdef SOFT_PARTICLES
|
|
24
|
+
varying vec4 projPosition;
|
|
25
|
+
varying float linearDepth;
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
// The unit tangent of a segment, or zero for a segment of no length.
|
|
29
|
+
vec3 trailDirection(vec3 d) {
|
|
30
|
+
float l = dot(d, d);
|
|
31
|
+
return l > 1e-12 ? d * inversesqrt(l) : vec3(0.0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The direction a ribbon point is widened along, in view space: across the trail's tangent and
|
|
35
|
+
// the line of sight, as Unity's View alignment and Niagara's ribbon facing do. Stays finite at
|
|
36
|
+
// repeated points, at a hairpin and for a segment pointing at the camera.
|
|
37
|
+
vec3 trailSide(vec3 previousPoint, vec3 point, vec3 nextPoint) {
|
|
38
|
+
vec3 incoming = trailDirection(point - previousPoint);
|
|
39
|
+
vec3 outgoing = trailDirection(nextPoint - point);
|
|
40
|
+
vec3 tangent = incoming + outgoing;
|
|
41
|
+
if (dot(tangent, tangent) < 1e-6) {
|
|
42
|
+
// A hairpin turns the two directions against each other: face the way it now goes.
|
|
43
|
+
tangent = dot(outgoing, outgoing) > 0.0 ? outgoing : incoming;
|
|
44
|
+
}
|
|
45
|
+
// The line of sight: to the point for a perspective camera, the view axis for an orthographic one.
|
|
46
|
+
vec3 sight = projection[3][3] > 0.5 ? vec3(0.0, 0.0, 1.0) : point;
|
|
47
|
+
vec3 side = cross(tangent, sight);
|
|
48
|
+
if (dot(side, side) < 1e-12) {
|
|
49
|
+
side = cross(tangent, vec3(0.0, 0.0, 1.0));
|
|
50
|
+
}
|
|
51
|
+
if (dot(side, side) < 1e-12) {
|
|
52
|
+
side = vec3(1.0, 0.0, 0.0);
|
|
53
|
+
}
|
|
54
|
+
return normalize(side);
|
|
30
55
|
}
|
|
31
56
|
|
|
32
57
|
${fogVertexDeclarationGLSL}
|
|
33
58
|
void main() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
vec2 prevP = fix(prevPos, aspect);
|
|
41
|
-
vec2 nextP = fix(nextPos, aspect);
|
|
42
|
-
|
|
43
|
-
float w = lineWidth * width;
|
|
44
|
-
vec2 dir;
|
|
45
|
-
if (distance(nextP, currentP) < 0.0001) {
|
|
46
|
-
dir = normalize(currentP - prevP);
|
|
47
|
-
} else if (distance(prevP, currentP) < 0.0001) {
|
|
48
|
-
dir = normalize(nextP - currentP);
|
|
49
|
-
} else {
|
|
50
|
-
vec2 dir1 = normalize(currentP - prevP);
|
|
51
|
-
vec2 dir2 = normalize(nextP - currentP);
|
|
52
|
-
dir = normalize(dir1 + dir2);
|
|
59
|
+
vec3 point = (view * vec4(position, 1.0)).xyz;
|
|
60
|
+
// A swept trail lays its vertices out on the CPU and gives them no width.
|
|
61
|
+
if (width > 0.0) {
|
|
62
|
+
vec3 previousPoint = (view * vec4(previous, 1.0)).xyz;
|
|
63
|
+
vec3 nextPoint = (view * vec4(next, 1.0)).xyz;
|
|
64
|
+
point += trailSide(previousPoint, point, nextPoint) * (0.5 * width * side);
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
finalPosition.xy += normal.xy * side;
|
|
62
|
-
gl_Position = finalPosition;
|
|
63
|
-
${fogVertexGLSL('(view * vec4(position, 1.0)).xyz')}
|
|
66
|
+
gl_Position = projection * vec4(point, 1.0);
|
|
67
|
+
${fogVertexGLSL('point')}
|
|
68
|
+
#ifdef SOFT_PARTICLES
|
|
69
|
+
projPosition = gl_Position;
|
|
70
|
+
// View depth is +z ahead of a left-handed camera and -z ahead of a right-handed one.
|
|
71
|
+
linearDepth = abs(point.z);
|
|
72
|
+
#endif
|
|
64
73
|
vUV = uv;
|
|
65
74
|
#ifdef UV1
|
|
66
75
|
vUV1 = uv2;
|
|
@@ -11,9 +11,6 @@ attribute color: vec4f;
|
|
|
11
11
|
|
|
12
12
|
uniform view: mat4x4f;
|
|
13
13
|
uniform projection: mat4x4f;
|
|
14
|
-
uniform lineWidth: f32;
|
|
15
|
-
uniform resolution: vec2f;
|
|
16
|
-
uniform sizeAttenuation: f32;
|
|
17
14
|
|
|
18
15
|
varying vUV: vec2f;
|
|
19
16
|
varying vColor: vec4f;
|
|
@@ -23,49 +20,58 @@ attribute uv2: vec2f;
|
|
|
23
20
|
varying vUV1: vec2f;
|
|
24
21
|
#endif
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
#ifdef SOFT_PARTICLES
|
|
24
|
+
varying projPosition: vec4f;
|
|
25
|
+
varying linearDepth: f32;
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
// The unit tangent of a segment, or zero for a segment of no length.
|
|
29
|
+
fn trailDirection(d: vec3f) -> vec3f {
|
|
30
|
+
let l = dot(d, d);
|
|
31
|
+
return select(vec3f(0.0), d * inverseSqrt(max(l, 1e-24)), l > 1e-12);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The direction a ribbon point is widened along, in view space: across the trail's tangent and
|
|
35
|
+
// the line of sight, as Unity's View alignment and Niagara's ribbon facing do. Stays finite at
|
|
36
|
+
// repeated points, at a hairpin and for a segment pointing at the camera.
|
|
37
|
+
fn trailSide(previousPoint: vec3f, point: vec3f, nextPoint: vec3f) -> vec3f {
|
|
38
|
+
let incoming = trailDirection(point - previousPoint);
|
|
39
|
+
let outgoing = trailDirection(nextPoint - point);
|
|
40
|
+
var tangent = incoming + outgoing;
|
|
41
|
+
if (dot(tangent, tangent) < 1e-6) {
|
|
42
|
+
// A hairpin turns the two directions against each other: face the way it now goes.
|
|
43
|
+
tangent = select(incoming, outgoing, dot(outgoing, outgoing) > 0.0);
|
|
44
|
+
}
|
|
45
|
+
// The line of sight: to the point for a perspective camera, the view axis for an orthographic one.
|
|
46
|
+
let sight = select(point, vec3f(0.0, 0.0, 1.0), uniforms.projection[3][3] > 0.5);
|
|
47
|
+
var side = cross(tangent, sight);
|
|
48
|
+
if (dot(side, side) < 1e-12) {
|
|
49
|
+
side = cross(tangent, vec3f(0.0, 0.0, 1.0));
|
|
50
|
+
}
|
|
51
|
+
if (dot(side, side) < 1e-12) {
|
|
52
|
+
side = vec3f(1.0, 0.0, 0.0);
|
|
53
|
+
}
|
|
54
|
+
return normalize(side);
|
|
30
55
|
}
|
|
31
56
|
|
|
32
57
|
${fogVertexDeclarationWGSL}
|
|
33
58
|
@vertex
|
|
34
59
|
fn main(input: VertexInputs) -> FragmentInputs {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let prevP = fix(prevPos, aspect);
|
|
42
|
-
let nextP = fix(nextPos, aspect);
|
|
43
|
-
|
|
44
|
-
let w = uniforms.lineWidth * vertexInputs.width;
|
|
45
|
-
var dir: vec2f;
|
|
46
|
-
if (distance(nextP, currentP) < 0.0001) {
|
|
47
|
-
dir = normalize(currentP - prevP);
|
|
48
|
-
} else if (distance(prevP, currentP) < 0.0001) {
|
|
49
|
-
dir = normalize(nextP - currentP);
|
|
50
|
-
} else {
|
|
51
|
-
let dir1 = normalize(currentP - prevP);
|
|
52
|
-
let dir2 = normalize(nextP - currentP);
|
|
53
|
-
dir = normalize(dir1 + dir2);
|
|
60
|
+
var point = (uniforms.view * vec4f(vertexInputs.position, 1.0)).xyz;
|
|
61
|
+
// A swept trail lays its vertices out on the CPU and gives them no width.
|
|
62
|
+
if (vertexInputs.width > 0.0) {
|
|
63
|
+
let previousPoint = (uniforms.view * vec4f(vertexInputs.previous, 1.0)).xyz;
|
|
64
|
+
let nextPoint = (uniforms.view * vec4f(vertexInputs.next, 1.0)).xyz;
|
|
65
|
+
point += trailSide(previousPoint, point, nextPoint) * (0.5 * vertexInputs.width * vertexInputs.side);
|
|
54
66
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
finalPosition.x += normal.x * vertexInputs.side;
|
|
65
|
-
finalPosition.y += normal.y * vertexInputs.side;
|
|
66
|
-
|
|
67
|
-
vertexOutputs.position = finalPosition;
|
|
68
|
-
${fogVertexWGSL('(uniforms.view * vec4f(vertexInputs.position, 1.0)).xyz')}
|
|
67
|
+
let clipPosition = uniforms.projection * vec4f(point, 1.0);
|
|
68
|
+
vertexOutputs.position = clipPosition;
|
|
69
|
+
${fogVertexWGSL('point')}
|
|
70
|
+
#ifdef SOFT_PARTICLES
|
|
71
|
+
vertexOutputs.projPosition = clipPosition;
|
|
72
|
+
// View depth is +z ahead of a left-handed camera and -z ahead of a right-handed one.
|
|
73
|
+
vertexOutputs.linearDepth = abs(point.z);
|
|
74
|
+
#endif
|
|
69
75
|
vertexOutputs.vUV = vertexInputs.uv;
|
|
70
76
|
#ifdef UV1
|
|
71
77
|
vertexOutputs.vUV1 = vertexInputs.uv2;
|
package/src/softDepth.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {Camera} from '@babylonjs/core/Cameras/camera';
|
|
2
|
+
import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
3
|
+
import type {Vector4} from '@babylonjs/core/Maths/math.vector';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a soft-particle depth texture stores per pixel, so the shaders can turn it back into the
|
|
7
|
+
* distance from the camera. These are the three outputs of Babylon's `DepthRenderer`:
|
|
8
|
+
*
|
|
9
|
+
* - `'linear'`: Babylon's default depth metric, `scene.enableDepthRenderer()`. Linear in view
|
|
10
|
+
* depth between the camera's near and far planes.
|
|
11
|
+
* - `'nonLinear'`: the depth buffer value, `enableDepthRenderer(camera, true)`. This is what
|
|
12
|
+
* Unity's `_CameraDepthTexture` holds and what `LinearEyeDepth` undoes.
|
|
13
|
+
* - `'viewZ'`: the view-space Z itself, `enableDepthRenderer(camera, false, true, sampling, true)`.
|
|
14
|
+
* Exact at any range and with an infinite far plane (`camera.maxZ = 0`), which the other two
|
|
15
|
+
* cannot represent; this is the closest match to Unreal's SceneDepth.
|
|
16
|
+
*
|
|
17
|
+
* Perspective and orthographic cameras, both NDC depth ranges (WebGL, WebGPU) and a reverse
|
|
18
|
+
* depth buffer are all handled, from the camera drawing the particles. With a reverse depth buffer
|
|
19
|
+
* and `'nonLinear'`, clear the depth renderer to 0 (`depthRenderer.clearColor.r = 0`): it clears
|
|
20
|
+
* to 1 by default, which a reverse buffer reads as the near plane, so empty sky would fade every
|
|
21
|
+
* particle in front of it. A packed `'viewZ'` cannot be decoded.
|
|
22
|
+
*/
|
|
23
|
+
export type SoftDepthEncoding = 'linear' | 'nonLinear' | 'viewZ';
|
|
24
|
+
|
|
25
|
+
/** How a depth texture given to {@link BatchedRenderer.setDepthTexture} is read. */
|
|
26
|
+
export interface DepthTextureOptions {
|
|
27
|
+
/** What the texture stores. Defaults to the encoding of the `DepthRenderer` given, else `'linear'`. */
|
|
28
|
+
encoding?: SoftDepthEncoding;
|
|
29
|
+
/**
|
|
30
|
+
* The depth is packed into the four 8-bit channels, as a `DepthRenderer` of type
|
|
31
|
+
* `UNSIGNED_BYTE` writes it. Defaults to the `DepthRenderer`'s `isPacked`, else false.
|
|
32
|
+
*/
|
|
33
|
+
packed?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The depth texture a batch's soft particles read, and how. */
|
|
37
|
+
export interface DepthBinding {
|
|
38
|
+
texture: BaseTexture;
|
|
39
|
+
encoding: SoftDepthEncoding;
|
|
40
|
+
packed: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The parts of a Babylon `DepthRenderer` read here, without importing it and its side effects. */
|
|
44
|
+
interface DepthRendererLike {
|
|
45
|
+
getDepthMap(): BaseTexture;
|
|
46
|
+
readonly isPacked: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** True for a Babylon `DepthRenderer`, as opposed to a texture. */
|
|
50
|
+
export function isDepthRenderer(value: unknown): value is DepthRendererLike {
|
|
51
|
+
return typeof (value as DepthRendererLike | null)?.getDepthMap === 'function';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The binding for a depth texture or `DepthRenderer`, with its encoding read off the renderer. */
|
|
55
|
+
export function depthBinding(depth: BaseTexture | DepthRendererLike, options: DepthTextureOptions = {}): DepthBinding {
|
|
56
|
+
const binding = readBinding(depth, options);
|
|
57
|
+
if (binding.packed && binding.encoding === 'viewZ') {
|
|
58
|
+
// Packing keeps only the fraction of the value, which loses a view Z past 1 unit.
|
|
59
|
+
throw new Error('[babylon.quarks] A packed view-Z depth cannot be decoded; use a float depth texture');
|
|
60
|
+
}
|
|
61
|
+
return binding;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readBinding(depth: BaseTexture | DepthRendererLike, options: DepthTextureOptions): DepthBinding {
|
|
65
|
+
if (isDepthRenderer(depth)) {
|
|
66
|
+
// DepthRenderer keeps its encoding private; these are its constructor's own fields.
|
|
67
|
+
const stored = depth as unknown as {_storeCameraSpaceZ?: boolean; _storeNonLinearDepth?: boolean};
|
|
68
|
+
const detected: SoftDepthEncoding = stored._storeCameraSpaceZ
|
|
69
|
+
? 'viewZ'
|
|
70
|
+
: stored._storeNonLinearDepth
|
|
71
|
+
? 'nonLinear'
|
|
72
|
+
: 'linear';
|
|
73
|
+
return {
|
|
74
|
+
texture: depth.getDepthMap(),
|
|
75
|
+
encoding: options.encoding ?? detected,
|
|
76
|
+
packed: options.packed ?? depth.isPacked,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {texture: depth, encoding: options.encoding ?? 'linear', packed: options.packed ?? false};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Writes the four coefficients the soft-particle shaders decode a depth sample `d` with:
|
|
84
|
+
* view depth = |(x·d + y) / (z·d + w)|. Each encoding is a ratio of linear terms in `d` for
|
|
85
|
+
* every projection Babylon builds, so one formula covers them all.
|
|
86
|
+
*
|
|
87
|
+
* @param camera - The camera the particles are drawn for, normally the one the depth was drawn for.
|
|
88
|
+
* @param encoding - What the depth texture stores.
|
|
89
|
+
* @param out - Receives the coefficients.
|
|
90
|
+
*/
|
|
91
|
+
export function softDepthParams(camera: Camera, encoding: SoftDepthEncoding, out: Vector4): Vector4 {
|
|
92
|
+
if (encoding === 'viewZ') {
|
|
93
|
+
return out.set(1, 0, 0, 1);
|
|
94
|
+
}
|
|
95
|
+
const engine = camera.getEngine();
|
|
96
|
+
const reverse = engine.useReverseDepthBuffer;
|
|
97
|
+
const halfZ = engine.isNDCHalfZRange;
|
|
98
|
+
// A view-space point (0, 0, z) projects to clip z = z·m10 + m14, clip w = z·m11 + m15.
|
|
99
|
+
const m = camera.getProjectionMatrix().m;
|
|
100
|
+
const m10 = m[10];
|
|
101
|
+
const m11 = m[11];
|
|
102
|
+
const m14 = m[14];
|
|
103
|
+
const m15 = m[15];
|
|
104
|
+
if (encoding === 'linear') {
|
|
105
|
+
// Babylon's depth metric, (±clip z + depthValues.x) / depthValues.y, solved for z.
|
|
106
|
+
const ortho = camera.mode === Camera.ORTHOGRAPHIC_CAMERA;
|
|
107
|
+
let near: number;
|
|
108
|
+
let far: number;
|
|
109
|
+
if (ortho) {
|
|
110
|
+
near = !reverse && halfZ ? 0 : 1;
|
|
111
|
+
far = reverse && halfZ ? 0 : 1;
|
|
112
|
+
} else {
|
|
113
|
+
near = reverse && halfZ ? camera.minZ : halfZ ? 0 : camera.minZ;
|
|
114
|
+
far = reverse && halfZ ? 0 : camera.maxZ;
|
|
115
|
+
}
|
|
116
|
+
const sign = reverse ? -1 : 1;
|
|
117
|
+
const span = near + far;
|
|
118
|
+
return out.set((sign * span) / m10, (-sign * near - m14) / m10, 0, 1);
|
|
119
|
+
}
|
|
120
|
+
// The depth buffer value: NDC z = d on a half range, 2d - 1 on a full one; then
|
|
121
|
+
// z = (m14 - ndc·m15) / (ndc·m11 - m10).
|
|
122
|
+
const scale = halfZ ? 1 : 2;
|
|
123
|
+
const offset = halfZ ? 0 : -1;
|
|
124
|
+
return out.set(-scale * m15, m14 - offset * m15, scale * m11, offset * m11 - m10);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The view depth the shaders read off a depth sample, for the coefficients of
|
|
129
|
+
* {@link softDepthParams}. Nothing there (a `'viewZ'` sample of 0, or a sample at an infinite far
|
|
130
|
+
* plane) reads as infinitely far, so the particle does not fade.
|
|
131
|
+
*/
|
|
132
|
+
export function decodeSoftDepth(sample: number, params: Vector4): number {
|
|
133
|
+
const denominator = params.z * sample + params.w;
|
|
134
|
+
const depth = Math.abs(denominator) > 1e-8 ? Math.abs((params.x * sample + params.y) / denominator) : 0;
|
|
135
|
+
return depth > 0 ? depth : 1e30;
|
|
136
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {Behavior, FusionStep, planBehaviorFusion} from 'quarks.core';
|
|
2
|
+
|
|
3
|
+
/** Built-in behaviors that write a particle's size / colour, and those that touch neither. */
|
|
4
|
+
const SIZE_BEHAVIORS = new Set(['SizeOverLife', 'SizeBySpeed']);
|
|
5
|
+
const COLOR_BEHAVIORS = new Set(['ColorOverLife', 'ColorBySpeed']);
|
|
6
|
+
const INERT_FOR_SIZE_AND_COLOR = new Set([
|
|
7
|
+
'ApplyForce',
|
|
8
|
+
'Noise',
|
|
9
|
+
'TurbulenceField',
|
|
10
|
+
'GravityForce',
|
|
11
|
+
'RotationOverLife',
|
|
12
|
+
'Rotation3DOverLife',
|
|
13
|
+
'RotationBySpeed',
|
|
14
|
+
'SpeedOverLife',
|
|
15
|
+
'FrameOverLife',
|
|
16
|
+
'ForceOverLife',
|
|
17
|
+
'VelocityOverLife',
|
|
18
|
+
'InheritVelocity',
|
|
19
|
+
'OrbitOverLife',
|
|
20
|
+
'WidthOverLength',
|
|
21
|
+
'ChangeEmitDirection',
|
|
22
|
+
'EmitSubParticleSystem',
|
|
23
|
+
'LimitSpeedOverLife',
|
|
24
|
+
'ApplyCollision',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* How a {@link ParticleSystem}'s behaviors run each step: consecutive behaviors that can be compiled
|
|
29
|
+
* into one loop are merged, and the plan records whether any of them can change a particle's size
|
|
30
|
+
* or colour after birth.
|
|
31
|
+
*/
|
|
32
|
+
export class BehaviorPlan {
|
|
33
|
+
/** Behaviors the cached plan was built from, compared by identity. */
|
|
34
|
+
private plan?: Array<Behavior>;
|
|
35
|
+
private fusionSteps: Array<FusionStep> = [];
|
|
36
|
+
/**
|
|
37
|
+
* Whether any behavior in the plan can change a particle's size or colour
|
|
38
|
+
* after birth. Only then does a step have to snapshot them for the renderer.
|
|
39
|
+
*/
|
|
40
|
+
changesSize = true;
|
|
41
|
+
changesColor = true;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Execution plan for this system's behaviors, rebuilt when the list changes.
|
|
45
|
+
*
|
|
46
|
+
* Consecutive behaviors that can be compiled into one loop are merged, so
|
|
47
|
+
* the particles are walked once instead of once per behavior. The plan is
|
|
48
|
+
* keyed on the behavior instances, not their types: swapping one out has to
|
|
49
|
+
* change the generated code's indices.
|
|
50
|
+
*/
|
|
51
|
+
steps(behaviors: Array<Behavior>): Array<FusionStep> {
|
|
52
|
+
const cached = this.plan;
|
|
53
|
+
if (cached !== undefined && cached.length === behaviors.length) {
|
|
54
|
+
let same = true;
|
|
55
|
+
for (let i = 0; i < behaviors.length; i++) {
|
|
56
|
+
if (cached[i] !== behaviors[i]) {
|
|
57
|
+
same = false;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (same) {
|
|
62
|
+
return this.fusionSteps;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.plan = behaviors.slice();
|
|
66
|
+
this.fusionSteps = planBehaviorFusion(behaviors);
|
|
67
|
+
let changesSize = false;
|
|
68
|
+
let changesColor = false;
|
|
69
|
+
for (let i = 0; i < behaviors.length; i++) {
|
|
70
|
+
const type = behaviors[i].type;
|
|
71
|
+
if (SIZE_BEHAVIORS.has(type)) {
|
|
72
|
+
changesSize = true;
|
|
73
|
+
} else if (COLOR_BEHAVIORS.has(type)) {
|
|
74
|
+
changesColor = true;
|
|
75
|
+
} else if (!INERT_FOR_SIZE_AND_COLOR.has(type)) {
|
|
76
|
+
// Unknown to this list, so it may write anything.
|
|
77
|
+
changesSize = true;
|
|
78
|
+
changesColor = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
this.changesSize = changesSize;
|
|
82
|
+
this.changesColor = changesColor;
|
|
83
|
+
return this.fusionSteps;
|
|
84
|
+
}
|
|
85
|
+
}
|