babylon.quarks 0.23.0 → 0.24.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/dist/babylon.quarks.cjs +1 -1
  3. package/dist/babylon.quarks.cjs.map +1 -1
  4. package/dist/babylon.quarks.esm.js +1 -1
  5. package/dist/babylon.quarks.esm.js.map +1 -1
  6. package/dist/babylon.quarks.umd.min.js +1 -1
  7. package/dist/babylon.quarks.umd.min.js.map +1 -1
  8. package/dist/types/index.d.ts +529 -20
  9. package/package.json +2 -2
  10. package/src/BatchedRenderer.ts +146 -2
  11. package/src/EffectPool.ts +165 -0
  12. package/src/MeshSurfaceEmitter.ts +4 -4
  13. package/src/ParticleEmitter.ts +2 -1
  14. package/src/ParticleSystem.ts +592 -15
  15. package/src/QuarksUtil.ts +55 -7
  16. package/src/SpriteBatch.ts +127 -3
  17. package/src/TrailBatch.ts +49 -12
  18. package/src/VFXBatch.ts +154 -3
  19. package/src/index.ts +4 -1
  20. package/src/instanceSort.ts +121 -0
  21. package/src/shaders/chunks/fog.ts +127 -0
  22. package/src/shaders/local_particle_physics_vert.glsl.ts +4 -0
  23. package/src/shaders/local_particle_physics_vert.wgsl.ts +4 -0
  24. package/src/shaders/particle_frag.glsl.ts +4 -0
  25. package/src/shaders/particle_frag.wgsl.ts +4 -0
  26. package/src/shaders/particle_physics_frag.glsl.ts +4 -0
  27. package/src/shaders/particle_physics_frag.wgsl.ts +4 -0
  28. package/src/shaders/particle_vert.glsl.ts +23 -4
  29. package/src/shaders/particle_vert.wgsl.ts +23 -4
  30. package/src/shaders/stretched_bb_particle_vert.glsl.ts +17 -0
  31. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +18 -1
  32. package/src/shaders/trail_frag.glsl.ts +4 -0
  33. package/src/shaders/trail_frag.wgsl.ts +4 -0
  34. package/src/shaders/trail_vert.glsl.ts +4 -0
  35. package/src/shaders/trail_vert.wgsl.ts +4 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Sorting a batch's instances by a per-instance key, fast enough to run every
3
+ * frame over tens of thousands of particles: a three-pass radix sort on 32-bit
4
+ * keys (no comparator calls, linear time) and a gather of each attribute buffer
5
+ * in the resulting order.
6
+ */
7
+
8
+ const RADIX_BITS = 11;
9
+ const RADIX_SIZE = 1 << RADIX_BITS;
10
+ const RADIX_MASK = RADIX_SIZE - 1;
11
+
12
+ /** Scratch space for {@link sortByKey}, sized for a given instance count. */
13
+ export class SortScratch {
14
+ keys: Uint32Array;
15
+ orderA: Uint32Array;
16
+ orderB: Uint32Array;
17
+ readonly counts = new Uint32Array(RADIX_SIZE);
18
+ private readonly floatView = new Float32Array(1);
19
+ private readonly uintView = new Uint32Array(this.floatView.buffer);
20
+
21
+ constructor(capacity: number) {
22
+ this.keys = new Uint32Array(capacity);
23
+ this.orderA = new Uint32Array(capacity);
24
+ this.orderB = new Uint32Array(capacity);
25
+ }
26
+
27
+ ensure(capacity: number): void {
28
+ if (this.keys.length < capacity) {
29
+ this.keys = new Uint32Array(capacity);
30
+ this.orderA = new Uint32Array(capacity);
31
+ this.orderB = new Uint32Array(capacity);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * The bits of a non-negative float, which order the same way the float
37
+ * does, so they can be radix sorted. `descending` flips the order.
38
+ */
39
+ floatKey(value: number, descending: boolean): number {
40
+ this.floatView[0] = value;
41
+ const bits = this.uintView[0];
42
+ return descending ? ~bits >>> 0 : bits;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Fills `order` with the instance indices `[0, count)` sorted by ascending
48
+ * `scratch.keys`. Returns the array holding the result, one of the two order
49
+ * buffers of the scratch.
50
+ */
51
+ export function sortByKey(scratch: SortScratch, count: number): Uint32Array {
52
+ const keys = scratch.keys;
53
+ const counts = scratch.counts;
54
+ let from = scratch.orderA;
55
+ let to = scratch.orderB;
56
+ for (let i = 0; i < count; i++) {
57
+ from[i] = i;
58
+ }
59
+ for (let shift = 0; shift < 32; shift += RADIX_BITS) {
60
+ counts.fill(0);
61
+ for (let i = 0; i < count; i++) {
62
+ counts[(keys[from[i]] >>> shift) & RADIX_MASK]++;
63
+ }
64
+ // A pass whose keys all share one digit changes nothing; skip the scatter.
65
+ if (counts[(keys[from[0]] >>> shift) & RADIX_MASK] === count) {
66
+ continue;
67
+ }
68
+ let sum = 0;
69
+ for (let b = 0; b < RADIX_SIZE; b++) {
70
+ const c = counts[b];
71
+ counts[b] = sum;
72
+ sum += c;
73
+ }
74
+ for (let i = 0; i < count; i++) {
75
+ const index = from[i];
76
+ to[counts[(keys[index] >>> shift) & RADIX_MASK]++] = index;
77
+ }
78
+ const swap = from;
79
+ from = to;
80
+ to = swap;
81
+ }
82
+ return from;
83
+ }
84
+
85
+ /** Copies `count` instances of `stride` floats from `source` into `target` in `order`. */
86
+ export function gather(
87
+ source: Float32Array,
88
+ target: Float32Array,
89
+ order: Uint32Array,
90
+ count: number,
91
+ stride: number
92
+ ): void {
93
+ if (stride === 1) {
94
+ for (let i = 0; i < count; i++) {
95
+ target[i] = source[order[i]];
96
+ }
97
+ } else if (stride === 3) {
98
+ for (let i = 0, o = 0; i < count; i++, o += 3) {
99
+ const s = order[i] * 3;
100
+ target[o] = source[s];
101
+ target[o + 1] = source[s + 1];
102
+ target[o + 2] = source[s + 2];
103
+ }
104
+ } else if (stride === 4) {
105
+ for (let i = 0, o = 0; i < count; i++, o += 4) {
106
+ const s = order[i] * 4;
107
+ target[o] = source[s];
108
+ target[o + 1] = source[s + 1];
109
+ target[o + 2] = source[s + 2];
110
+ target[o + 3] = source[s + 3];
111
+ }
112
+ } else {
113
+ for (let i = 0; i < count; i++) {
114
+ const s = order[i] * stride;
115
+ const o = i * stride;
116
+ for (let k = 0; k < stride; k++) {
117
+ target[o + k] = source[s + k];
118
+ }
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Scene fog for the particle shaders, in both shader languages.
3
+ *
4
+ * Babylon's own fog includes expect variables the particle shaders do not have
5
+ * (`worldPos`, a `color` the fragment writes), so the few lines involved live
6
+ * here instead and are spliced into each shader. The maths is Babylon's
7
+ * (`fogFragmentDeclaration`): the fog mode, start, end and density arrive in
8
+ * `vFogInfos`, the colour in `vFogColor`, and the distance is the view-space
9
+ * position's length. Mode 0 is "no fog", so a scene that turns its fog off
10
+ * needs no recompile — the uniform does it.
11
+ *
12
+ * Additive particles cannot fade toward a fog colour (that would add fog to
13
+ * the whole screen), so under `FOG_ADDITIVE` they fade toward black instead,
14
+ * which is what Unity does for additive materials.
15
+ */
16
+
17
+ /** Vertex-side declaration, GLSL. */
18
+ export const fogVertexDeclarationGLSL = /* glsl */ `
19
+ #ifdef FOG
20
+ varying vec3 vFogDistance;
21
+ #endif
22
+ `;
23
+
24
+ /** Writes the view-space position the fragment measures its distance from, GLSL. */
25
+ export function fogVertexGLSL(viewPosition: string): string {
26
+ return /* glsl */ `
27
+ #ifdef FOG
28
+ vFogDistance = ${viewPosition};
29
+ #endif
30
+ `;
31
+ }
32
+
33
+ /** Fragment-side declaration and fog factor, GLSL. */
34
+ export const fogFragmentDeclarationGLSL = /* glsl */ `
35
+ #ifdef FOG
36
+ uniform vec4 vFogInfos;
37
+ uniform vec3 vFogColor;
38
+ varying vec3 vFogDistance;
39
+
40
+ float quarksFogFactor() {
41
+ float fogCoeff = 1.0;
42
+ float fogStart = vFogInfos.y;
43
+ float fogEnd = vFogInfos.z;
44
+ float fogDensity = vFogInfos.w;
45
+ float fogDistance = length(vFogDistance);
46
+ if (vFogInfos.x == 3.0) {
47
+ fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
48
+ } else if (vFogInfos.x == 1.0) {
49
+ fogCoeff = exp(-fogDistance * fogDensity);
50
+ } else if (vFogInfos.x == 2.0) {
51
+ fogCoeff = exp(-fogDistance * fogDistance * fogDensity * fogDensity);
52
+ }
53
+ return clamp(fogCoeff, 0.0, 1.0);
54
+ }
55
+ #endif
56
+ `;
57
+
58
+ /** Applies the fog to an rgb lvalue (`color` or `baseColor.rgb`), GLSL. */
59
+ export function fogFragmentGLSL(rgb: string): string {
60
+ return /* glsl */ `
61
+ #ifdef FOG
62
+ {
63
+ float fogFactor = quarksFogFactor();
64
+ #ifdef FOG_ADDITIVE
65
+ ${rgb} *= fogFactor;
66
+ #else
67
+ ${rgb} = mix(vFogColor, ${rgb}, fogFactor);
68
+ #endif
69
+ }
70
+ #endif
71
+ `;
72
+ }
73
+
74
+ /** Vertex-side declaration, WGSL. */
75
+ export const fogVertexDeclarationWGSL = /* wgsl */ `
76
+ #ifdef FOG
77
+ varying vFogDistance: vec3f;
78
+ #endif
79
+ `;
80
+
81
+ /** Writes the view-space position the fragment measures its distance from, WGSL. */
82
+ export function fogVertexWGSL(viewPosition: string): string {
83
+ return /* wgsl */ `
84
+ #ifdef FOG
85
+ vertexOutputs.vFogDistance = ${viewPosition};
86
+ #endif
87
+ `;
88
+ }
89
+
90
+ /** Fragment-side declaration and fog function, WGSL. */
91
+ export const fogFragmentDeclarationWGSL = /* wgsl */ `
92
+ #ifdef FOG
93
+ uniform vFogInfos: vec4f;
94
+ uniform vFogColor: vec3f;
95
+ varying vFogDistance: vec3f;
96
+
97
+ fn quarksFog(rgb: vec3f, fogDistanceVector: vec3f) -> vec3f {
98
+ var fogCoeff = 1.0;
99
+ let fogStart = uniforms.vFogInfos.y;
100
+ let fogEnd = uniforms.vFogInfos.z;
101
+ let fogDensity = uniforms.vFogInfos.w;
102
+ let fogDistance = length(fogDistanceVector);
103
+ if (uniforms.vFogInfos.x == 3.0) {
104
+ fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
105
+ } else if (uniforms.vFogInfos.x == 1.0) {
106
+ fogCoeff = exp(-fogDistance * fogDensity);
107
+ } else if (uniforms.vFogInfos.x == 2.0) {
108
+ fogCoeff = exp(-fogDistance * fogDistance * fogDensity * fogDensity);
109
+ }
110
+ let fogFactor = clamp(fogCoeff, 0.0, 1.0);
111
+ #ifdef FOG_ADDITIVE
112
+ return rgb * fogFactor;
113
+ #else
114
+ return mix(uniforms.vFogColor, rgb, fogFactor);
115
+ #endif
116
+ }
117
+ #endif
118
+ `;
119
+
120
+ /** Applies the fog to a `var` vec4 colour, WGSL. */
121
+ export function fogFragmentWGSL(color: string): string {
122
+ return /* wgsl */ `
123
+ #ifdef FOG
124
+ ${color} = vec4f(quarksFog(${color}.rgb, fragmentInputs.vFogDistance), ${color}.a);
125
+ #endif
126
+ `;
127
+ }
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  attribute vec3 position;
3
5
  attribute vec2 uv;
@@ -39,6 +41,7 @@ vec3 applyQuaternion(vec3 v, vec4 q) {
39
41
  return v + qW * t + cross(qVec, t);
40
42
  }
41
43
 
44
+ ${fogVertexDeclarationGLSL}
42
45
  void main() {
43
46
  vec3 scaledPosition = position * size;
44
47
  vec3 rotatedPosition = applyQuaternion(scaledPosition, rotation);
@@ -48,6 +51,7 @@ void main() {
48
51
  vec4 worldPos = world * vec4(localPos, 1.0);
49
52
  vec4 viewPos = view * worldPos;
50
53
  gl_Position = projection * viewPos;
54
+ ${fogVertexGLSL('viewPos.xyz')}
51
55
 
52
56
  vWorldPos = worldPos.xyz;
53
57
  vNormal = normalize((world * vec4(rotatedNormal, 0.0)).xyz);
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationWGSL, fogVertexWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  attribute position: vec3f;
3
5
  attribute uv: vec2f;
@@ -39,6 +41,7 @@ fn applyQuaternion(v: vec3f, q: vec4f) -> vec3f {
39
41
  return v + qW * t + cross(qVec, t);
40
42
  }
41
43
 
44
+ ${fogVertexDeclarationWGSL}
42
45
  @vertex
43
46
  fn main(input: VertexInputs) -> FragmentInputs {
44
47
  let scaledPosition = vertexInputs.position * vertexInputs.size;
@@ -50,6 +53,7 @@ fn main(input: VertexInputs) -> FragmentInputs {
50
53
  let viewPos = uniforms.view * worldPos;
51
54
  let clipPosition = uniforms.projection * viewPos;
52
55
  vertexOutputs.position = clipPosition;
56
+ ${fogVertexWGSL('viewPos.xyz')}
53
57
 
54
58
  vertexOutputs.vWorldPos = worldPos.xyz;
55
59
  vertexOutputs.vNormal = normalize((uniforms.world * vec4f(rotatedNormal, 0.0)).xyz);
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  varying vec2 vUV;
3
5
  varying vec4 vColor;
@@ -23,6 +25,7 @@ varying float linearDepth;
23
25
  uniform float alphaTest;
24
26
  #endif
25
27
 
28
+ ${fogFragmentDeclarationGLSL}
26
29
  void main() {
27
30
  vec4 baseColor = vColor;
28
31
 
@@ -45,6 +48,7 @@ void main() {
45
48
  if (baseColor.a < 0.01) discard;
46
49
  #endif
47
50
 
51
+ ${fogFragmentGLSL('baseColor.rgb')}
48
52
  gl_FragColor = baseColor;
49
53
 
50
54
  #ifdef SOFT_PARTICLES
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  varying vUV: vec2f;
3
5
  varying vColor: vec4f;
@@ -25,6 +27,7 @@ varying linearDepth: f32;
25
27
  uniform alphaTest: f32;
26
28
  #endif
27
29
 
30
+ ${fogFragmentDeclarationWGSL}
28
31
  @fragment
29
32
  fn main(input: FragmentInputs) -> FragmentOutputs {
30
33
  var baseColor = fragmentInputs.vColor;
@@ -59,6 +62,7 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
59
62
  baseColor *= fade;
60
63
  #endif
61
64
 
65
+ ${fogFragmentWGSL('baseColor')}
62
66
  fragmentOutputs.color = baseColor;
63
67
  }
64
68
  `;
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  varying vec2 vUV;
3
5
  varying vec4 vColor;
@@ -85,6 +87,7 @@ varying float linearDepth;
85
87
  uniform float alphaTest;
86
88
  #endif
87
89
 
90
+ ${fogFragmentDeclarationGLSL}
88
91
  void main() {
89
92
  vec4 baseColor = vColor;
90
93
 
@@ -131,6 +134,7 @@ void main() {
131
134
  #endif
132
135
  #endif
133
136
 
137
+ ${fogFragmentGLSL('color')}
134
138
  gl_FragColor = vec4(color, baseColor.a);
135
139
 
136
140
  #ifdef SOFT_PARTICLES
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  varying vUV: vec2f;
3
5
  varying vColor: vec4f;
@@ -85,6 +87,7 @@ varying linearDepth: f32;
85
87
  uniform alphaTest: f32;
86
88
  #endif
87
89
 
90
+ ${fogFragmentDeclarationWGSL}
88
91
  @fragment
89
92
  fn main(input: FragmentInputs) -> FragmentOutputs {
90
93
  var baseColor = fragmentInputs.vColor;
@@ -145,6 +148,7 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
145
148
  finalColor *= fade;
146
149
  #endif
147
150
 
151
+ ${fogFragmentWGSL('finalColor')}
148
152
  fragmentOutputs.color = finalColor;
149
153
  }
150
154
  `;
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  // Per-vertex attributes
3
5
  attribute vec3 position;
@@ -38,15 +40,31 @@ varying vec4 projPosition;
38
40
  varying float linearDepth;
39
41
  #endif
40
42
 
43
+ #ifdef SIZE_LIMITS
44
+ // Smallest and largest size a particle may have on screen, as fractions of the viewport height.
45
+ uniform vec2 sizeLimits;
46
+ #endif
47
+ ${fogVertexDeclarationGLSL}
41
48
  void main() {
42
49
  float c = cos(rotation);
43
50
  float s = sin(rotation);
44
51
 
52
+ #ifdef SIZE_LIMITS
53
+ // The particle's size on screen as a fraction of the viewport height, held
54
+ // between the limits; a perspective projection divides by the view depth.
55
+ vec4 limitCenter = view * world * vec4(offset, 1.0);
56
+ float limitDepth = abs(projection[2][3]) > 0.0 ? abs(limitCenter.z) : 1.0;
57
+ float screenSize = max(abs(size.x), abs(size.y)) * projection[1][1] / (2.0 * limitDepth);
58
+ float sizeScale = screenSize > 0.0 ? clamp(screenSize, sizeLimits.x, sizeLimits.y) / screenSize : 1.0;
59
+ vec3 limitedSize = size * sizeScale;
60
+ #else
61
+ vec3 limitedSize = size;
62
+ #endif
45
63
  // A negative size mirrors the quad, which is how flipped particles arrive.
46
- vec2 alignedPosition = position.xy * size.xy;
64
+ vec2 alignedPosition = position.xy * limitedSize.xy;
47
65
  #ifdef PIVOT
48
66
  // The pivot turns with the particle but is not mirrored with it.
49
- alignedPosition += pivot.xy * abs(size.xy);
67
+ alignedPosition += pivot.xy * abs(limitedSize.xy);
50
68
  #endif
51
69
  vec2 rotatedPosition;
52
70
  rotatedPosition.x = c * alignedPosition.x - s * alignedPosition.y;
@@ -57,7 +75,7 @@ void main() {
57
75
  mvPosition.x += rotatedPosition.x;
58
76
  mvPosition.z -= rotatedPosition.y;
59
77
  #ifdef PIVOT
60
- mvPosition.y += pivot.z * abs(size.x);
78
+ mvPosition.y += pivot.z * abs(limitedSize.x);
61
79
  #endif
62
80
  mvPosition = view * mvPosition;
63
81
  #elif defined(VERTICAL)
@@ -73,11 +91,12 @@ void main() {
73
91
  #ifndef HORIZONTAL
74
92
  // Toward the camera along its view axis, whichever way the view space
75
93
  // faces: anything in front of the camera has the sign of the far side.
76
- mvPosition.z -= sign(mvPosition.z) * pivot.z * abs(size.x);
94
+ mvPosition.z -= sign(mvPosition.z) * pivot.z * abs(limitedSize.x);
77
95
  #endif
78
96
  #endif
79
97
 
80
98
  gl_Position = projection * mvPosition;
99
+ ${fogVertexGLSL('mvPosition.xyz')}
81
100
  #ifdef SOFT_PARTICLES
82
101
  projPosition = gl_Position;
83
102
  linearDepth = -mvPosition.z;
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationWGSL, fogVertexWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  // Per-vertex attributes
3
5
  attribute position: vec3f;
@@ -37,16 +39,32 @@ varying projPosition: vec4f;
37
39
  varying linearDepth: f32;
38
40
  #endif
39
41
 
42
+ #ifdef SIZE_LIMITS
43
+ // Smallest and largest size a particle may have on screen, as fractions of the viewport height.
44
+ uniform sizeLimits: vec2f;
45
+ #endif
46
+ ${fogVertexDeclarationWGSL}
40
47
  @vertex
41
48
  fn main(input: VertexInputs) -> FragmentInputs {
42
49
  let c = cos(vertexInputs.rotation);
43
50
  let s = sin(vertexInputs.rotation);
44
51
 
52
+ #ifdef SIZE_LIMITS
53
+ // The particle's size on screen as a fraction of the viewport height, held
54
+ // between the limits; a perspective projection divides by the view depth.
55
+ let limitCenter = uniforms.view * uniforms.world * vec4f(vertexInputs.offset, 1.0);
56
+ let limitDepth = select(1.0, abs(limitCenter.z), abs(uniforms.projection[2][3]) > 0.0);
57
+ let screenSize = max(abs(vertexInputs.size.x), abs(vertexInputs.size.y)) * uniforms.projection[1][1] / (2.0 * limitDepth);
58
+ let sizeScale = select(1.0, clamp(screenSize, uniforms.sizeLimits.x, uniforms.sizeLimits.y) / screenSize, screenSize > 0.0);
59
+ let limitedSize = vertexInputs.size * sizeScale;
60
+ #else
61
+ let limitedSize = vertexInputs.size;
62
+ #endif
45
63
  // A negative size mirrors the quad, which is how flipped particles arrive.
46
- var alignedPosition = vertexInputs.position.xy * vertexInputs.size.xy;
64
+ var alignedPosition = vertexInputs.position.xy * limitedSize.xy;
47
65
  #ifdef PIVOT
48
66
  // The pivot turns with the particle but is not mirrored with it.
49
- alignedPosition += uniforms.pivot.xy * abs(vertexInputs.size.xy);
67
+ alignedPosition += uniforms.pivot.xy * abs(limitedSize.xy);
50
68
  #endif
51
69
  let rotatedPosition = vec2f(
52
70
  c * alignedPosition.x - s * alignedPosition.y,
@@ -57,7 +75,7 @@ fn main(input: VertexInputs) -> FragmentInputs {
57
75
  mvPosition.x += rotatedPosition.x;
58
76
  mvPosition.z -= rotatedPosition.y;
59
77
  #ifdef PIVOT
60
- mvPosition.y += uniforms.pivot.z * abs(vertexInputs.size.x);
78
+ mvPosition.y += uniforms.pivot.z * abs(limitedSize.x);
61
79
  #endif
62
80
  mvPosition = uniforms.view * mvPosition;
63
81
  #elif defined(VERTICAL)
@@ -74,12 +92,13 @@ fn main(input: VertexInputs) -> FragmentInputs {
74
92
  #ifndef HORIZONTAL
75
93
  // Toward the camera along its view axis, whichever way the view space
76
94
  // faces: anything in front of the camera has the sign of the far side.
77
- mvPosition.z -= sign(mvPosition.z) * uniforms.pivot.z * abs(vertexInputs.size.x);
95
+ mvPosition.z -= sign(mvPosition.z) * uniforms.pivot.z * abs(limitedSize.x);
78
96
  #endif
79
97
  #endif
80
98
 
81
99
  let clipPosition = uniforms.projection * mvPosition;
82
100
  vertexOutputs.position = clipPosition;
101
+ ${fogVertexWGSL('mvPosition.xyz')}
83
102
  #ifdef SOFT_PARTICLES
84
103
  vertexOutputs.projPosition = clipPosition;
85
104
  vertexOutputs.linearDepth = -mvPosition.z;
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  // Per-vertex attributes
3
5
  attribute vec3 position;
@@ -39,12 +41,26 @@ varying vec4 projPosition;
39
41
  varying float linearDepth;
40
42
  #endif
41
43
 
44
+ #ifdef SIZE_LIMITS
45
+ // Smallest and largest size a particle may have on screen, as fractions of the viewport height.
46
+ uniform vec2 sizeLimits;
47
+ #endif
48
+ ${fogVertexDeclarationGLSL}
42
49
  void main() {
43
50
  float lengthFactor = velocity.w;
44
51
  // A flipped particle arrives with a negative size: the streak keeps its
45
52
  // extent and mirrors its texture instead (see quadUV below).
46
53
  float avgSize = (abs(size.x) + abs(size.y)) * 0.5;
47
54
  vec4 mvPosition = view * world * vec4(offset, 1.0);
55
+ #ifdef SIZE_LIMITS
56
+ // The streak's width on screen as a fraction of the viewport height, held
57
+ // between the limits; a perspective projection divides by the view depth.
58
+ float limitDepth = abs(projection[2][3]) > 0.0 ? abs(mvPosition.z) : 1.0;
59
+ float screenSize = avgSize * projection[1][1] / (2.0 * limitDepth);
60
+ if (screenSize > 0.0) {
61
+ avgSize *= clamp(screenSize, sizeLimits.x, sizeLimits.y) / screenSize;
62
+ }
63
+ #endif
48
64
  vec3 viewVelocity = mat3(view * world) * velocity.xyz;
49
65
  float vlength = length(viewVelocity);
50
66
  // Stretch direction is the velocity direction; only a genuinely motionless particle has none.
@@ -61,6 +77,7 @@ void main() {
61
77
  mvPosition.xyz += (pivot.y * side - pivot.x * vdir) * avgSize;
62
78
  #endif
63
79
  gl_Position = projection * mvPosition;
80
+ ${fogVertexGLSL('mvPosition.xyz')}
64
81
  vec2 quadUV = vec2(size.x < 0.0 ? 1.0 - uv.x : uv.x, size.y < 0.0 ? 1.0 - uv.y : uv.y);
65
82
  #ifdef SOFT_PARTICLES
66
83
  projPosition = gl_Position;
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationWGSL, fogVertexWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  // Per-vertex attributes
3
5
  attribute position: vec3f;
@@ -39,13 +41,27 @@ varying projPosition: vec4f;
39
41
  varying linearDepth: f32;
40
42
  #endif
41
43
 
44
+ #ifdef SIZE_LIMITS
45
+ // Smallest and largest size a particle may have on screen, as fractions of the viewport height.
46
+ uniform sizeLimits: vec2f;
47
+ #endif
48
+ ${fogVertexDeclarationWGSL}
42
49
  @vertex
43
50
  fn main(input: VertexInputs) -> FragmentInputs {
44
51
  let lengthFactor = vertexInputs.velocity.w;
45
52
  // A flipped particle arrives with a negative size: the streak keeps its
46
53
  // extent and mirrors its texture instead (see quadUV below).
47
- let avgSize = (abs(vertexInputs.size.x) + abs(vertexInputs.size.y)) * 0.5;
54
+ var avgSize = (abs(vertexInputs.size.x) + abs(vertexInputs.size.y)) * 0.5;
48
55
  var mvPosition = uniforms.view * uniforms.world * vec4f(vertexInputs.offset, 1.0);
56
+ #ifdef SIZE_LIMITS
57
+ // The streak's width on screen as a fraction of the viewport height, held
58
+ // between the limits; a perspective projection divides by the view depth.
59
+ let limitDepth = select(1.0, abs(mvPosition.z), abs(uniforms.projection[2][3]) > 0.0);
60
+ let screenSize = avgSize * uniforms.projection[1][1] / (2.0 * limitDepth);
61
+ if (screenSize > 0.0) {
62
+ avgSize *= clamp(screenSize, uniforms.sizeLimits.x, uniforms.sizeLimits.y) / screenSize;
63
+ }
64
+ #endif
49
65
  let viewWorld = uniforms.view * uniforms.world;
50
66
  let viewWorld3 = mat3x3f(viewWorld[0].xyz, viewWorld[1].xyz, viewWorld[2].xyz);
51
67
  let viewVelocity = viewWorld3 * vertexInputs.velocity.xyz;
@@ -73,6 +89,7 @@ fn main(input: VertexInputs) -> FragmentInputs {
73
89
 
74
90
  let clipPosition = uniforms.projection * mvPosition;
75
91
  vertexOutputs.position = clipPosition;
92
+ ${fogVertexWGSL('mvPosition.xyz')}
76
93
  let quadUV = select(vertexInputs.uv, 1.0 - vertexInputs.uv, vertexInputs.size.xy < vec2f(0.0));
77
94
  #ifdef SOFT_PARTICLES
78
95
  vertexOutputs.projPosition = clipPosition;
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  varying vec2 vUV;
3
5
  varying vec4 vColor;
@@ -6,6 +8,7 @@ varying vec4 vColor;
6
8
  uniform sampler2D map;
7
9
  #endif
8
10
 
11
+ ${fogFragmentDeclarationGLSL}
9
12
  void main() {
10
13
  vec4 baseColor = vColor;
11
14
 
@@ -16,6 +19,7 @@ void main() {
16
19
 
17
20
  if (baseColor.a < 0.01) discard;
18
21
 
22
+ ${fogFragmentGLSL('baseColor.rgb')}
19
23
  gl_FragColor = baseColor;
20
24
  }
21
25
  `;
@@ -1,3 +1,5 @@
1
+ import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  varying vUV: vec2f;
3
5
  varying vColor: vec4f;
@@ -7,6 +9,7 @@ var mapSampler: sampler;
7
9
  var map: texture_2d<f32>;
8
10
  #endif
9
11
 
12
+ ${fogFragmentDeclarationWGSL}
10
13
  @fragment
11
14
  fn main(input: FragmentInputs) -> FragmentOutputs {
12
15
  var baseColor = fragmentInputs.vColor;
@@ -18,6 +21,7 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
18
21
 
19
22
  if (baseColor.a < 0.01) { discard; }
20
23
 
24
+ ${fogFragmentWGSL('baseColor')}
21
25
  fragmentOutputs.color = baseColor;
22
26
  }
23
27
  `;
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
2
+
1
3
  export default /* glsl */ `
2
4
  attribute vec3 position;
3
5
  attribute vec3 previous;
@@ -22,6 +24,7 @@ vec2 fix(vec4 i, float aspect) {
22
24
  return res;
23
25
  }
24
26
 
27
+ ${fogVertexDeclarationGLSL}
25
28
  void main() {
26
29
  float aspect = resolution.x / resolution.y;
27
30
  vec4 finalPosition = projection * view * vec4(position, 1.0);
@@ -52,6 +55,7 @@ void main() {
52
55
  }
53
56
  finalPosition.xy += normal.xy * side;
54
57
  gl_Position = finalPosition;
58
+ ${fogVertexGLSL('(view * vec4(position, 1.0)).xyz')}
55
59
  vUV = uv;
56
60
  vColor = color;
57
61
  }
@@ -1,3 +1,5 @@
1
+ import {fogVertexDeclarationWGSL, fogVertexWGSL} from './chunks/fog';
2
+
1
3
  export default /* wgsl */ `
2
4
  attribute position: vec3f;
3
5
  attribute previous: vec3f;
@@ -22,6 +24,7 @@ fn fix(i: vec4f, aspect: f32) -> vec2f {
22
24
  return res;
23
25
  }
24
26
 
27
+ ${fogVertexDeclarationWGSL}
25
28
  @vertex
26
29
  fn main(input: VertexInputs) -> FragmentInputs {
27
30
  let aspect = uniforms.resolution.x / uniforms.resolution.y;
@@ -57,6 +60,7 @@ fn main(input: VertexInputs) -> FragmentInputs {
57
60
  finalPosition.y += normal.y * vertexInputs.side;
58
61
 
59
62
  vertexOutputs.position = finalPosition;
63
+ ${fogVertexWGSL('(uniforms.view * vec4f(vertexInputs.position, 1.0)).xyz')}
60
64
  vertexOutputs.vUV = vertexInputs.uv;
61
65
  vertexOutputs.vColor = vertexInputs.color;
62
66
  }