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/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: IParticleSystem[] = [];
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.push(system);
102
+ visibleSystems[count++] = system;
88
103
  }
89
104
  }
105
+ visibleSystems.length = count;
90
106
  return visibleSystems;
91
107
  }
92
108
 
@@ -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 {BatchedRenderer} from './BatchedRenderer';
6
- export type {VFXBatchSettings, AdaptivePerformanceOptions, AdaptivePerformanceState} from './BatchedRenderer';
5
+ export * from 'quarks.core';
7
6
  export {BatchedParticleRenderer} from './BatchedParticleRenderer';
8
- export {VFXBatch, RenderMode} from './VFXBatch';
9
- export type {StoredBatchSettings} from './VFXBatch';
10
- export {SpriteBatch} from './SpriteBatch';
11
- export {TrailBatch} from './TrailBatch';
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
- ParticleSystemParameters,
21
+ BabylonMetaData,
15
22
  BurstParameters,
16
23
  BurstParametersJSON,
17
24
  ParticleSystemJSONParameters,
18
- BabylonMetaData,
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 {MeshSurfaceEmitter, MeshSurfaceEmitterPlugin} from './MeshSurfaceEmitter';
31
+ export {QuarksUtil} from './QuarksUtil';
27
32
  export * from './shaders/';
28
- export * from './materials/';
29
- export * from 'quarks.core';
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
- IParticleSystem,
35
- IParticle,
36
- Particle,
37
- EmissionState,
38
- RendererEmitterSettings,
39
- TrailSettings,
40
- StretchedBillBoardSettings,
40
+ Behavior,
41
41
  BillBoardSettings,
42
- MeshSettings,
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
- Behavior,
48
- EmitterShape,
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
 
@@ -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
- gl_FragColor = vec4(baseColor.rgb * litColor, baseColor.a);
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
+ `;