sparcoon 0.6.1 → 0.7.1
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/dist/emitter/FXEmitter.d.ts +4 -0
- package/dist/emitter/FXEmitter.js +8 -0
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +0 -2
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +0 -2
- package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +0 -4
- package/dist/emitter/behavior/FXBehaviorPointForce.js +0 -4
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +4 -3
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +9 -3
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +0 -1
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +0 -1
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +0 -2
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +0 -2
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +4 -2
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +9 -2
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +0 -1
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +0 -1
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +0 -2
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +0 -2
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +4 -2
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +9 -2
- package/dist/emitter/spawn/FXSpawnBox.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnBox.js +0 -1
- package/dist/emitter/spawn/FXSpawnLifetime.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnLifetime.js +0 -1
- package/dist/emitter/spawn/FXSpawnOffset.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnOffset.js +0 -1
- package/dist/emitter/spawn/FXSpawnPoint.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnPoint.js +0 -1
- package/dist/emitter/spawn/FXSpawnRotation.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnRotation.js +0 -1
- package/dist/emitter/spawn/FXSpawnScale.d.ts +0 -2
- package/dist/emitter/spawn/FXSpawnScale.js +0 -2
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +3 -10
- package/dist/emitter/spawn/FXSpawnSphere.js +8 -15
- package/dist/emitter/spawn/FXSpawnTorque.d.ts +0 -1
- package/dist/emitter/spawn/FXSpawnTorque.js +0 -1
- package/dist/emitter/spawn/FXSpawnVelocity.d.ts +3 -6
- package/dist/emitter/spawn/FXSpawnVelocity.js +6 -9
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/instancedParticle/FXInstancedParticle.d.ts +1 -0
- package/dist/instancedParticle/FXInstancedParticle.js +4 -1
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +4 -3
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +49 -63
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +1 -1
- package/dist/materials/FXMaterial/FXBlending.Internal.d.ts +24 -0
- package/dist/materials/FXMaterial/FXBlending.Internal.js +64 -0
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.js +10 -4
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +9 -4
- package/dist/materials/FXMaterial/FXMaterial.d.ts +18 -15
- package/dist/materials/FXMaterial/FXMaterial.js +22 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +3 -2
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.js +18 -14
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +1 -1
- package/dist/nodes/FXNode.d.ts +5 -0
- package/dist/nodes/FXNode.js +10 -0
- package/dist/nodes/blending/FXNodeBlending.d.ts +6 -0
- package/dist/nodes/blending/FXNodeBlending.js +9 -0
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.d.ts +16 -0
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.js +49 -0
- package/dist/nodes/color/FXNodeColor.d.ts +2 -3
- package/dist/nodes/color/FXNodeColor.js +3 -5
- package/dist/nodes/color/FXNodeColorOverLife.d.ts +0 -5
- package/dist/nodes/color/FXNodeColorOverLife.js +6 -8
- package/dist/nodes/color/FXNodeSphericalClip.js +6 -3
- package/dist/nodes/normal/FXNodeFlatNormal.d.ts +0 -1
- package/dist/nodes/normal/FXNodeFlatNormal.js +5 -5
- package/dist/nodes/normal/FXNodeNormal.d.ts +2 -3
- package/dist/nodes/normal/FXNodeNormal.js +3 -1
- package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +0 -1
- package/dist/nodes/normal/FXNodeSphericalNormal.js +11 -9
- package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +3 -3
- package/dist/nodes/texture/FXNodeAnimatedTexture.js +19 -17
- package/dist/nodes/texture/FXNodeStaticTexture.d.ts +3 -3
- package/dist/nodes/texture/FXNodeStaticTexture.js +21 -18
- package/dist/nodes/texture/FXNodeTexture.d.ts +2 -3
- package/dist/nodes/texture/FXNodeTexture.js +3 -5
- package/package.json +1 -1
|
@@ -27,6 +27,9 @@ class FXInstancedParticle extends Mesh {
|
|
|
27
27
|
get instanceCount() {
|
|
28
28
|
return this._private_instancedGeometry.instanceCount;
|
|
29
29
|
}
|
|
30
|
+
get instanceCapacity() {
|
|
31
|
+
return this._private_capacity;
|
|
32
|
+
}
|
|
30
33
|
createInstances(count) {
|
|
31
34
|
const currentInstanceCount = this._private_instancedGeometry.instanceCount;
|
|
32
35
|
this._private_ensureCapacity(currentInstanceCount + count);
|
|
@@ -151,7 +154,7 @@ class FXInstancedParticle extends Mesh {
|
|
|
151
154
|
//
|
|
152
155
|
// _maxInstanceCount is an internal renderer cache field set by
|
|
153
156
|
// WebGLBindingStates.js. It was never promoted to a public API in the
|
|
154
|
-
// r157
|
|
157
|
+
// r157-r180 range. Deleting it is the standard workaround used across
|
|
155
158
|
// the Three.js ecosystem (see #19706, #26363, #27205).
|
|
156
159
|
//
|
|
157
160
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { Blending } from "three";
|
|
2
1
|
import { Color, MeshLambertMaterial } from "three";
|
|
3
2
|
import type { GLTypeInfo } from "../../instancedParticle/shared";
|
|
4
|
-
import type {
|
|
3
|
+
import type { FXNodeBlending } from "../../nodes/blending/FXNodeBlending";
|
|
4
|
+
import { FXNodeColor } from "../../nodes/color/FXNodeColor";
|
|
5
5
|
import type { FXNodeNormal } from "../../nodes/normal/FXNodeNormal";
|
|
6
6
|
import { FXNodeTexture } from "../../nodes/texture/FXNodeTexture";
|
|
7
|
+
import type { FXBlending } from "../FXMaterial/FXMaterial";
|
|
7
8
|
export interface FXScatterUniforms {
|
|
8
9
|
u_ScatterTint: {
|
|
9
10
|
value: Color;
|
|
@@ -18,6 +19,6 @@ export interface FXScatterUniforms {
|
|
|
18
19
|
value: number;
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
export declare function buildFXDiffuseMaterial(varyings: Record<string, GLTypeInfo>, blending:
|
|
22
|
+
export declare function buildFXDiffuseMaterial(varyings: Record<string, GLTypeInfo>, blending: FXBlending, useAlphaHashing: boolean, alphaTest: number, albedoNodes: readonly (FXNodeColor | FXNodeTexture | FXNodeBlending)[], normalNodes: readonly (FXNodeTexture | FXNodeNormal)[], emissionNodes: readonly (FXNodeColor | FXNodeTexture)[], scatterUniforms?: FXScatterUniforms, shadowSensitivity?: {
|
|
22
23
|
value: number;
|
|
23
24
|
}): MeshLambertMaterial;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { MeshLambertMaterial, Color, DoubleSide } from 'three';
|
|
1
|
+
import { MeshLambertMaterial, Color, DoubleSide, NormalBlending } from 'three';
|
|
2
2
|
import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { FXNodeColor } from '../../nodes/color/FXNodeColor.js';
|
|
4
|
+
import { CURRENT_EXPRESSION_VALUE_PLACEHOLDER } from '../../nodes/FXNode.js';
|
|
3
5
|
import { FXNodeTexture } from '../../nodes/texture/FXNodeTexture.js';
|
|
6
|
+
import { buildAlbedoAndBlendingCode } from '../FXMaterial/FXBlending.Internal.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Wraps every `getShadow(...)` call in the shader source with
|
|
@@ -37,7 +40,7 @@ function wrapShadowCalls(source, uniformName) {
|
|
|
37
40
|
}
|
|
38
41
|
return result;
|
|
39
42
|
}
|
|
40
|
-
function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
43
|
+
function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest, albedoNodes, normalNodes, emissionNodes, scatterUniforms, shadowSensitivity) {
|
|
41
44
|
const useScatter = scatterUniforms !== undefined;
|
|
42
45
|
const useShadowSensitivity = shadowSensitivity !== undefined;
|
|
43
46
|
const useNormals = normalNodes.length > 0;
|
|
@@ -56,12 +59,12 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
|
56
59
|
depthWrite: useAlphaHashing,
|
|
57
60
|
depthTest: true,
|
|
58
61
|
alphaHash: useAlphaHashing,
|
|
59
|
-
blending,
|
|
62
|
+
blending: NormalBlending,
|
|
60
63
|
side: DoubleSide,
|
|
61
64
|
forceSinglePass: true,
|
|
62
65
|
alphaTest,
|
|
63
66
|
emissive: emissionNodes.length > 0 ? new Color(1, 1, 1) : new Color(0, 0, 0),
|
|
64
|
-
premultipliedAlpha,
|
|
67
|
+
premultipliedAlpha: true,
|
|
65
68
|
});
|
|
66
69
|
material.onBeforeCompile = (shader) => {
|
|
67
70
|
const vertexPreamble = [
|
|
@@ -117,25 +120,16 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
|
117
120
|
);
|
|
118
121
|
#endif
|
|
119
122
|
`);
|
|
120
|
-
const blendReorientedNormalsHelper = normalNodes.length > 1
|
|
121
|
-
? `
|
|
122
|
-
vec3 blendReorientedNormals(vec3 base, vec3 detail) {
|
|
123
|
-
base += vec3(0.0, 0.0, 1.0);
|
|
124
|
-
detail *= vec3(-1.0, -1.0, 1.0);
|
|
125
|
-
return normalize(base * dot(base, detail) / base.z - detail);
|
|
126
|
-
}
|
|
127
|
-
`
|
|
128
|
-
: "";
|
|
129
123
|
const seenCacheKeys = new Set();
|
|
130
124
|
const uniqueHelpers = (nodes) => nodes
|
|
131
|
-
.filter((
|
|
132
|
-
if (seenCacheKeys.has(
|
|
125
|
+
.filter((node) => {
|
|
126
|
+
if (seenCacheKeys.has(node.cacheKey)) {
|
|
133
127
|
return false;
|
|
134
128
|
}
|
|
135
|
-
seenCacheKeys.add(
|
|
129
|
+
seenCacheKeys.add(node.cacheKey);
|
|
136
130
|
return true;
|
|
137
131
|
})
|
|
138
|
-
.map((
|
|
132
|
+
.map((node) => node.helperFunctions)
|
|
139
133
|
.join("\n");
|
|
140
134
|
const scatterDeclarations = useScatter
|
|
141
135
|
? [
|
|
@@ -160,12 +154,11 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
|
160
154
|
"varying vec2 p_uv;",
|
|
161
155
|
useNormals ? "varying vec2 p_billboardSinCos;" : "",
|
|
162
156
|
varyingDeclarations.join("\n"),
|
|
163
|
-
albedoNodes.flatMap((
|
|
157
|
+
albedoNodes.flatMap((node) => node.uniformDeclarations).join("\n"),
|
|
164
158
|
uniqueHelpers(albedoNodes),
|
|
165
|
-
normalNodes.flatMap((
|
|
159
|
+
normalNodes.flatMap((node) => node.uniformDeclarations).join("\n"),
|
|
166
160
|
uniqueHelpers(normalNodes),
|
|
167
|
-
|
|
168
|
-
emissionNodes.flatMap((n) => n.uniformDeclarations).join("\n"),
|
|
161
|
+
emissionNodes.flatMap((node) => node.uniformDeclarations).join("\n"),
|
|
169
162
|
uniqueHelpers(emissionNodes),
|
|
170
163
|
...scatterDeclarations,
|
|
171
164
|
...shadowSensitivityDeclarations,
|
|
@@ -190,42 +183,32 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
|
190
183
|
}
|
|
191
184
|
let fragmentShader = shader.fragmentShader;
|
|
192
185
|
if (useNormals) {
|
|
193
|
-
const
|
|
194
|
-
if (node instanceof FXNodeTexture) {
|
|
195
|
-
return `normalize(${node.colorExpression}.rgb * 2.0 - 1.0)`;
|
|
196
|
-
}
|
|
197
|
-
return node.normalExpression;
|
|
198
|
-
});
|
|
199
|
-
const blendLines = normalExpressions
|
|
200
|
-
.slice(1)
|
|
201
|
-
.map((expression) => `normal = blendReorientedNormals(normal, ${expression});`);
|
|
202
|
-
const normalCode = [
|
|
186
|
+
const normalLines = [
|
|
203
187
|
"float faceDirection = gl_FrontFacing ? 1.0 : -1.0;",
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"",
|
|
217
|
-
"normal *= faceDirection;",
|
|
218
|
-
].join("\n");
|
|
219
|
-
fragmentShader = fragmentShader.replace("#include <normal_fragment_begin>", normalCode);
|
|
188
|
+
"vec3 fxNormal = vec3(0.0, 0.0, 1.0);",
|
|
189
|
+
];
|
|
190
|
+
for (const node of normalNodes) {
|
|
191
|
+
if (node instanceof FXNodeTexture || node instanceof FXNodeColor) {
|
|
192
|
+
normalLines.push(`fxNormal = normalize((${node.colorExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "vec4(fxNormal, 1.0)")}).rgb * 2.0 - 1.0);`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
normalLines.push(`fxNormal = ${node.normalExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxNormal")};`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
normalLines.push("", "float sinR = p_billboardSinCos.x;", "float cosR = p_billboardSinCos.y;", "vec3 normal = vec3(", " fxNormal.x * cosR - fxNormal.y * sinR,", " fxNormal.x * sinR + fxNormal.y * cosR,", " fxNormal.z", ");", "", "normal *= faceDirection;");
|
|
199
|
+
fragmentShader = fragmentShader.replace("#include <normal_fragment_begin>", normalLines.join("\n"));
|
|
220
200
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
fragmentShader = fragmentShader.replace("#include <map_fragment>", `diffuseColor = ${combinedExpression}; ${discardLine}`);
|
|
201
|
+
const albedoCode = buildAlbedoAndBlendingCode(albedoNodes, blending, useAlphaHashing);
|
|
202
|
+
if (albedoCode.mapFragment !== null) {
|
|
203
|
+
fragmentShader = fragmentShader.replace("#include <map_fragment>", albedoCode.mapFragment);
|
|
225
204
|
}
|
|
226
205
|
if (emissionNodes.length > 0) {
|
|
227
|
-
const
|
|
228
|
-
|
|
206
|
+
const emissiveLines = ["vec4 fxEmissive = vec4(1.0);"];
|
|
207
|
+
for (const node of emissionNodes) {
|
|
208
|
+
emissiveLines.push(`fxEmissive = ${node.colorExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxEmissive")};`);
|
|
209
|
+
}
|
|
210
|
+
emissiveLines.push("totalEmissiveRadiance = fxEmissive.rgb;");
|
|
211
|
+
fragmentShader = fragmentShader.replace("#include <emissivemap_fragment>", emissiveLines.join("\n"));
|
|
229
212
|
}
|
|
230
213
|
if (useScatter) {
|
|
231
214
|
fragmentShader = fragmentShader.replace("#include <opaque_fragment>", `
|
|
@@ -271,18 +254,21 @@ function buildFXDiffuseMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
|
271
254
|
if (useShadowSensitivity) {
|
|
272
255
|
fragmentShader = wrapShadowCalls(fragmentShader, "u_shadowSensitivity");
|
|
273
256
|
}
|
|
257
|
+
fragmentShader = fragmentShader.replace("#include <premultiplied_alpha_fragment>", albedoCode.premultChunk);
|
|
274
258
|
shader.fragmentShader = fragmentPreamble + "\n" + fragmentShader;
|
|
275
259
|
};
|
|
276
|
-
const scatterKey = useScatter ? `scatter(${scatterUniforms.u_ScatterPower.value})` : "no-scatter";
|
|
277
|
-
const shadowKey = useShadowSensitivity ? "shadow-sens" : "no-shadow-sens";
|
|
278
|
-
material.customProgramCacheKey = () =>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
260
|
+
// const scatterKey = useScatter ? `scatter(${scatterUniforms.u_ScatterPower.value})` : "no-scatter";
|
|
261
|
+
// const shadowKey = useShadowSensitivity ? "shadow-sens" : "no-shadow-sens";
|
|
262
|
+
// material.customProgramCacheKey = (): string =>
|
|
263
|
+
// [
|
|
264
|
+
// "fx",
|
|
265
|
+
// albedoNodes.map((node) => node.cacheKey).join("-") || "none",
|
|
266
|
+
// normalNodes.map((node) => node.cacheKey).join("-") || "none",
|
|
267
|
+
// emissionNodes.map((node) => node.cacheKey).join("-") || "none",
|
|
268
|
+
// FXBlending[blending],
|
|
269
|
+
// scatterKey,
|
|
270
|
+
// shadowKey,
|
|
271
|
+
// ].join("_");
|
|
286
272
|
return material;
|
|
287
273
|
}
|
|
288
274
|
|
|
@@ -41,7 +41,7 @@ class FXDiffuseMaterial extends FXMaterial {
|
|
|
41
41
|
}
|
|
42
42
|
/** @internal */
|
|
43
43
|
buildThreeMaterial(varyings) {
|
|
44
|
-
return buildFXDiffuseMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.
|
|
44
|
+
return buildFXDiffuseMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.albedoNodes, this._private_normalNodes, this._private_emissionNodes, this._private_scatterUniforms, this._private_shadowSensitivity);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FXNodeBlending } from "../../nodes/blending/FXNodeBlending";
|
|
2
|
+
import type { FXNodeColor } from "../../nodes/color/FXNodeColor";
|
|
3
|
+
import type { FXNodeTexture } from "../../nodes/texture/FXNodeTexture";
|
|
4
|
+
import { FXBlending } from "./FXMaterial";
|
|
5
|
+
export interface AlbedoBlendingCode {
|
|
6
|
+
/** Replacement for #include <map_fragment>. null means: leave the include as-is */
|
|
7
|
+
mapFragment: string | null;
|
|
8
|
+
/** Replacement for #include <premultiplied_alpha_fragment> */
|
|
9
|
+
premultChunk: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Processes albedoNodes in order to produce shader code for both the color assignment
|
|
13
|
+
* and the per-pixel blending mode.
|
|
14
|
+
*
|
|
15
|
+
* Nodes are composed sequentially: each node receives the current accumulated value via
|
|
16
|
+
* EXPRESSION_CURRENT_VALUE_PLACEHOLDER and returns a new value. Nodes that do not use
|
|
17
|
+
* the placeholder act as sources and replace the current value entirely.
|
|
18
|
+
*
|
|
19
|
+
* When a FXNodeBlending appears, it receives both the current mask (via
|
|
20
|
+
* EXPRESSION_CURRENT_VALUE_PLACEHOLDER) and the current color (via
|
|
21
|
+
* EXPRESSION_CURRENT_COLOR_PLACEHOLDER), and returns the new mask value. Color nodes
|
|
22
|
+
* that follow a blending node do not affect the mask.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildAlbedoAndBlendingCode(albedoNodes: readonly (FXNodeColor | FXNodeTexture | FXNodeBlending)[], blending: FXBlending, useAlphaHashing: boolean): AlbedoBlendingCode;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CURRENT_EXPRESSION_VALUE_PLACEHOLDER, BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER } from '../../nodes/FXNode.js';
|
|
2
|
+
import { FXNodeBlending } from '../../nodes/blending/FXNodeBlending.js';
|
|
3
|
+
import { FXBlending } from './FXMaterial.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Processes albedoNodes in order to produce shader code for both the color assignment
|
|
7
|
+
* and the per-pixel blending mode.
|
|
8
|
+
*
|
|
9
|
+
* Nodes are composed sequentially: each node receives the current accumulated value via
|
|
10
|
+
* EXPRESSION_CURRENT_VALUE_PLACEHOLDER and returns a new value. Nodes that do not use
|
|
11
|
+
* the placeholder act as sources and replace the current value entirely.
|
|
12
|
+
*
|
|
13
|
+
* When a FXNodeBlending appears, it receives both the current mask (via
|
|
14
|
+
* EXPRESSION_CURRENT_VALUE_PLACEHOLDER) and the current color (via
|
|
15
|
+
* EXPRESSION_CURRENT_COLOR_PLACEHOLDER), and returns the new mask value. Color nodes
|
|
16
|
+
* that follow a blending node do not affect the mask.
|
|
17
|
+
*/
|
|
18
|
+
function buildAlbedoAndBlendingCode(albedoNodes, blending, useAlphaHashing) {
|
|
19
|
+
if (albedoNodes.length === 0) {
|
|
20
|
+
return { mapFragment: null, premultChunk: buildPremultChunk(blending, false) };
|
|
21
|
+
}
|
|
22
|
+
const hasBlendingNodes = albedoNodes.some((node) => node instanceof FXNodeBlending);
|
|
23
|
+
const lines = [];
|
|
24
|
+
lines.push("vec4 fxAlbedo = vec4(1.0);");
|
|
25
|
+
if (hasBlendingNodes) {
|
|
26
|
+
lines.push("float fxBlendMask = 1.0;");
|
|
27
|
+
}
|
|
28
|
+
for (const node of albedoNodes) {
|
|
29
|
+
if (node instanceof FXNodeBlending) {
|
|
30
|
+
lines.push(`fxBlendMask = ${node.blendingExpression
|
|
31
|
+
.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxBlendMask")
|
|
32
|
+
.replace(BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER, "fxAlbedo")};`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push(`fxAlbedo = ${node.colorExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxAlbedo")};`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const discardLine = useAlphaHashing ? "" : "if (diffuseColor.a < 0.0035) discard;";
|
|
39
|
+
lines.push(`diffuseColor = fxAlbedo; ${discardLine}`);
|
|
40
|
+
return {
|
|
41
|
+
mapFragment: lines.join("\n"),
|
|
42
|
+
premultChunk: buildPremultChunk(blending, hasBlendingNodes),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function buildPremultChunk(blending, hasBlendingNodes) {
|
|
46
|
+
if (!hasBlendingNodes) {
|
|
47
|
+
return `gl_FragColor.rgb *= gl_FragColor.a;`;
|
|
48
|
+
}
|
|
49
|
+
if (blending === FXBlending.MASKED_ADDITIVE) {
|
|
50
|
+
return `
|
|
51
|
+
gl_FragColor.rgb *= gl_FragColor.a;
|
|
52
|
+
gl_FragColor.a *= (1.0 - clamp(fxBlendMask, 0.0, 1.0));
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
// MASKED_MULTIPLY: dark areas darken destination, bright areas normal-blend
|
|
56
|
+
return `
|
|
57
|
+
gl_FragColor.rgb *= gl_FragColor.a;
|
|
58
|
+
float fxLumPremult = dot(gl_FragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
59
|
+
vec4 fxMultiply = vec4(0.0, 0.0, 0.0, gl_FragColor.a - fxLumPremult);
|
|
60
|
+
gl_FragColor = mix(gl_FragColor, fxMultiply, clamp(fxBlendMask, 0.0, 1.0));
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { buildAlbedoAndBlendingCode };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MeshDepthMaterial, DoubleSide, RGBADepthPacking } from 'three';
|
|
2
2
|
import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { CURRENT_EXPRESSION_VALUE_PLACEHOLDER } from '../../nodes/FXNode.js';
|
|
3
4
|
|
|
4
5
|
const DEPTH_VERTEX_SHADER = `
|
|
5
6
|
#define PARTICLE_POSITION_X a_builtin[0][0]
|
|
@@ -116,9 +117,13 @@ function buildDepthMaterial(albedoNodes, depthAlphaTest, useDepthAlphaHash, useS
|
|
|
116
117
|
}
|
|
117
118
|
let fragmentShader = shader.fragmentShader;
|
|
118
119
|
if (depthNodes.length > 0) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
const albedoLines = ["vec4 fxDepthAlbedo = vec4(1.0);"];
|
|
121
|
+
for (const node of depthNodes) {
|
|
122
|
+
albedoLines.push(`fxDepthAlbedo = ${node.colorExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxDepthAlbedo")};`);
|
|
123
|
+
}
|
|
124
|
+
albedoLines.push(`diffuseColor.a = fxDepthAlbedo.a;`);
|
|
125
|
+
albedoLines.push(`if (diffuseColor.a < ${depthAlphaTest.toFixed(4)}) discard;`);
|
|
126
|
+
fragmentShader = fragmentShader.replace("#include <map_fragment>", albedoLines.join("\n"));
|
|
122
127
|
}
|
|
123
128
|
if (useSphericalDepth) {
|
|
124
129
|
if (!FRAG_COORD_Z_PATTERN.test(fragmentShader)) {
|
|
@@ -132,7 +137,8 @@ function buildDepthMaterial(albedoNodes, depthAlphaTest, useDepthAlphaHash, useS
|
|
|
132
137
|
}
|
|
133
138
|
shader.fragmentShader = fragmentPreamble + "\n" + fragmentShader;
|
|
134
139
|
};
|
|
135
|
-
material.customProgramCacheKey = ()
|
|
140
|
+
// material.customProgramCacheKey = (): string =>
|
|
141
|
+
// `fx-depth-${useDepthAlphaHash ? "alphaHash" : "alphaTest"}-${useSphericalDepth ? "spherical" : "flat"}-${depthNodes.map((node) => node.cacheKey).join("-") || "none"}`;
|
|
136
142
|
return material;
|
|
137
143
|
}
|
|
138
144
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MeshDistanceMaterial, DoubleSide } from 'three';
|
|
2
2
|
import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { CURRENT_EXPRESSION_VALUE_PLACEHOLDER } from '../../nodes/FXNode.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Custom vertex shader for point/spot light shadow maps.
|
|
@@ -134,9 +135,12 @@ function buildDistanceMaterial(albedoNodes, distanceAlphaTest, useDistanceAlphaH
|
|
|
134
135
|
// <map_fragment> depending on the Three.js version. If the chunk is
|
|
135
136
|
// absent, insert the alpha discard before the distance calculation.
|
|
136
137
|
if (distanceNodes.length > 0) {
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
`
|
|
138
|
+
const albedoLines = ["vec4 fxDistAlbedo = vec4(1.0);"];
|
|
139
|
+
for (const node of distanceNodes) {
|
|
140
|
+
albedoLines.push(`fxDistAlbedo = ${node.colorExpression.replace(CURRENT_EXPRESSION_VALUE_PLACEHOLDER, "fxDistAlbedo")};`);
|
|
141
|
+
}
|
|
142
|
+
albedoLines.push(`float nodeAlpha = fxDistAlbedo.a;`);
|
|
143
|
+
const alphaDiscardCode = albedoLines.join("\n") + `\nif (nodeAlpha < ${distanceAlphaTest.toFixed(4)}) discard;`;
|
|
140
144
|
if (fragmentShader.includes("#include <map_fragment>")) {
|
|
141
145
|
fragmentShader = fragmentShader.replace("#include <map_fragment>", alphaDiscardCode);
|
|
142
146
|
}
|
|
@@ -164,7 +168,8 @@ function buildDistanceMaterial(albedoNodes, distanceAlphaTest, useDistanceAlphaH
|
|
|
164
168
|
}
|
|
165
169
|
shader.fragmentShader = fragmentPreamble + "\n" + fragmentShader;
|
|
166
170
|
};
|
|
167
|
-
material.customProgramCacheKey = ()
|
|
171
|
+
// material.customProgramCacheKey = (): string =>
|
|
172
|
+
// `fx-distance-${useDistanceAlphaHash ? "alphaHash" : "alphaTest"}-${useSphericalDepth ? "spherical" : "flat"}-${distanceNodes.map((node) => node.cacheKey).join("-") || "none"}`;
|
|
168
173
|
return material;
|
|
169
174
|
}
|
|
170
175
|
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import { FXNodeColor } from "../../nodes/color/FXNodeColor";
|
|
2
|
+
import { FXNodeTexture } from "../../nodes/texture/FXNodeTexture";
|
|
3
|
+
/**
|
|
4
|
+
* Per-particle blend mode used when compositing particles into the scene
|
|
5
|
+
*/
|
|
6
|
+
export declare enum FXBlending {
|
|
7
|
+
/**
|
|
8
|
+
* Premultiplied additive blend - particle color is pre-multiplied by alpha and added to the
|
|
9
|
+
* destination with zero alpha contribution
|
|
10
|
+
*/
|
|
11
|
+
MASKED_ADDITIVE = 0,
|
|
12
|
+
/**
|
|
13
|
+
* Luminance-aware multiply blend - dark areas darken the destination while bright areas
|
|
14
|
+
* normal-blend through a mask
|
|
15
|
+
*/
|
|
16
|
+
MASKED_MULTIPLY = 1
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Configuration options shared by all {@link FXMaterial} subclasses
|
|
6
20
|
*/
|
|
@@ -11,12 +25,7 @@ export interface FXMaterialOptions {
|
|
|
11
25
|
* @defaultValue `[]`
|
|
12
26
|
*/
|
|
13
27
|
albedoNodes: (FXNodeColor | FXNodeTexture)[];
|
|
14
|
-
|
|
15
|
-
* Three.js blending mode
|
|
16
|
-
*
|
|
17
|
-
* @defaultValue `NormalBlending`
|
|
18
|
-
*/
|
|
19
|
-
blending: Blending;
|
|
28
|
+
blending: FXBlending;
|
|
20
29
|
/**
|
|
21
30
|
* Use alpha hashing instead of standard transparency
|
|
22
31
|
*
|
|
@@ -47,12 +56,6 @@ export interface FXMaterialOptions {
|
|
|
47
56
|
* @defaultValue `true`
|
|
48
57
|
*/
|
|
49
58
|
useSphericalDepth: boolean;
|
|
50
|
-
/**
|
|
51
|
-
* Use premultiplied alpha blending
|
|
52
|
-
*
|
|
53
|
-
* @defaultValue `true`
|
|
54
|
-
*/
|
|
55
|
-
premultipliedAlpha: boolean;
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
61
|
* Abstract base class for particle materials
|
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FXNodeColor } from '../../nodes/color/FXNodeColor.js';
|
|
2
|
+
import { FXNodeTexture } from '../../nodes/texture/FXNodeTexture.js';
|
|
2
3
|
import { buildDepthMaterial } from './FXDepthMaterial.Internal.js';
|
|
3
4
|
import { buildDistanceMaterial } from './FXDistanceMaterial.Internal.js';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Per-particle blend mode used when compositing particles into the scene
|
|
8
|
+
*/
|
|
9
|
+
var FXBlending;
|
|
10
|
+
(function (FXBlending) {
|
|
11
|
+
/**
|
|
12
|
+
* Premultiplied additive blend - particle color is pre-multiplied by alpha and added to the
|
|
13
|
+
* destination with zero alpha contribution
|
|
14
|
+
*/
|
|
15
|
+
FXBlending[FXBlending["MASKED_ADDITIVE"] = 0] = "MASKED_ADDITIVE";
|
|
16
|
+
/**
|
|
17
|
+
* Luminance-aware multiply blend - dark areas darken the destination while bright areas
|
|
18
|
+
* normal-blend through a mask
|
|
19
|
+
*/
|
|
20
|
+
FXBlending[FXBlending["MASKED_MULTIPLY"] = 1] = "MASKED_MULTIPLY";
|
|
21
|
+
})(FXBlending || (FXBlending = {}));
|
|
5
22
|
/**
|
|
6
23
|
* Abstract base class for particle materials
|
|
7
24
|
*/
|
|
8
25
|
class FXMaterial {
|
|
9
26
|
constructor(options = {}) {
|
|
10
27
|
this.albedoNodes = options.albedoNodes ?? [];
|
|
11
|
-
this.blending = options.blending ??
|
|
28
|
+
this.blending = options.blending ?? FXBlending.MASKED_ADDITIVE;
|
|
12
29
|
this.useAlphaHashing = options.useAlphaHashing ?? false;
|
|
13
30
|
this.alphaTest = options.alphaTest ?? 0.0075;
|
|
14
|
-
this.premultipliedAlpha =
|
|
15
|
-
(options.useAlphaHashing === undefined && options.premultipliedAlpha) ?? true;
|
|
16
31
|
this._private_depthAlphaTest = options.depthAlphaTest ?? 0.5;
|
|
17
32
|
this._private_useDepthAlphaHash = options.useDepthAlphaHash ?? false;
|
|
18
33
|
this._private_useSphericalDepth = options.useSphericalDepth ?? true;
|
|
@@ -25,12 +40,12 @@ class FXMaterial {
|
|
|
25
40
|
}
|
|
26
41
|
/** @internal */
|
|
27
42
|
buildDepthMaterial() {
|
|
28
|
-
return buildDepthMaterial(this.albedoNodes, this._private_depthAlphaTest, this._private_useDepthAlphaHash, this._private_useSphericalDepth);
|
|
43
|
+
return buildDepthMaterial(this.albedoNodes.filter((node) => node instanceof FXNodeColor || node instanceof FXNodeTexture), this._private_depthAlphaTest, this._private_useDepthAlphaHash, this._private_useSphericalDepth);
|
|
29
44
|
}
|
|
30
45
|
/** @internal */
|
|
31
46
|
buildDistanceMaterial() {
|
|
32
|
-
return buildDistanceMaterial(this.albedoNodes, this._private_depthAlphaTest, this._private_useDepthAlphaHash, this._private_useSphericalDepth);
|
|
47
|
+
return buildDistanceMaterial(this.albedoNodes.filter((node) => node instanceof FXNodeColor || node instanceof FXNodeTexture), this._private_depthAlphaTest, this._private_useDepthAlphaHash, this._private_useSphericalDepth);
|
|
33
48
|
}
|
|
34
49
|
}
|
|
35
50
|
|
|
36
|
-
export { FXMaterial };
|
|
51
|
+
export { FXBlending, FXMaterial };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Blending } from "three";
|
|
2
1
|
import { MeshBasicMaterial } from "three";
|
|
3
2
|
import type { GLTypeInfo } from "../../instancedParticle/shared";
|
|
3
|
+
import type { FXNodeBlending } from "../../nodes/blending/FXNodeBlending";
|
|
4
4
|
import type { FXNodeColor } from "../../nodes/color/FXNodeColor";
|
|
5
5
|
import type { FXNodeTexture } from "../../nodes/texture/FXNodeTexture";
|
|
6
|
-
|
|
6
|
+
import type { FXBlending } from "../FXMaterial/FXMaterial";
|
|
7
|
+
export declare function buildFXUnlitMaterial(varyings: Record<string, GLTypeInfo>, blending: FXBlending, useAlphaHashing: boolean, alphaTest: number, albedoNodes: readonly (FXNodeColor | FXNodeTexture | FXNodeBlending)[]): MeshBasicMaterial;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { MeshBasicMaterial, DoubleSide } from 'three';
|
|
1
|
+
import { MeshBasicMaterial, DoubleSide, NormalBlending } from 'three';
|
|
2
2
|
import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { buildAlbedoAndBlendingCode } from '../FXMaterial/FXBlending.Internal.js';
|
|
3
4
|
|
|
4
|
-
function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest,
|
|
5
|
+
function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, albedoNodes) {
|
|
5
6
|
const attributeDeclarations = [];
|
|
6
7
|
const varyingDeclarations = [];
|
|
7
8
|
const vertexAssignments = [];
|
|
@@ -17,11 +18,11 @@ function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, pr
|
|
|
17
18
|
depthWrite: useAlphaHashing,
|
|
18
19
|
depthTest: true,
|
|
19
20
|
alphaHash: useAlphaHashing,
|
|
20
|
-
blending,
|
|
21
|
+
blending: NormalBlending,
|
|
21
22
|
side: DoubleSide,
|
|
22
23
|
forceSinglePass: true,
|
|
23
24
|
alphaTest,
|
|
24
|
-
premultipliedAlpha,
|
|
25
|
+
premultipliedAlpha: true,
|
|
25
26
|
});
|
|
26
27
|
material.onBeforeCompile = (shader) => {
|
|
27
28
|
const vertexPreamble = [
|
|
@@ -63,34 +64,37 @@ function buildFXUnlitMaterial(varyings, blending, useAlphaHashing, alphaTest, pr
|
|
|
63
64
|
`);
|
|
64
65
|
const seenCacheKeys = new Set();
|
|
65
66
|
const uniqueHelpers = (nodes) => nodes
|
|
66
|
-
.filter((
|
|
67
|
-
if (seenCacheKeys.has(
|
|
67
|
+
.filter((node) => {
|
|
68
|
+
if (seenCacheKeys.has(node.cacheKey)) {
|
|
68
69
|
return false;
|
|
69
70
|
}
|
|
70
|
-
seenCacheKeys.add(
|
|
71
|
+
seenCacheKeys.add(node.cacheKey);
|
|
71
72
|
return true;
|
|
72
73
|
})
|
|
73
|
-
.map((
|
|
74
|
+
.map((node) => node.helperFunctions)
|
|
74
75
|
.join("\n");
|
|
75
76
|
const fragmentPreamble = [
|
|
76
77
|
PARTICLE_DEFINES,
|
|
77
78
|
"varying vec2 p_uv;",
|
|
78
79
|
varyingDeclarations.join("\n"),
|
|
79
|
-
albedoNodes.flatMap((
|
|
80
|
+
albedoNodes.flatMap((node) => node.uniformDeclarations).join("\n"),
|
|
80
81
|
uniqueHelpers(albedoNodes),
|
|
81
82
|
].join("\n");
|
|
82
83
|
for (const node of albedoNodes) {
|
|
83
84
|
Object.assign(shader.uniforms, node.uniforms);
|
|
84
85
|
}
|
|
86
|
+
const albedoCode = buildAlbedoAndBlendingCode(albedoNodes, blending, useAlphaHashing);
|
|
85
87
|
let fragmentShader = shader.fragmentShader;
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
const discardLine = useAlphaHashing ? "" : "if (diffuseColor.a < 0.0035) discard;";
|
|
89
|
-
fragmentShader = fragmentShader.replace("#include <map_fragment>", `diffuseColor = ${combinedExpression}; ${discardLine}`);
|
|
88
|
+
if (albedoCode.mapFragment !== null) {
|
|
89
|
+
fragmentShader = fragmentShader.replace("#include <map_fragment>", albedoCode.mapFragment);
|
|
90
90
|
}
|
|
91
|
+
fragmentShader = fragmentShader.replace("#include <premultiplied_alpha_fragment>", albedoCode.premultChunk);
|
|
91
92
|
shader.fragmentShader = fragmentPreamble + "\n" + fragmentShader;
|
|
92
93
|
};
|
|
93
|
-
material.customProgramCacheKey = ()
|
|
94
|
+
// material.customProgramCacheKey = (): string =>
|
|
95
|
+
// ["fx-unlit", albedoNodes.map((node) => node.cacheKey).join("-") || "none", FXBlending[blending]].join(
|
|
96
|
+
// "_",
|
|
97
|
+
// );
|
|
94
98
|
return material;
|
|
95
99
|
}
|
|
96
100
|
|
|
@@ -7,7 +7,7 @@ import { buildFXUnlitMaterial } from './FXUnlitMaterial.Internal.js';
|
|
|
7
7
|
class FXUnlitMaterial extends FXMaterial {
|
|
8
8
|
/** @internal */
|
|
9
9
|
buildThreeMaterial(varyings) {
|
|
10
|
-
return buildFXUnlitMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.
|
|
10
|
+
return buildFXUnlitMaterial(varyings, this.blending, this.useAlphaHashing, this.alphaTest, this.albedoNodes);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const CURRENT_EXPRESSION_VALUE_PLACEHOLDER = "CURRENT_EXPRESSION_VALUE_PLACEHOLDER";
|
|
2
|
+
export declare const BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER = "BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER";
|
|
3
|
+
export declare abstract class FXNode {
|
|
4
|
+
destroy?(): void;
|
|
5
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const CURRENT_EXPRESSION_VALUE_PLACEHOLDER = "CURRENT_EXPRESSION_VALUE_PLACEHOLDER";
|
|
2
|
+
const BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER = "BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER";
|
|
3
|
+
class FXNode {
|
|
4
|
+
constructor() {
|
|
5
|
+
/** @internal */
|
|
6
|
+
this.affectsDepth = false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { BLENDING_EXPRESSION_CURRENT_COLOR_PLACEHOLDER, CURRENT_EXPRESSION_VALUE_PLACEHOLDER, FXNode };
|