babylon.quarks 0.20.0 → 0.23.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 +105 -0
- package/README.md +2 -0
- 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.ts +523 -43
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +59 -2
- package/src/ParticleSystem.ts +388 -59
- package/src/QuarksLoader.ts +381 -56
- package/src/SpriteBatch.ts +388 -111
- package/src/TrailBatch.ts +79 -63
- package/src/VFXBatch.ts +101 -0
- package/src/index.ts +9 -3
- package/src/shaders/particle_physics_frag.glsl.ts +26 -1
- package/src/shaders/particle_physics_frag.wgsl.ts +26 -1
package/src/TrailBatch.ts
CHANGED
|
@@ -56,6 +56,12 @@ export class TrailBatch extends VFXBatch {
|
|
|
56
56
|
this.widthBuffer = new Float32Array(this.maxParticles * 2);
|
|
57
57
|
this.colorBuffer = new Float32Array(this.maxParticles * 8);
|
|
58
58
|
this.indexBuffer = new Uint32Array(this.maxParticles * 6);
|
|
59
|
+
// Vertices come in pairs, one per side of the ribbon, so the side never
|
|
60
|
+
// changes: written once here and never uploaded again.
|
|
61
|
+
for (let i = 0; i < this.sideBuffer.length; i += 2) {
|
|
62
|
+
this.sideBuffer[i] = 1;
|
|
63
|
+
this.sideBuffer[i + 1] = -1;
|
|
64
|
+
}
|
|
59
65
|
|
|
60
66
|
// Initialize with full-capacity buffers (updatable) and a dummy triangle
|
|
61
67
|
this.positionBuffer[0] = 0;
|
|
@@ -216,10 +222,21 @@ export class TrailBatch extends VFXBatch {
|
|
|
216
222
|
private vector3_ = new Vector3();
|
|
217
223
|
private quaternion_ = new Quaternion();
|
|
218
224
|
|
|
219
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Brings the emitter's world matrix up to date when its transform changed.
|
|
227
|
+
*
|
|
228
|
+
* Babylon only recomputes a world matrix once per render id, so a frame that
|
|
229
|
+
* moves the emitter after that (or a step driven without rendering) would
|
|
230
|
+
* draw the ribbon against the old one. Recomputing unconditionally every
|
|
231
|
+
* frame costs a matrix compose per system; checking the transform cache
|
|
232
|
+
* first makes a still emitter free.
|
|
233
|
+
*/
|
|
220
234
|
private static ensureWorldMatrix(system: IParticleSystem): void {
|
|
221
|
-
const emitter = system.emitter as {
|
|
222
|
-
|
|
235
|
+
const emitter = system.emitter as {
|
|
236
|
+
computeWorldMatrix?: (force: boolean) => void;
|
|
237
|
+
isSynchronized?: () => boolean;
|
|
238
|
+
};
|
|
239
|
+
if (emitter.computeWorldMatrix && (!emitter.isSynchronized || !emitter.isSynchronized())) {
|
|
223
240
|
emitter.computeWorldMatrix(true);
|
|
224
241
|
}
|
|
225
242
|
}
|
|
@@ -327,31 +344,46 @@ export class TrailBatch extends VFXBatch {
|
|
|
327
344
|
1 / (stepFraction !== 0 ? particleHistoryLength : Math.max(1, particleHistoryLength - 1));
|
|
328
345
|
|
|
329
346
|
// Walk the ring buffer oldest-first with hand-rolled wrapping so the
|
|
330
|
-
// inner loop stays free of modulo operations.
|
|
347
|
+
// inner loop stays free of modulo operations. Each sample is brought
|
|
348
|
+
// into world space once: the point written as `next` on one
|
|
349
|
+
// iteration is `current` on the following one and `previous` on
|
|
350
|
+
// the one after that.
|
|
331
351
|
let currentSlot = particle.getHistoryIndex(0);
|
|
332
|
-
let
|
|
352
|
+
let currentX = historyPositions[currentSlot * 3];
|
|
353
|
+
let currentY = historyPositions[currentSlot * 3 + 1];
|
|
354
|
+
let currentZ = historyPositions[currentSlot * 3 + 2];
|
|
355
|
+
if (!samplesInWorld) {
|
|
356
|
+
const w = 1 / (m03 * currentX + m13 * currentY + m23 * currentZ + m33);
|
|
357
|
+
const tx = (m00 * currentX + m10 * currentY + m20 * currentZ + m30) * w;
|
|
358
|
+
const ty = (m01 * currentX + m11 * currentY + m21 * currentZ + m31) * w;
|
|
359
|
+
currentZ = (m02 * currentX + m12 * currentY + m22 * currentZ + m32) * w;
|
|
360
|
+
currentX = tx;
|
|
361
|
+
currentY = ty;
|
|
362
|
+
}
|
|
363
|
+
let previousX = currentX;
|
|
364
|
+
let previousY = currentY;
|
|
365
|
+
let previousZ = currentZ;
|
|
333
366
|
|
|
334
367
|
for (let i = 0; i < particleHistoryLength; i++, index += 2) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
let nextZ = historyPositions[nextPosIndex + 2];
|
|
368
|
+
let nextX = currentX;
|
|
369
|
+
let nextY = currentY;
|
|
370
|
+
let nextZ = currentZ;
|
|
371
|
+
let nextSlot = currentSlot;
|
|
372
|
+
if (i + 1 < particleHistoryLength) {
|
|
373
|
+
nextSlot = currentSlot + 1 === historyCapacity ? 0 : currentSlot + 1;
|
|
374
|
+
const nextPosIndex = nextSlot * 3;
|
|
375
|
+
nextX = historyPositions[nextPosIndex];
|
|
376
|
+
nextY = historyPositions[nextPosIndex + 1];
|
|
377
|
+
nextZ = historyPositions[nextPosIndex + 2];
|
|
378
|
+
if (!samplesInWorld) {
|
|
379
|
+
const w = 1 / (m03 * nextX + m13 * nextY + m23 * nextZ + m33);
|
|
380
|
+
const tx = (m00 * nextX + m10 * nextY + m20 * nextZ + m30) * w;
|
|
381
|
+
const ty = (m01 * nextX + m11 * nextY + m21 * nextZ + m31) * w;
|
|
382
|
+
nextZ = (m02 * nextX + m12 * nextY + m22 * nextZ + m32) * w;
|
|
383
|
+
nextX = tx;
|
|
384
|
+
nextY = ty;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
355
387
|
|
|
356
388
|
const currentSize = historySizes[currentSlot];
|
|
357
389
|
const currentColorIndex = colorFromParticle ? particleColorIndex : currentSlot * 4;
|
|
@@ -360,33 +392,6 @@ export class TrailBatch extends VFXBatch {
|
|
|
360
392
|
const currentColorZ = colors[currentColorIndex + 2];
|
|
361
393
|
const currentColorW = colors[currentColorIndex + 3];
|
|
362
394
|
|
|
363
|
-
previousSlot = currentSlot;
|
|
364
|
-
currentSlot = nextSlot;
|
|
365
|
-
|
|
366
|
-
if (!samplesInWorld) {
|
|
367
|
-
// Inlined point transforms — three per trail sample.
|
|
368
|
-
let w = 1 / (m03 * currentX + m13 * currentY + m23 * currentZ + m33);
|
|
369
|
-
let tx = (m00 * currentX + m10 * currentY + m20 * currentZ + m30) * w;
|
|
370
|
-
let ty = (m01 * currentX + m11 * currentY + m21 * currentZ + m31) * w;
|
|
371
|
-
currentZ = (m02 * currentX + m12 * currentY + m22 * currentZ + m32) * w;
|
|
372
|
-
currentX = tx;
|
|
373
|
-
currentY = ty;
|
|
374
|
-
|
|
375
|
-
w = 1 / (m03 * previousX + m13 * previousY + m23 * previousZ + m33);
|
|
376
|
-
tx = (m00 * previousX + m10 * previousY + m20 * previousZ + m30) * w;
|
|
377
|
-
ty = (m01 * previousX + m11 * previousY + m21 * previousZ + m31) * w;
|
|
378
|
-
previousZ = (m02 * previousX + m12 * previousY + m22 * previousZ + m32) * w;
|
|
379
|
-
previousX = tx;
|
|
380
|
-
previousY = ty;
|
|
381
|
-
|
|
382
|
-
w = 1 / (m03 * nextX + m13 * nextY + m23 * nextZ + m33);
|
|
383
|
-
tx = (m00 * nextX + m10 * nextY + m20 * nextZ + m30) * w;
|
|
384
|
-
ty = (m01 * nextX + m11 * nextY + m21 * nextZ + m31) * w;
|
|
385
|
-
nextZ = (m02 * nextX + m12 * nextY + m22 * nextZ + m32) * w;
|
|
386
|
-
nextX = tx;
|
|
387
|
-
nextY = ty;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
395
|
const pi = index * 3;
|
|
391
396
|
this.positionBuffer[pi] = currentX;
|
|
392
397
|
this.positionBuffer[pi + 1] = currentY;
|
|
@@ -409,9 +414,6 @@ export class TrailBatch extends VFXBatch {
|
|
|
409
414
|
this.nextBuffer[pi + 4] = nextY;
|
|
410
415
|
this.nextBuffer[pi + 5] = nextZ;
|
|
411
416
|
|
|
412
|
-
this.sideBuffer[index] = 1;
|
|
413
|
-
this.sideBuffer[index + 1] = -1;
|
|
414
|
-
|
|
415
417
|
if (systemWorldSpace || particle.parentMatrix) {
|
|
416
418
|
this.widthBuffer[index] = currentSize;
|
|
417
419
|
this.widthBuffer[index + 1] = currentSize;
|
|
@@ -448,6 +450,14 @@ export class TrailBatch extends VFXBatch {
|
|
|
448
450
|
this.indexBuffer[triangles * 3 + 2] = index + 3;
|
|
449
451
|
triangles++;
|
|
450
452
|
}
|
|
453
|
+
|
|
454
|
+
previousX = currentX;
|
|
455
|
+
previousY = currentY;
|
|
456
|
+
previousZ = currentZ;
|
|
457
|
+
currentX = nextX;
|
|
458
|
+
currentY = nextY;
|
|
459
|
+
currentZ = nextZ;
|
|
460
|
+
currentSlot = nextSlot;
|
|
451
461
|
}
|
|
452
462
|
|
|
453
463
|
// Live tip: same place a sprite would be drawn this frame, past
|
|
@@ -505,9 +515,6 @@ export class TrailBatch extends VFXBatch {
|
|
|
505
515
|
this.nextBuffer[headPi + 4] = tipY;
|
|
506
516
|
this.nextBuffer[headPi + 5] = tipZ;
|
|
507
517
|
|
|
508
|
-
this.sideBuffer[index] = 1;
|
|
509
|
-
this.sideBuffer[index + 1] = -1;
|
|
510
|
-
|
|
511
518
|
const tipSize = particle.size.x;
|
|
512
519
|
if (systemWorldSpace || particle.parentMatrix) {
|
|
513
520
|
this.widthBuffer[index] = tipSize;
|
|
@@ -560,22 +567,30 @@ export class TrailBatch extends VFXBatch {
|
|
|
560
567
|
* costs that loop its optimised code.
|
|
561
568
|
*/
|
|
562
569
|
private uploadGeometry(index: number, triangles: number): void {
|
|
570
|
+
const geometry = this.mesh.geometry;
|
|
571
|
+
const indexBuffer = geometry?.getIndexBuffer();
|
|
563
572
|
if (index > 0 && triangles > 0) {
|
|
564
573
|
// Upload only the vertices written this frame; the buffers are sized for
|
|
565
574
|
// the high-water mark of every system in the batch.
|
|
566
575
|
this.mesh.updateVerticesData(VertexBuffer.PositionKind, this.positionBuffer.subarray(0, index * 3));
|
|
567
576
|
this.mesh.updateVerticesData(VertexBuffer.UVKind, this.uvBuffer.subarray(0, index * 2));
|
|
568
|
-
|
|
577
|
+
// Straight to the GPU buffer. Going through `mesh.updateIndices` with a
|
|
578
|
+
// range whose length changes every frame makes Babylon copy the
|
|
579
|
+
// indices, rebuild the sub mesh and recompute its bounds from the
|
|
580
|
+
// whole position buffer, all of which this batch then undoes.
|
|
581
|
+
if (indexBuffer) {
|
|
582
|
+
this.scene
|
|
583
|
+
.getEngine()
|
|
584
|
+
.updateDynamicIndexBuffer(indexBuffer, this.indexBuffer.subarray(0, triangles * 3), 0);
|
|
585
|
+
}
|
|
569
586
|
this.previousVB.update(this.previousBuffer.subarray(0, index * 3));
|
|
570
587
|
this.nextVB.update(this.nextBuffer.subarray(0, index * 3));
|
|
571
|
-
this.sideVB.update(this.sideBuffer.subarray(0, index));
|
|
572
588
|
this.widthVB.update(this.widthBuffer.subarray(0, index));
|
|
573
589
|
|
|
574
590
|
const colorVB = this.mesh.getVertexBuffer(VertexBuffer.ColorKind);
|
|
575
591
|
if (colorVB) colorVB.update(this.colorBuffer.subarray(0, index * 4));
|
|
576
592
|
|
|
577
593
|
if (this.mesh.subMeshes && this.mesh.subMeshes.length > 0) {
|
|
578
|
-
this.adoptMeshBounds();
|
|
579
594
|
this.mesh.subMeshes[0].indexCount = triangles * 3;
|
|
580
595
|
this.mesh.subMeshes[0].verticesCount = index;
|
|
581
596
|
}
|
|
@@ -593,8 +608,9 @@ export class TrailBatch extends VFXBatch {
|
|
|
593
608
|
// every empty frame.
|
|
594
609
|
if (!this.drawsNothing) {
|
|
595
610
|
this.indexBuffer.fill(0, 0, 6);
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
if (indexBuffer) {
|
|
612
|
+
this.scene.getEngine().updateDynamicIndexBuffer(indexBuffer, this.indexBuffer.subarray(0, 6), 0);
|
|
613
|
+
}
|
|
598
614
|
this.drawsNothing = true;
|
|
599
615
|
}
|
|
600
616
|
this.mesh.subMeshes[0].indexCount = 6;
|
package/src/VFXBatch.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
2
2
|
import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
|
|
3
|
+
import {Color3} from '@babylonjs/core/Maths/math.color';
|
|
4
|
+
import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
|
|
3
5
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
4
6
|
import {Scene} from '@babylonjs/core/scene';
|
|
5
7
|
import {IParticleSystem, Vector3} from 'quarks.core';
|
|
@@ -14,6 +16,96 @@ export enum RenderMode {
|
|
|
14
16
|
VerticalBillBoard = 5,
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* How mesh particles are lit.
|
|
21
|
+
*
|
|
22
|
+
* - `'fixed'` (default): one directional light plus ambient, the same for every
|
|
23
|
+
* mesh batch of a renderer, set with {@link BatchedRenderer.setMeshLighting}.
|
|
24
|
+
* - `'unlit'`: no lighting term — the texture times the particle colour, for
|
|
25
|
+
* emissive meshes (fire, energy, glowing blades).
|
|
26
|
+
* - `'scene'`: follows the scene's first enabled `DirectionalLight` or
|
|
27
|
+
* `HemisphericLight` every frame, falling back to the fixed light when there
|
|
28
|
+
* is none.
|
|
29
|
+
*
|
|
30
|
+
* Only mesh particles are lit; other render modes ignore it.
|
|
31
|
+
*/
|
|
32
|
+
export type MeshLighting = 'unlit' | 'fixed' | 'scene';
|
|
33
|
+
|
|
34
|
+
/** The light {@link MeshLighting} `'fixed'` mesh particles are lit by. */
|
|
35
|
+
export interface MeshLightingSettings {
|
|
36
|
+
/** Direction the light travels in, world space. Default `(0.4, -1, 0.6)`. */
|
|
37
|
+
direction: BVector3;
|
|
38
|
+
/** Light colour, intensity folded in. Default white. */
|
|
39
|
+
color: Color3;
|
|
40
|
+
/** Light every face gets regardless of its direction. Default `(0.35, 0.35, 0.35)`. */
|
|
41
|
+
ambient: Color3;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The light mesh particles have always been lit by. */
|
|
45
|
+
export function defaultMeshLighting(): MeshLightingSettings {
|
|
46
|
+
return {
|
|
47
|
+
direction: new BVector3(0.4, -1, 0.6),
|
|
48
|
+
color: new Color3(1, 1, 1),
|
|
49
|
+
ambient: new Color3(0.35, 0.35, 0.35),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A rim (Fresnel) term on mesh particles: a colour that grows where the surface
|
|
55
|
+
* turns away from the camera, `pow(1 - |N·V|, power) * strength`. Unset: no rim.
|
|
56
|
+
* Only mesh particles draw it.
|
|
57
|
+
*/
|
|
58
|
+
export interface MeshRim {
|
|
59
|
+
/** Edge colour, linear RGB. Default `[1, 1, 1]`. */
|
|
60
|
+
color: [number, number, number];
|
|
61
|
+
/** Falloff exponent; higher keeps the rim to a thinner edge. Default 2. */
|
|
62
|
+
power: number;
|
|
63
|
+
/** How much of the rim is applied, usually 0…1. Default 1. */
|
|
64
|
+
strength: number;
|
|
65
|
+
/**
|
|
66
|
+
* `'add'` (default) adds the colour, for a glowing edge; `'mix'` blends the
|
|
67
|
+
* surface toward it, for an outline (a dark ink line with a dark colour).
|
|
68
|
+
*/
|
|
69
|
+
mode: 'add' | 'mix';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A complete rim from a partial one, every field defaulted and kept in range,
|
|
74
|
+
* or undefined when `rim` is unset.
|
|
75
|
+
*/
|
|
76
|
+
export function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): MeshRim | undefined {
|
|
77
|
+
if (!rim || typeof rim !== 'object') {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const color = Array.isArray(rim.color) ? rim.color : [1, 1, 1];
|
|
81
|
+
const channel = (value: unknown, fallback: number) =>
|
|
82
|
+
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
83
|
+
return {
|
|
84
|
+
color: [channel(color[0], 1), channel(color[1], 1), channel(color[2], 1)],
|
|
85
|
+
power: Math.max(0.01, channel(rim.power, 2)),
|
|
86
|
+
strength: Math.max(0, channel(rim.strength, 1)),
|
|
87
|
+
mode: rim.mode === 'mix' ? 'mix' : 'add',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True when both describe the same rim (or both none). */
|
|
92
|
+
export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): boolean {
|
|
93
|
+
if (a === b) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (!a || !b) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
a.mode === b.mode &&
|
|
101
|
+
a.power === b.power &&
|
|
102
|
+
a.strength === b.strength &&
|
|
103
|
+
a.color[0] === b.color[0] &&
|
|
104
|
+
a.color[1] === b.color[1] &&
|
|
105
|
+
a.color[2] === b.color[2]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
17
109
|
export interface StoredBatchSettings {
|
|
18
110
|
instancingGeometry: Float32Array;
|
|
19
111
|
instancingIndices: Uint32Array | Uint16Array;
|
|
@@ -40,6 +132,10 @@ export interface StoredBatchSettings {
|
|
|
40
132
|
layerMask: number;
|
|
41
133
|
/** Billboard pivot in particle-size units, see {@link VFXBatchSettings.pivot}. */
|
|
42
134
|
pivot?: Vector3;
|
|
135
|
+
/** Mesh particle lighting, see {@link VFXBatchSettings.lighting}. */
|
|
136
|
+
lighting?: MeshLighting;
|
|
137
|
+
/** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
|
|
138
|
+
rim?: MeshRim;
|
|
43
139
|
}
|
|
44
140
|
|
|
45
141
|
export abstract class VFXBatch {
|
|
@@ -79,6 +175,8 @@ export abstract class VFXBatch {
|
|
|
79
175
|
reflectionAtlas: settings.reflectionAtlas ?? null,
|
|
80
176
|
pivot: settings.pivot?.clone(),
|
|
81
177
|
layerMask: settings.layerMask,
|
|
178
|
+
lighting: settings.lighting,
|
|
179
|
+
rim: normalizeMeshRim(settings.rim),
|
|
82
180
|
};
|
|
83
181
|
this.mesh = new Mesh('vfxBatch', scene);
|
|
84
182
|
this.mesh.alwaysSelectAsActiveMesh = true;
|
|
@@ -116,6 +214,9 @@ export abstract class VFXBatch {
|
|
|
116
214
|
}
|
|
117
215
|
}
|
|
118
216
|
|
|
217
|
+
/** Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it. */
|
|
218
|
+
applyMeshLighting(_lighting: MeshLightingSettings): void {}
|
|
219
|
+
|
|
119
220
|
abstract setupBuffers(): void;
|
|
120
221
|
abstract expandBuffers(target: number): void;
|
|
121
222
|
abstract rebuildMaterial(): void;
|
package/src/index.ts
CHANGED
|
@@ -25,15 +25,21 @@ export type {
|
|
|
25
25
|
ParticleSystemParameters,
|
|
26
26
|
} from './ParticleSystem';
|
|
27
27
|
export {QuarksLoader} from './QuarksLoader';
|
|
28
|
-
export type {
|
|
28
|
+
export type {
|
|
29
|
+
QuarksBatchLoadOptions,
|
|
30
|
+
QuarksJSONLoadEntry,
|
|
31
|
+
QuarksLoaderOptions,
|
|
32
|
+
QuarksResourceKind,
|
|
33
|
+
QuarksResourceResolver,
|
|
34
|
+
} from './QuarksLoader';
|
|
29
35
|
export {QuarksPrefab} from './QuarksPrefab';
|
|
30
36
|
export type {AnimationData, QuarksTimelineClip} from './QuarksPrefab';
|
|
31
37
|
export {QuarksUtil} from './QuarksUtil';
|
|
32
38
|
export * from './shaders/';
|
|
33
39
|
export {SpriteBatch} from './SpriteBatch';
|
|
34
40
|
export {TrailBatch} from './TrailBatch';
|
|
35
|
-
export {RenderMode, VFXBatch} from './VFXBatch';
|
|
36
|
-
export type {StoredBatchSettings} from './VFXBatch';
|
|
41
|
+
export {defaultMeshLighting, meshRimEqual, normalizeMeshRim, RenderMode, VFXBatch} from './VFXBatch';
|
|
42
|
+
export type {MeshLighting, MeshLightingSettings, MeshRim, StoredBatchSettings} from './VFXBatch';
|
|
37
43
|
|
|
38
44
|
// Re-export quarks.core types (interfaces - type-only)
|
|
39
45
|
export type {
|
|
@@ -4,9 +4,11 @@ varying vec4 vColor;
|
|
|
4
4
|
varying vec3 vNormal;
|
|
5
5
|
varying vec3 vWorldPos;
|
|
6
6
|
|
|
7
|
+
#ifndef MESH_UNLIT
|
|
7
8
|
uniform vec3 lightDirection;
|
|
8
9
|
uniform vec3 lightColor;
|
|
9
10
|
uniform vec3 ambientColor;
|
|
11
|
+
#endif
|
|
10
12
|
|
|
11
13
|
#ifdef TILE_BLEND
|
|
12
14
|
varying vec2 vUV2;
|
|
@@ -17,11 +19,19 @@ varying float vTileBlend;
|
|
|
17
19
|
uniform sampler2D map;
|
|
18
20
|
#endif
|
|
19
21
|
|
|
22
|
+
#if defined(USE_ENVMAP_ATLAS) || defined(MESH_RIM)
|
|
23
|
+
uniform vec3 eyePosition;
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
#ifdef MESH_RIM
|
|
27
|
+
uniform vec3 rimColor;
|
|
28
|
+
uniform vec2 rimParams;
|
|
29
|
+
#endif
|
|
30
|
+
|
|
20
31
|
// One 3×2 atlas (px py pz / nx ny nz). Avoids samplerCube and multi-face
|
|
21
32
|
// sampler bindings that raise GL_INVALID_OPERATION on iOS WebKit.
|
|
22
33
|
#ifdef USE_ENVMAP_ATLAS
|
|
23
34
|
uniform sampler2D envAtlas;
|
|
24
|
-
uniform vec3 eyePosition;
|
|
25
35
|
uniform float reflectionLevel;
|
|
26
36
|
|
|
27
37
|
vec3 sampleEnvAtlas(vec3 r) {
|
|
@@ -94,11 +104,15 @@ void main() {
|
|
|
94
104
|
#endif
|
|
95
105
|
|
|
96
106
|
vec3 N = normalize(vNormal);
|
|
107
|
+
#ifdef MESH_UNLIT
|
|
108
|
+
vec3 color = baseColor.rgb;
|
|
109
|
+
#else
|
|
97
110
|
vec3 L = normalize(-lightDirection);
|
|
98
111
|
float NdotL = max(dot(N, L), 0.0);
|
|
99
112
|
vec3 litColor = ambientColor + lightColor * NdotL;
|
|
100
113
|
|
|
101
114
|
vec3 color = baseColor.rgb * litColor;
|
|
115
|
+
#endif
|
|
102
116
|
|
|
103
117
|
#ifdef USE_ENVMAP_ATLAS
|
|
104
118
|
vec3 viewDir = normalize(vWorldPos - eyePosition);
|
|
@@ -106,6 +120,17 @@ void main() {
|
|
|
106
120
|
color += sampleEnvAtlas(reflectionCoords) * reflectionLevel;
|
|
107
121
|
#endif
|
|
108
122
|
|
|
123
|
+
#ifdef MESH_RIM
|
|
124
|
+
// Fresnel: strongest where the surface turns away from the camera.
|
|
125
|
+
vec3 V = normalize(eyePosition - vWorldPos);
|
|
126
|
+
float rim = pow(1.0 - abs(dot(N, V)), rimParams.x) * rimParams.y;
|
|
127
|
+
#ifdef MESH_RIM_MIX
|
|
128
|
+
color = mix(color, rimColor, clamp(rim, 0.0, 1.0));
|
|
129
|
+
#else
|
|
130
|
+
color += rimColor * rim;
|
|
131
|
+
#endif
|
|
132
|
+
#endif
|
|
133
|
+
|
|
109
134
|
gl_FragColor = vec4(color, baseColor.a);
|
|
110
135
|
|
|
111
136
|
#ifdef SOFT_PARTICLES
|
|
@@ -4,9 +4,11 @@ varying vColor: vec4f;
|
|
|
4
4
|
varying vNormal: vec3f;
|
|
5
5
|
varying vWorldPos: vec3f;
|
|
6
6
|
|
|
7
|
+
#ifndef MESH_UNLIT
|
|
7
8
|
uniform lightDirection: vec3f;
|
|
8
9
|
uniform lightColor: vec3f;
|
|
9
10
|
uniform ambientColor: vec3f;
|
|
11
|
+
#endif
|
|
10
12
|
|
|
11
13
|
#ifdef TILE_BLEND
|
|
12
14
|
varying vUV2: vec2f;
|
|
@@ -18,10 +20,18 @@ var mapSampler: sampler;
|
|
|
18
20
|
var map: texture_2d<f32>;
|
|
19
21
|
#endif
|
|
20
22
|
|
|
23
|
+
#if defined(USE_ENVMAP_ATLAS) || defined(MESH_RIM)
|
|
24
|
+
uniform eyePosition: vec3f;
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
#ifdef MESH_RIM
|
|
28
|
+
uniform rimColor: vec3f;
|
|
29
|
+
uniform rimParams: vec2f;
|
|
30
|
+
#endif
|
|
31
|
+
|
|
21
32
|
#ifdef USE_ENVMAP_ATLAS
|
|
22
33
|
var envAtlasSampler: sampler;
|
|
23
34
|
var envAtlas: texture_2d<f32>;
|
|
24
|
-
uniform eyePosition: vec3f;
|
|
25
35
|
uniform reflectionLevel: f32;
|
|
26
36
|
|
|
27
37
|
fn sampleEnvAtlas(r: vec3f) -> vec3f {
|
|
@@ -95,11 +105,15 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
|
95
105
|
#endif
|
|
96
106
|
|
|
97
107
|
let N = normalize(fragmentInputs.vNormal);
|
|
108
|
+
#ifdef MESH_UNLIT
|
|
109
|
+
var color = baseColor.rgb;
|
|
110
|
+
#else
|
|
98
111
|
let L = normalize(-uniforms.lightDirection);
|
|
99
112
|
let NdotL = max(dot(N, L), 0.0);
|
|
100
113
|
let litColor = uniforms.ambientColor + uniforms.lightColor * NdotL;
|
|
101
114
|
|
|
102
115
|
var color = baseColor.rgb * litColor;
|
|
116
|
+
#endif
|
|
103
117
|
|
|
104
118
|
#ifdef USE_ENVMAP_ATLAS
|
|
105
119
|
let viewDir = normalize(fragmentInputs.vWorldPos - uniforms.eyePosition);
|
|
@@ -107,6 +121,17 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
|
107
121
|
color += sampleEnvAtlas(reflectionCoords) * uniforms.reflectionLevel;
|
|
108
122
|
#endif
|
|
109
123
|
|
|
124
|
+
#ifdef MESH_RIM
|
|
125
|
+
// Fresnel: strongest where the surface turns away from the camera.
|
|
126
|
+
let V = normalize(uniforms.eyePosition - fragmentInputs.vWorldPos);
|
|
127
|
+
let rimTerm = pow(1.0 - abs(dot(N, V)), uniforms.rimParams.x) * uniforms.rimParams.y;
|
|
128
|
+
#ifdef MESH_RIM_MIX
|
|
129
|
+
color = mix(color, uniforms.rimColor, clamp(rimTerm, 0.0, 1.0));
|
|
130
|
+
#else
|
|
131
|
+
color += uniforms.rimColor * rimTerm;
|
|
132
|
+
#endif
|
|
133
|
+
#endif
|
|
134
|
+
|
|
110
135
|
var finalColor = vec4f(color, baseColor.a);
|
|
111
136
|
|
|
112
137
|
#ifdef SOFT_PARTICLES
|