babylon.quarks 0.18.0 → 0.19.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/README.md +16 -16
- 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 +1835 -1171
- package/package.json +67 -67
- package/src/BatchedRenderer.ts +213 -14
- package/src/MeshSurfaceEmitter.ts +25 -20
- package/src/ParticleEmitter.ts +34 -10
- package/src/ParticleSystem.ts +467 -203
- package/src/QuarksLoader.ts +68 -15
- package/src/QuarksPrefab.ts +8 -11
- package/src/QuarksUtil.ts +1 -3
- package/src/SpriteBatch.ts +447 -96
- package/src/TrailBatch.ts +361 -164
- package/src/VFXBatch.ts +20 -4
- package/src/envAtlas.ts +136 -0
- package/src/index.ts +34 -29
- package/src/materials/ParticleMaterials.ts +1 -1
- package/src/shaders/index.ts +4 -4
- package/src/shaders/local_particle_physics_vert.wgsl.ts +81 -0
- package/src/shaders/particle_frag.wgsl.ts +64 -0
- package/src/shaders/particle_physics_frag.glsl.ts +56 -1
- package/src/shaders/particle_physics_frag.wgsl.ts +125 -0
- package/src/shaders/particle_vert.wgsl.ts +87 -0
- package/src/shaders/shaderLanguageSupport.ts +54 -0
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +88 -0
- package/src/shaders/trail_frag.wgsl.ts +23 -0
- package/src/shaders/trail_vert.wgsl.ts +63 -0
package/src/VFXBatch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
2
|
-
import {Scene} from '@babylonjs/core/scene';
|
|
3
1
|
import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
4
2
|
import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
|
|
3
|
+
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
4
|
+
import {Scene} from '@babylonjs/core/scene';
|
|
5
5
|
import {IParticleSystem} from 'quarks.core';
|
|
6
6
|
import {VFXBatchSettings} from './BatchedRenderer';
|
|
7
7
|
|
|
@@ -33,6 +33,10 @@ export interface StoredBatchSettings {
|
|
|
33
33
|
materialDepthWrite: boolean;
|
|
34
34
|
materialAlphaTest: number;
|
|
35
35
|
texture: any;
|
|
36
|
+
reflectionTexture: BaseTexture | null;
|
|
37
|
+
reflectionLevel: number;
|
|
38
|
+
reflectionFaces: BaseTexture[] | null;
|
|
39
|
+
reflectionAtlas: BaseTexture | null;
|
|
36
40
|
layerMask: number;
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -42,6 +46,7 @@ export abstract class VFXBatch {
|
|
|
42
46
|
settings: StoredBatchSettings;
|
|
43
47
|
protected maxParticles: number;
|
|
44
48
|
protected scene: Scene;
|
|
49
|
+
private readonly visibleSystems: IParticleSystem[] = [];
|
|
45
50
|
|
|
46
51
|
protected constructor(settings: VFXBatchSettings, scene: Scene) {
|
|
47
52
|
this.scene = scene;
|
|
@@ -66,6 +71,10 @@ export abstract class VFXBatch {
|
|
|
66
71
|
materialDepthWrite: settings.materialDepthWrite,
|
|
67
72
|
materialAlphaTest: settings.materialAlphaTest,
|
|
68
73
|
texture: settings.texture,
|
|
74
|
+
reflectionTexture: settings.reflectionTexture ?? null,
|
|
75
|
+
reflectionLevel: settings.reflectionLevel ?? 1,
|
|
76
|
+
reflectionFaces: settings.reflectionFaces ?? null,
|
|
77
|
+
reflectionAtlas: settings.reflectionAtlas ?? null,
|
|
69
78
|
layerMask: settings.layerMask,
|
|
70
79
|
};
|
|
71
80
|
this.mesh = new Mesh('vfxBatch', scene);
|
|
@@ -80,13 +89,20 @@ export abstract class VFXBatch {
|
|
|
80
89
|
this.systems.delete(system);
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Visible systems of this batch, written into a buffer owned by the batch.
|
|
94
|
+
* Called once per frame per batch, so the result is reused rather than
|
|
95
|
+
* reallocated; treat it as valid only until the next call.
|
|
96
|
+
*/
|
|
83
97
|
getVisibleSystems(): IParticleSystem[] {
|
|
84
|
-
const visibleSystems
|
|
98
|
+
const visibleSystems = this.visibleSystems;
|
|
99
|
+
let count = 0;
|
|
85
100
|
for (const system of this.systems) {
|
|
86
101
|
if (system.emitter.visible) {
|
|
87
|
-
visibleSystems
|
|
102
|
+
visibleSystems[count++] = system;
|
|
88
103
|
}
|
|
89
104
|
}
|
|
105
|
+
visibleSystems.length = count;
|
|
90
106
|
return visibleSystems;
|
|
91
107
|
}
|
|
92
108
|
|
package/src/envAtlas.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
2
|
+
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
3
|
+
import {Scene} from '@babylonjs/core/scene';
|
|
4
|
+
|
|
5
|
+
const atlasByCube = new WeakMap<BaseTexture, Texture>();
|
|
6
|
+
const pendingByCube = new WeakMap<BaseTexture, Promise<Texture | null>>();
|
|
7
|
+
|
|
8
|
+
/** Reads the six face URLs from a Babylon CubeTexture (px py pz nx ny nz). */
|
|
9
|
+
export function cubeFaceUrls(reflection: BaseTexture | null | undefined): string[] | null {
|
|
10
|
+
if (!reflection?.isCube) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const files =
|
|
14
|
+
(reflection as {files?: string[]; _files?: string[]}).files ?? (reflection as {_files?: string[]})._files;
|
|
15
|
+
return files?.length === 6 ? files.slice() : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Returns a previously built atlas for this cube, if any. */
|
|
19
|
+
export function getCachedEnvAtlas(cube: BaseTexture): Texture | null {
|
|
20
|
+
return atlasByCube.get(cube) ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Stores an atlas for a cube source (tests / prebuilt atlases). */
|
|
24
|
+
export function cacheEnvAtlas(cube: BaseTexture, atlas: Texture): void {
|
|
25
|
+
atlasByCube.set(cube, atlas);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Packs six cube-face images into one 3×2 atlas Texture (px py pz / nx ny nz).
|
|
30
|
+
* Uses invertY:false so UV cellY=0 maps to the +faces row matching the shader.
|
|
31
|
+
*/
|
|
32
|
+
export async function createEnvAtlasFromFaceUrls(urls: string[], scene: Scene): Promise<Texture> {
|
|
33
|
+
if (typeof document === 'undefined' || typeof Image === 'undefined') {
|
|
34
|
+
throw new Error('createEnvAtlasFromFaceUrls requires a DOM Image/canvas environment');
|
|
35
|
+
}
|
|
36
|
+
const images = await Promise.all(urls.map((url) => loadImage(url)));
|
|
37
|
+
const size = images[0].width || images[0].naturalWidth;
|
|
38
|
+
if (!size) {
|
|
39
|
+
throw new Error('Env atlas face image has zero size');
|
|
40
|
+
}
|
|
41
|
+
const canvas = document.createElement('canvas');
|
|
42
|
+
canvas.width = size * 3;
|
|
43
|
+
canvas.height = size * 2;
|
|
44
|
+
const ctx = canvas.getContext('2d');
|
|
45
|
+
if (!ctx) {
|
|
46
|
+
throw new Error('Could not create env atlas canvas');
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < 6; i++) {
|
|
49
|
+
ctx.drawImage(images[i], (i % 3) * size, Math.floor(i / 3) * size, size, size);
|
|
50
|
+
}
|
|
51
|
+
const atlas = new Texture(canvas.toDataURL('image/jpeg', 0.92), scene, {
|
|
52
|
+
noMipmap: true,
|
|
53
|
+
invertY: false,
|
|
54
|
+
samplingMode: Texture.LINEAR_LINEAR,
|
|
55
|
+
});
|
|
56
|
+
atlas.name = 'quarksEnvAtlas';
|
|
57
|
+
return atlas;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Overridable builder hook (tests replace this to avoid needing DOM Image/canvas). */
|
|
61
|
+
export const envAtlasBuilder = {
|
|
62
|
+
createFromFaceUrls: createEnvAtlasFromFaceUrls,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ensures a 3×2 env atlas exists for a CubeTexture. Returns a cached atlas
|
|
67
|
+
* synchronously when available; otherwise starts (or joins) an async build and
|
|
68
|
+
* invokes onReady when the atlas is usable.
|
|
69
|
+
*/
|
|
70
|
+
export function ensureEnvAtlasFromCube(
|
|
71
|
+
cube: BaseTexture,
|
|
72
|
+
scene: Scene,
|
|
73
|
+
onReady: (atlas: Texture) => void
|
|
74
|
+
): Texture | null {
|
|
75
|
+
const cached = atlasByCube.get(cube);
|
|
76
|
+
if (cached) {
|
|
77
|
+
onReady(cached);
|
|
78
|
+
return cached;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const kick = () => {
|
|
82
|
+
const existingPending = pendingByCube.get(cube);
|
|
83
|
+
if (existingPending) {
|
|
84
|
+
existingPending.then((atlas) => {
|
|
85
|
+
if (atlas) {
|
|
86
|
+
onReady(atlas);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const urls = cubeFaceUrls(cube);
|
|
93
|
+
if (!urls) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const pending = envAtlasBuilder
|
|
98
|
+
.createFromFaceUrls(urls, scene)
|
|
99
|
+
.then((atlas) => {
|
|
100
|
+
atlasByCube.set(cube, atlas);
|
|
101
|
+
pendingByCube.delete(cube);
|
|
102
|
+
onReady(atlas);
|
|
103
|
+
return atlas;
|
|
104
|
+
})
|
|
105
|
+
.catch((err) => {
|
|
106
|
+
pendingByCube.delete(cube);
|
|
107
|
+
console.warn('[babylon.quarks] Failed to build env atlas from cubemap faces', err);
|
|
108
|
+
return null;
|
|
109
|
+
});
|
|
110
|
+
pendingByCube.set(cube, pending);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (cube.isReady()) {
|
|
114
|
+
kick();
|
|
115
|
+
} else {
|
|
116
|
+
const observable = (cube as {onLoadObservable?: {addOnce: (cb: () => void) => void}}).onLoadObservable;
|
|
117
|
+
if (observable) {
|
|
118
|
+
observable.addOnce(() => kick());
|
|
119
|
+
} else {
|
|
120
|
+
kick();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return atlasByCube.get(cube) ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Loads one image URL as an HTMLImageElement. */
|
|
128
|
+
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const image = new Image();
|
|
131
|
+
image.crossOrigin = 'anonymous';
|
|
132
|
+
image.onload = () => resolve(image);
|
|
133
|
+
image.onerror = () => reject(new Error(`Failed to load env face ${url}`));
|
|
134
|
+
image.src = url;
|
|
135
|
+
});
|
|
136
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,58 +2,63 @@ import {loadPlugin} from 'quarks.core';
|
|
|
2
2
|
import {MeshSurfaceEmitterPlugin} from './MeshSurfaceEmitter';
|
|
3
3
|
import {registerShaderChunks} from './shaders';
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
export type {VFXBatchSettings, AdaptivePerformanceOptions, AdaptivePerformanceState} from './BatchedRenderer';
|
|
5
|
+
export * from 'quarks.core';
|
|
7
6
|
export {BatchedParticleRenderer} from './BatchedParticleRenderer';
|
|
8
|
-
export {
|
|
9
|
-
export type {
|
|
10
|
-
export {
|
|
11
|
-
|
|
7
|
+
export {BatchedRenderer} from './BatchedRenderer';
|
|
8
|
+
export type {AdaptivePerformanceOptions, AdaptivePerformanceState, VFXBatchSettings} from './BatchedRenderer';
|
|
9
|
+
export {
|
|
10
|
+
cacheEnvAtlas,
|
|
11
|
+
createEnvAtlasFromFaceUrls,
|
|
12
|
+
cubeFaceUrls,
|
|
13
|
+
ensureEnvAtlasFromCube,
|
|
14
|
+
getCachedEnvAtlas,
|
|
15
|
+
} from './envAtlas';
|
|
16
|
+
export * from './materials/';
|
|
17
|
+
export {MeshSurfaceEmitter, MeshSurfaceEmitterPlugin} from './MeshSurfaceEmitter';
|
|
18
|
+
export {ParticleEmitter} from './ParticleEmitter';
|
|
12
19
|
export {ParticleSystem} from './ParticleSystem';
|
|
13
20
|
export type {
|
|
14
|
-
|
|
21
|
+
BabylonMetaData,
|
|
15
22
|
BurstParameters,
|
|
16
23
|
BurstParametersJSON,
|
|
17
24
|
ParticleSystemJSONParameters,
|
|
18
|
-
|
|
25
|
+
ParticleSystemParameters,
|
|
19
26
|
} from './ParticleSystem';
|
|
20
|
-
export {ParticleEmitter} from './ParticleEmitter';
|
|
21
|
-
export {QuarksUtil} from './QuarksUtil';
|
|
22
27
|
export {QuarksLoader} from './QuarksLoader';
|
|
23
28
|
export type {QuarksLoaderOptions} from './QuarksLoader';
|
|
24
29
|
export {QuarksPrefab} from './QuarksPrefab';
|
|
25
30
|
export type {AnimationData, QuarksTimelineClip} from './QuarksPrefab';
|
|
26
|
-
export {
|
|
31
|
+
export {QuarksUtil} from './QuarksUtil';
|
|
27
32
|
export * from './shaders/';
|
|
28
|
-
export
|
|
29
|
-
export
|
|
30
|
-
|
|
33
|
+
export {SpriteBatch} from './SpriteBatch';
|
|
34
|
+
export {TrailBatch} from './TrailBatch';
|
|
35
|
+
export {RenderMode, VFXBatch} from './VFXBatch';
|
|
36
|
+
export type {StoredBatchSettings} from './VFXBatch';
|
|
31
37
|
|
|
32
38
|
// Re-export quarks.core types (interfaces - type-only)
|
|
33
39
|
export type {
|
|
34
|
-
|
|
35
|
-
IParticle,
|
|
36
|
-
Particle,
|
|
37
|
-
EmissionState,
|
|
38
|
-
RendererEmitterSettings,
|
|
39
|
-
TrailSettings,
|
|
40
|
-
StretchedBillBoardSettings,
|
|
40
|
+
Behavior,
|
|
41
41
|
BillBoardSettings,
|
|
42
|
-
|
|
42
|
+
ColorGenerator,
|
|
43
|
+
EmissionState,
|
|
44
|
+
EmitterShape,
|
|
45
|
+
FunctionColorGenerator,
|
|
46
|
+
FunctionValueGenerator,
|
|
43
47
|
IEmitter,
|
|
48
|
+
IParticle,
|
|
49
|
+
IParticleSystem,
|
|
50
|
+
MeshSettings,
|
|
51
|
+
Particle,
|
|
44
52
|
ParticleSystemEvent,
|
|
45
53
|
ParticleSystemEventType,
|
|
54
|
+
RendererEmitterSettings,
|
|
55
|
+
RotationGenerator,
|
|
46
56
|
SerializationOptions,
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
StretchedBillBoardSettings,
|
|
58
|
+
TrailSettings,
|
|
49
59
|
ValueGenerator,
|
|
50
|
-
FunctionValueGenerator,
|
|
51
|
-
ColorGenerator,
|
|
52
|
-
FunctionColorGenerator,
|
|
53
|
-
RotationGenerator,
|
|
54
60
|
Vector3Generator,
|
|
55
61
|
} from 'quarks.core';
|
|
56
62
|
|
|
57
63
|
registerShaderChunks();
|
|
58
64
|
loadPlugin(MeshSurfaceEmitterPlugin);
|
|
59
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {Scene} from '@babylonjs/core/scene';
|
|
2
1
|
import {PBRMaterial} from '@babylonjs/core/Materials/PBR/pbrMaterial';
|
|
3
2
|
import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
|
|
3
|
+
import {Scene} from '@babylonjs/core/scene';
|
|
4
4
|
|
|
5
5
|
export type BabylonParticleMaterialParameters = Record<string, any>;
|
|
6
6
|
|
package/src/shaders/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export * from './chunks';
|
|
2
|
-
export {default as particleVertShader} from './particle_vert.glsl';
|
|
3
|
-
export {default as particleFragShader} from './particle_frag.glsl';
|
|
4
|
-
export {default as localParticleVertShader} from './local_particle_vert.glsl';
|
|
5
2
|
export {default as localParticlePhysicsVertShader} from './local_particle_physics_vert.glsl';
|
|
3
|
+
export {default as localParticleVertShader} from './local_particle_vert.glsl';
|
|
4
|
+
export {default as particleFragShader} from './particle_frag.glsl';
|
|
6
5
|
export {default as particlePhysicsFragShader} from './particle_physics_frag.glsl';
|
|
6
|
+
export {default as particleVertShader} from './particle_vert.glsl';
|
|
7
7
|
export {default as stretchedBillboardVertShader} from './stretched_bb_particle_vert.glsl';
|
|
8
|
-
export {default as trailVertShader} from './trail_vert.glsl';
|
|
9
8
|
export {default as trailFragShader} from './trail_frag.glsl';
|
|
9
|
+
export {default as trailVertShader} from './trail_vert.glsl';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export default /* wgsl */ `
|
|
2
|
+
attribute position: vec3f;
|
|
3
|
+
attribute uv: vec2f;
|
|
4
|
+
attribute normal: vec3f;
|
|
5
|
+
attribute offset: vec3f;
|
|
6
|
+
attribute color: vec4f;
|
|
7
|
+
attribute size: vec3f;
|
|
8
|
+
attribute rotation: vec4f;
|
|
9
|
+
attribute uvTile: f32;
|
|
10
|
+
|
|
11
|
+
uniform world: mat4x4f;
|
|
12
|
+
uniform view: mat4x4f;
|
|
13
|
+
uniform projection: mat4x4f;
|
|
14
|
+
|
|
15
|
+
#ifdef UV_TILE
|
|
16
|
+
uniform tileCountX: f32;
|
|
17
|
+
uniform tileCountY: f32;
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
varying vUV: vec2f;
|
|
21
|
+
varying vColor: vec4f;
|
|
22
|
+
varying vNormal: vec3f;
|
|
23
|
+
varying vWorldPos: vec3f;
|
|
24
|
+
|
|
25
|
+
#ifdef TILE_BLEND
|
|
26
|
+
varying vUV2: vec2f;
|
|
27
|
+
varying vTileBlend: f32;
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#ifdef SOFT_PARTICLES
|
|
31
|
+
varying projPosition: vec4f;
|
|
32
|
+
varying linearDepth: f32;
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
fn applyQuaternion(v: vec3f, q: vec4f) -> vec3f {
|
|
36
|
+
let qVec = q.xyz;
|
|
37
|
+
let qW = q.w;
|
|
38
|
+
let t = 2.0 * cross(qVec, v);
|
|
39
|
+
return v + qW * t + cross(qVec, t);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@vertex
|
|
43
|
+
fn main(input: VertexInputs) -> FragmentInputs {
|
|
44
|
+
let scaledPosition = vertexInputs.position * vertexInputs.size;
|
|
45
|
+
let rotatedPosition = applyQuaternion(scaledPosition, vertexInputs.rotation);
|
|
46
|
+
let rotatedNormal = normalize(applyQuaternion(vertexInputs.normal, vertexInputs.rotation));
|
|
47
|
+
let localPos = rotatedPosition + vertexInputs.offset;
|
|
48
|
+
|
|
49
|
+
let worldPos = uniforms.world * vec4f(localPos, 1.0);
|
|
50
|
+
let viewPos = uniforms.view * worldPos;
|
|
51
|
+
let clipPosition = uniforms.projection * viewPos;
|
|
52
|
+
vertexOutputs.position = clipPosition;
|
|
53
|
+
|
|
54
|
+
vertexOutputs.vWorldPos = worldPos.xyz;
|
|
55
|
+
vertexOutputs.vNormal = normalize((uniforms.world * vec4f(rotatedNormal, 0.0)).xyz);
|
|
56
|
+
|
|
57
|
+
#ifdef SOFT_PARTICLES
|
|
58
|
+
vertexOutputs.projPosition = clipPosition;
|
|
59
|
+
vertexOutputs.linearDepth = -viewPos.z;
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
#ifdef UV_TILE
|
|
63
|
+
let tc = vec2f(uniforms.tileCountX, uniforms.tileCountY);
|
|
64
|
+
let baseTile = floor(vertexInputs.uvTile);
|
|
65
|
+
let tileU = (baseTile % tc.x) / tc.x;
|
|
66
|
+
let tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
67
|
+
vertexOutputs.vUV = vertexInputs.uv / tc + vec2f(tileU, tileV);
|
|
68
|
+
#ifdef TILE_BLEND
|
|
69
|
+
let nextTile = ceil(vertexInputs.uvTile);
|
|
70
|
+
let nextU = (nextTile % tc.x) / tc.x;
|
|
71
|
+
let nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
72
|
+
vertexOutputs.vUV2 = vertexInputs.uv / tc + vec2f(nextU, nextV);
|
|
73
|
+
vertexOutputs.vTileBlend = fract(vertexInputs.uvTile);
|
|
74
|
+
#endif
|
|
75
|
+
#else
|
|
76
|
+
vertexOutputs.vUV = vertexInputs.uv;
|
|
77
|
+
#endif
|
|
78
|
+
|
|
79
|
+
vertexOutputs.vColor = vertexInputs.color;
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export default /* wgsl */ `
|
|
2
|
+
varying vUV: vec2f;
|
|
3
|
+
varying vColor: vec4f;
|
|
4
|
+
|
|
5
|
+
#ifdef TILE_BLEND
|
|
6
|
+
varying vUV2: vec2f;
|
|
7
|
+
varying vTileBlend: f32;
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
#ifdef USE_MAP
|
|
11
|
+
var mapSampler: sampler;
|
|
12
|
+
var map: texture_2d<f32>;
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#ifdef SOFT_PARTICLES
|
|
16
|
+
var depthTextureSampler: sampler;
|
|
17
|
+
var depthTexture: texture_2d<f32>;
|
|
18
|
+
uniform softParams: vec2f;
|
|
19
|
+
uniform projParams: vec4f;
|
|
20
|
+
varying projPosition: vec4f;
|
|
21
|
+
varying linearDepth: f32;
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
#ifdef USE_ALPHATEST
|
|
25
|
+
uniform alphaTest: f32;
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
@fragment
|
|
29
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
30
|
+
var baseColor = fragmentInputs.vColor;
|
|
31
|
+
|
|
32
|
+
#ifdef USE_MAP
|
|
33
|
+
var texColor = textureSample(map, mapSampler, fragmentInputs.vUV);
|
|
34
|
+
#ifdef TILE_BLEND
|
|
35
|
+
let texColor2 = textureSample(map, mapSampler, fragmentInputs.vUV2);
|
|
36
|
+
texColor = mix(texColor, texColor2, fragmentInputs.vTileBlend);
|
|
37
|
+
#endif
|
|
38
|
+
baseColor *= texColor;
|
|
39
|
+
#endif
|
|
40
|
+
|
|
41
|
+
#ifdef USE_COLOR_AS_ALPHA
|
|
42
|
+
baseColor.a *= (baseColor.r + baseColor.g + baseColor.b) / 3.0;
|
|
43
|
+
#endif
|
|
44
|
+
|
|
45
|
+
#ifdef USE_ALPHATEST
|
|
46
|
+
if (baseColor.a < uniforms.alphaTest) { discard; }
|
|
47
|
+
#else
|
|
48
|
+
if (baseColor.a < 0.01) { discard; }
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
#ifdef SOFT_PARTICLES
|
|
52
|
+
var p2 = fragmentInputs.projPosition.xy / fragmentInputs.projPosition.w;
|
|
53
|
+
p2 = 0.5 * p2 + 0.5;
|
|
54
|
+
let readDepth = textureSample(depthTexture, depthTextureSampler, p2).r;
|
|
55
|
+
let zNear = uniforms.projParams.x;
|
|
56
|
+
let zFar = uniforms.projParams.y;
|
|
57
|
+
let viewDepth = (zFar * zNear) / (zFar - readDepth * (zFar - zNear));
|
|
58
|
+
let fade = clamp(uniforms.softParams.y * ((viewDepth - uniforms.softParams.x) - fragmentInputs.linearDepth), 0.0, 1.0);
|
|
59
|
+
baseColor *= fade;
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
fragmentOutputs.color = baseColor;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
@@ -2,6 +2,7 @@ export default /* glsl */ `
|
|
|
2
2
|
varying vec2 vUV;
|
|
3
3
|
varying vec4 vColor;
|
|
4
4
|
varying vec3 vNormal;
|
|
5
|
+
varying vec3 vWorldPos;
|
|
5
6
|
|
|
6
7
|
uniform vec3 lightDirection;
|
|
7
8
|
uniform vec3 lightColor;
|
|
@@ -16,6 +17,52 @@ varying float vTileBlend;
|
|
|
16
17
|
uniform sampler2D map;
|
|
17
18
|
#endif
|
|
18
19
|
|
|
20
|
+
// One 3×2 atlas (px py pz / nx ny nz). Avoids samplerCube and multi-face
|
|
21
|
+
// sampler bindings that raise GL_INVALID_OPERATION on iOS WebKit.
|
|
22
|
+
#ifdef USE_ENVMAP_ATLAS
|
|
23
|
+
uniform sampler2D envAtlas;
|
|
24
|
+
uniform vec3 eyePosition;
|
|
25
|
+
uniform float reflectionLevel;
|
|
26
|
+
|
|
27
|
+
vec3 sampleEnvAtlas(vec3 r) {
|
|
28
|
+
vec3 a = abs(r);
|
|
29
|
+
vec2 localUV;
|
|
30
|
+
float cellX;
|
|
31
|
+
float cellY;
|
|
32
|
+
// Atlas layout: +X +Y +Z on row 0, -X -Y -Z on row 1.
|
|
33
|
+
if (a.x >= a.y && a.x >= a.z) {
|
|
34
|
+
cellX = 0.0;
|
|
35
|
+
if (r.x >= 0.0) {
|
|
36
|
+
cellY = 0.0;
|
|
37
|
+
localUV = vec2(-r.z, -r.y) / a.x * 0.5 + 0.5;
|
|
38
|
+
} else {
|
|
39
|
+
cellY = 1.0;
|
|
40
|
+
localUV = vec2(r.z, -r.y) / a.x * 0.5 + 0.5;
|
|
41
|
+
}
|
|
42
|
+
} else if (a.y >= a.z) {
|
|
43
|
+
cellX = 1.0;
|
|
44
|
+
if (r.y >= 0.0) {
|
|
45
|
+
cellY = 0.0;
|
|
46
|
+
localUV = vec2(r.x, r.z) / a.y * 0.5 + 0.5;
|
|
47
|
+
} else {
|
|
48
|
+
cellY = 1.0;
|
|
49
|
+
localUV = vec2(r.x, -r.z) / a.y * 0.5 + 0.5;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
cellX = 2.0;
|
|
53
|
+
if (r.z >= 0.0) {
|
|
54
|
+
cellY = 0.0;
|
|
55
|
+
localUV = vec2(r.x, -r.y) / a.z * 0.5 + 0.5;
|
|
56
|
+
} else {
|
|
57
|
+
cellY = 1.0;
|
|
58
|
+
localUV = vec2(-r.x, -r.y) / a.z * 0.5 + 0.5;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
vec2 uv = (vec2(cellX, cellY) + clamp(localUV, 0.0, 1.0)) / vec2(3.0, 2.0);
|
|
62
|
+
return texture2D(envAtlas, uv).rgb;
|
|
63
|
+
}
|
|
64
|
+
#endif
|
|
65
|
+
|
|
19
66
|
#ifdef SOFT_PARTICLES
|
|
20
67
|
uniform sampler2D depthTexture;
|
|
21
68
|
uniform vec2 softParams;
|
|
@@ -51,7 +98,15 @@ void main() {
|
|
|
51
98
|
float NdotL = max(dot(N, L), 0.0);
|
|
52
99
|
vec3 litColor = ambientColor + lightColor * NdotL;
|
|
53
100
|
|
|
54
|
-
|
|
101
|
+
vec3 color = baseColor.rgb * litColor;
|
|
102
|
+
|
|
103
|
+
#ifdef USE_ENVMAP_ATLAS
|
|
104
|
+
vec3 viewDir = normalize(vWorldPos - eyePosition);
|
|
105
|
+
vec3 reflectionCoords = reflect(viewDir, N);
|
|
106
|
+
color += sampleEnvAtlas(reflectionCoords) * reflectionLevel;
|
|
107
|
+
#endif
|
|
108
|
+
|
|
109
|
+
gl_FragColor = vec4(color, baseColor.a);
|
|
55
110
|
|
|
56
111
|
#ifdef SOFT_PARTICLES
|
|
57
112
|
vec2 p2 = projPosition.xy / projPosition.w;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export default /* wgsl */ `
|
|
2
|
+
varying vUV: vec2f;
|
|
3
|
+
varying vColor: vec4f;
|
|
4
|
+
varying vNormal: vec3f;
|
|
5
|
+
varying vWorldPos: vec3f;
|
|
6
|
+
|
|
7
|
+
uniform lightDirection: vec3f;
|
|
8
|
+
uniform lightColor: vec3f;
|
|
9
|
+
uniform ambientColor: vec3f;
|
|
10
|
+
|
|
11
|
+
#ifdef TILE_BLEND
|
|
12
|
+
varying vUV2: vec2f;
|
|
13
|
+
varying vTileBlend: f32;
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
#ifdef USE_MAP
|
|
17
|
+
var mapSampler: sampler;
|
|
18
|
+
var map: texture_2d<f32>;
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
#ifdef USE_ENVMAP_ATLAS
|
|
22
|
+
var envAtlasSampler: sampler;
|
|
23
|
+
var envAtlas: texture_2d<f32>;
|
|
24
|
+
uniform eyePosition: vec3f;
|
|
25
|
+
uniform reflectionLevel: f32;
|
|
26
|
+
|
|
27
|
+
fn sampleEnvAtlas(r: vec3f) -> vec3f {
|
|
28
|
+
let a = abs(r);
|
|
29
|
+
var localUV: vec2f;
|
|
30
|
+
var cellX: f32;
|
|
31
|
+
var cellY: f32;
|
|
32
|
+
if (a.x >= a.y && a.x >= a.z) {
|
|
33
|
+
cellX = 0.0;
|
|
34
|
+
if (r.x >= 0.0) {
|
|
35
|
+
cellY = 0.0;
|
|
36
|
+
localUV = vec2f(-r.z, -r.y) / a.x * 0.5 + 0.5;
|
|
37
|
+
} else {
|
|
38
|
+
cellY = 1.0;
|
|
39
|
+
localUV = vec2f(r.z, -r.y) / a.x * 0.5 + 0.5;
|
|
40
|
+
}
|
|
41
|
+
} else if (a.y >= a.z) {
|
|
42
|
+
cellX = 1.0;
|
|
43
|
+
if (r.y >= 0.0) {
|
|
44
|
+
cellY = 0.0;
|
|
45
|
+
localUV = vec2f(r.x, r.z) / a.y * 0.5 + 0.5;
|
|
46
|
+
} else {
|
|
47
|
+
cellY = 1.0;
|
|
48
|
+
localUV = vec2f(r.x, -r.z) / a.y * 0.5 + 0.5;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
cellX = 2.0;
|
|
52
|
+
if (r.z >= 0.0) {
|
|
53
|
+
cellY = 0.0;
|
|
54
|
+
localUV = vec2f(r.x, -r.y) / a.z * 0.5 + 0.5;
|
|
55
|
+
} else {
|
|
56
|
+
cellY = 1.0;
|
|
57
|
+
localUV = vec2f(-r.x, -r.y) / a.z * 0.5 + 0.5;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let uv = (vec2f(cellX, cellY) + clamp(localUV, vec2f(0.0), vec2f(1.0))) / vec2f(3.0, 2.0);
|
|
61
|
+
return textureSample(envAtlas, envAtlasSampler, uv).rgb;
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
#ifdef SOFT_PARTICLES
|
|
66
|
+
var depthTextureSampler: sampler;
|
|
67
|
+
var depthTexture: texture_2d<f32>;
|
|
68
|
+
uniform softParams: vec2f;
|
|
69
|
+
uniform projParams: vec4f;
|
|
70
|
+
varying projPosition: vec4f;
|
|
71
|
+
varying linearDepth: f32;
|
|
72
|
+
#endif
|
|
73
|
+
|
|
74
|
+
#ifdef USE_ALPHATEST
|
|
75
|
+
uniform alphaTest: f32;
|
|
76
|
+
#endif
|
|
77
|
+
|
|
78
|
+
@fragment
|
|
79
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
80
|
+
var baseColor = fragmentInputs.vColor;
|
|
81
|
+
|
|
82
|
+
#ifdef USE_MAP
|
|
83
|
+
var texColor = textureSample(map, mapSampler, fragmentInputs.vUV);
|
|
84
|
+
#ifdef TILE_BLEND
|
|
85
|
+
let texColor2 = textureSample(map, mapSampler, fragmentInputs.vUV2);
|
|
86
|
+
texColor = mix(texColor, texColor2, fragmentInputs.vTileBlend);
|
|
87
|
+
#endif
|
|
88
|
+
baseColor *= texColor;
|
|
89
|
+
#endif
|
|
90
|
+
|
|
91
|
+
#ifdef USE_ALPHATEST
|
|
92
|
+
if (baseColor.a < uniforms.alphaTest) { discard; }
|
|
93
|
+
#else
|
|
94
|
+
if (baseColor.a < 0.01) { discard; }
|
|
95
|
+
#endif
|
|
96
|
+
|
|
97
|
+
let N = normalize(fragmentInputs.vNormal);
|
|
98
|
+
let L = normalize(-uniforms.lightDirection);
|
|
99
|
+
let NdotL = max(dot(N, L), 0.0);
|
|
100
|
+
let litColor = uniforms.ambientColor + uniforms.lightColor * NdotL;
|
|
101
|
+
|
|
102
|
+
var color = baseColor.rgb * litColor;
|
|
103
|
+
|
|
104
|
+
#ifdef USE_ENVMAP_ATLAS
|
|
105
|
+
let viewDir = normalize(fragmentInputs.vWorldPos - uniforms.eyePosition);
|
|
106
|
+
let reflectionCoords = reflect(viewDir, N);
|
|
107
|
+
color += sampleEnvAtlas(reflectionCoords) * uniforms.reflectionLevel;
|
|
108
|
+
#endif
|
|
109
|
+
|
|
110
|
+
var finalColor = vec4f(color, baseColor.a);
|
|
111
|
+
|
|
112
|
+
#ifdef SOFT_PARTICLES
|
|
113
|
+
var p2 = fragmentInputs.projPosition.xy / fragmentInputs.projPosition.w;
|
|
114
|
+
p2 = 0.5 * p2 + 0.5;
|
|
115
|
+
let readDepth = textureSample(depthTexture, depthTextureSampler, p2).r;
|
|
116
|
+
let zNear = uniforms.projParams.x;
|
|
117
|
+
let zFar = uniforms.projParams.y;
|
|
118
|
+
let viewDepth = (zFar * zNear) / (zFar - readDepth * (zFar - zNear));
|
|
119
|
+
let fade = clamp(uniforms.softParams.y * ((viewDepth - uniforms.softParams.x) - fragmentInputs.linearDepth), 0.0, 1.0);
|
|
120
|
+
finalColor *= fade;
|
|
121
|
+
#endif
|
|
122
|
+
|
|
123
|
+
fragmentOutputs.color = finalColor;
|
|
124
|
+
}
|
|
125
|
+
`;
|