sparcoon 0.7.3 → 0.9.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 +124 -76
- package/dist/artifact/FXArtifact.d.ts +120 -0
- package/dist/artifact/FXArtifact.js +1 -0
- package/dist/coreLayout.d.ts +22 -0
- package/dist/coreLayout.js +32 -0
- package/dist/effect/FXEffect.d.ts +77 -0
- package/dist/effect/FXEffect.js +454 -0
- package/dist/effect/FXEffectSampling.Internal.d.ts +24 -0
- package/dist/effect/FXEffectSampling.Internal.js +165 -0
- package/dist/effect/FXEffectSpec.d.ts +115 -0
- package/dist/effect/FXEffectSpec.js +1 -0
- package/dist/emitter/FXEmitter.Internal.d.ts +23 -58
- package/dist/emitter/FXEmitter.Internal.js +13 -29
- package/dist/emitter/FXEmitter.d.ts +46 -48
- package/dist/emitter/FXEmitter.js +355 -186
- package/dist/emitter/FXFromArtifactsOptions.d.ts +13 -0
- package/dist/emitter/FXFromArtifactsOptions.js +1 -0
- package/dist/emitter/FXObjectMotion.Internal.d.ts +33 -0
- package/dist/emitter/FXObjectMotion.Internal.js +87 -0
- package/dist/emitter/FXSimulationHolder.d.ts +1 -0
- package/dist/emitter/FXSimulationHolder.js +43 -0
- package/dist/index.d.ts +10 -39
- package/dist/index.js +13 -36
- package/dist/instancedParticle/FXInstancedParticle.d.ts +1 -22
- package/dist/instancedParticle/FXInstancedParticle.js +145 -85
- package/dist/instancedParticle/glTypeInfo.d.ts +5 -0
- package/dist/instancedParticle/glTypeInfo.js +3 -0
- package/dist/instancedParticle/primitiveGeometry.d.ts +1 -0
- package/dist/instancedParticle/primitiveGeometry.js +30 -0
- package/dist/miscellaneous/asserts.d.ts +1 -18
- package/dist/miscellaneous/asserts.js +21 -28
- package/dist/miscellaneous/fxMath.d.ts +26 -0
- package/dist/miscellaneous/fxMath.js +261 -0
- package/dist/miscellaneous/math.d.ts +2 -7
- package/dist/miscellaneous/math.js +8 -9
- package/dist/miscellaneous/texture/fxDataTexture.d.ts +26 -0
- package/dist/miscellaneous/texture/fxDataTexture.js +20 -0
- package/dist/render/FXArtifactMaterial.Internal.d.ts +16 -0
- package/dist/render/FXArtifactMaterial.Internal.js +944 -0
- package/dist/render/FXArtifactMaterial.d.ts +1 -0
- package/dist/render/FXArtifactMaterial.js +79 -0
- package/dist/render/FXMaterialOptions.d.ts +27 -0
- package/dist/render/FXMaterialOptions.js +5 -0
- package/dist/render/FXMeshMaterial.d.ts +40 -0
- package/dist/render/FXMeshMaterial.js +77 -0
- package/dist/world/FXWorld.d.ts +42 -0
- package/dist/world/FXWorld.js +103 -0
- package/package.json +45 -29
- package/dist/_virtual/2d.js +0 -3
- package/dist/_virtual/3d.js +0 -3
- package/dist/_virtual/4d.js +0 -3
- package/dist/_virtual/mod.js +0 -5
- package/dist/_virtual/mod2.js +0 -3
- package/dist/emitter/behavior/FXBehavior.d.ts +0 -24
- package/dist/emitter/behavior/FXBehavior.js +0 -10
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +0 -23
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +0 -72
- package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +0 -36
- package/dist/emitter/behavior/FXBehaviorPointForce.js +0 -94
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +0 -24
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +0 -55
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +0 -48
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +0 -67
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +0 -19
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +0 -41
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +0 -50
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +0 -76
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +0 -22
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +0 -55
- package/dist/emitter/spawn/FXSpawn.d.ts +0 -11
- package/dist/emitter/spawn/FXSpawn.js +0 -10
- package/dist/emitter/spawn/FXSpawnBox.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnBox.js +0 -50
- package/dist/emitter/spawn/FXSpawnLifetime.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnLifetime.js +0 -44
- package/dist/emitter/spawn/FXSpawnOffset.d.ts +0 -21
- package/dist/emitter/spawn/FXSpawnOffset.js +0 -49
- package/dist/emitter/spawn/FXSpawnPoint.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnPoint.js +0 -46
- package/dist/emitter/spawn/FXSpawnRotation.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnRotation.js +0 -42
- package/dist/emitter/spawn/FXSpawnScale.d.ts +0 -22
- package/dist/emitter/spawn/FXSpawnScale.js +0 -56
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +0 -36
- package/dist/emitter/spawn/FXSpawnSphere.js +0 -87
- package/dist/emitter/spawn/FXSpawnTorque.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnTorque.js +0 -42
- package/dist/emitter/spawn/FXSpawnVelocity.d.ts +0 -29
- package/dist/emitter/spawn/FXSpawnVelocity.js +0 -91
- package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +0 -2
- package/dist/instancedParticle/shared.d.ts +0 -19
- package/dist/instancedParticle/shared.js +0 -116
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +0 -24
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +0 -275
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +0 -75
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +0 -63
- package/dist/materials/FXMaterial/FXBlending.Internal.d.ts +0 -24
- package/dist/materials/FXMaterial/FXBlending.Internal.js +0 -64
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.js +0 -144
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +0 -175
- package/dist/materials/FXMaterial/FXMaterial.d.ts +0 -70
- package/dist/materials/FXMaterial/FXMaterial.js +0 -51
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.js +0 -103
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +0 -21
- package/dist/miscellaneous/color/FXColor.Internal.d.ts +0 -7
- package/dist/miscellaneous/color/FXColor.Internal.js +0 -42
- package/dist/miscellaneous/color/FXColor.d.ts +0 -234
- package/dist/miscellaneous/color/FXColor.js +0 -636
- package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +0 -20
- package/dist/miscellaneous/curve/FXCurve1D.d.ts +0 -17
- package/dist/miscellaneous/curve/FXCurve1D.js +0 -94
- package/dist/miscellaneous/miscellaneous.d.ts +0 -34
- package/dist/miscellaneous/miscellaneous.js +0 -85
- package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +0 -49
- package/dist/miscellaneous/texture/FXTextureView.Internal.js +0 -6
- package/dist/miscellaneous/texture/FXTextureView.d.ts +0 -72
- package/dist/miscellaneous/texture/FXTextureView.js +0 -234
- package/dist/miscellaneous/webglCapabilities.d.ts +0 -1
- package/dist/miscellaneous/webglCapabilities.js +0 -45
- package/dist/node_modules/fast-simplex-noise/lib/2d.js +0 -93
- package/dist/node_modules/fast-simplex-noise/lib/3d.js +0 -142
- package/dist/node_modules/fast-simplex-noise/lib/4d.js +0 -196
- package/dist/node_modules/fast-simplex-noise/lib/mod.js +0 -24
- package/dist/nodes/FXNode.d.ts +0 -5
- package/dist/nodes/FXNode.js +0 -10
- package/dist/nodes/blending/FXNodeBlending.d.ts +0 -6
- package/dist/nodes/blending/FXNodeBlending.js +0 -9
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.d.ts +0 -19
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.js +0 -65
- package/dist/nodes/color/FXNodeColor.d.ts +0 -6
- package/dist/nodes/color/FXNodeColor.js +0 -9
- package/dist/nodes/color/FXNodeColorOverLife.d.ts +0 -22
- package/dist/nodes/color/FXNodeColorOverLife.js +0 -64
- package/dist/nodes/color/FXNodeSphericalClip.d.ts +0 -21
- package/dist/nodes/color/FXNodeSphericalClip.js +0 -54
- package/dist/nodes/normal/FXNodeFlatNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeFlatNormal.js +0 -16
- package/dist/nodes/normal/FXNodeNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeNormal.js +0 -9
- package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeSphericalNormal.js +0 -23
- package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +0 -52
- package/dist/nodes/texture/FXNodeAnimatedTexture.js +0 -151
- package/dist/nodes/texture/FXNodeStaticTexture.d.ts +0 -20
- package/dist/nodes/texture/FXNodeStaticTexture.js +0 -67
- package/dist/nodes/texture/FXNodeTexture.d.ts +0 -6
- package/dist/nodes/texture/FXNodeTexture.js +0 -9
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import { DoubleSide, NormalBlending, ShaderMaterial, UniformsLib, UniformsUtils } from "three";
|
|
2
|
+
import { FX_CORE_PARTICLE_DEFINES } from "../coreLayout.js";
|
|
3
|
+
/** The Three material transparency/depth flags for a render mode. Blending is always `NormalBlending`
|
|
4
|
+
* (`premultipliedAlpha`) - additive is done in-shader (see {@link blendTail}), not a separate mode. */
|
|
5
|
+
function materialFlags(renderMode) {
|
|
6
|
+
const blended = renderMode === "blending";
|
|
7
|
+
return { transparent: blended, depthWrite: !blended, alphaHash: renderMode === "alphaHash" };
|
|
8
|
+
}
|
|
9
|
+
// Each property becomes an `a_<name>` attribute mirrored into a `p_<name>` varying. Shared by both adapters.
|
|
10
|
+
function declareVaryings(varyings) {
|
|
11
|
+
const attributeDeclarations = [];
|
|
12
|
+
const varyingDeclarations = [];
|
|
13
|
+
const vertexAssignments = [];
|
|
14
|
+
for (const name in varyings) {
|
|
15
|
+
const { glslTypeName } = varyings[name];
|
|
16
|
+
attributeDeclarations.push(`attribute ${glslTypeName} a_${name};`);
|
|
17
|
+
varyingDeclarations.push(`varying ${glslTypeName} p_${name};`);
|
|
18
|
+
vertexAssignments.push(`p_${name} = a_${name};`);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
attributeDeclarations: attributeDeclarations.join("\n"),
|
|
22
|
+
varyingDeclarations: varyingDeclarations.join("\n"),
|
|
23
|
+
vertexAssignments: vertexAssignments.join("\n"),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** GLSL expression for a vertex output slot, or its default when the slot is unbound. */
|
|
27
|
+
function outputOrDefault(outputs, slot, fallback) {
|
|
28
|
+
return slot in outputs ? outputs[slot] : fallback;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds `fxModelPos = particleCenter + (particleTransform * (vertexTransform * position)).xyz` from
|
|
32
|
+
* the two optional mat4 outputs (identity when unwired). Geometry-agnostic - a billboard is just a
|
|
33
|
+
* plane whose `particleTransform` is a camera-facing rotation. `withNormal` also emits `fxNormalXform`
|
|
34
|
+
* (mat3) for the lambert adapter's real mesh normal.
|
|
35
|
+
*/
|
|
36
|
+
function transformPreamble(outputs, withNormal) {
|
|
37
|
+
const particleTransform = outputOrDefault(outputs, "particleTransform", "mat4(1.0)");
|
|
38
|
+
const vertexTransform = outputOrDefault(outputs, "vertexTransform", "mat4(1.0)");
|
|
39
|
+
const lines = [
|
|
40
|
+
`mat4 fxParticleXform = ${particleTransform};`,
|
|
41
|
+
`mat4 fxVertexXform = ${vertexTransform};`,
|
|
42
|
+
"vec3 particleCenter = vec3(PARTICLE_POSITION_X, PARTICLE_POSITION_Y, PARTICLE_POSITION_Z);",
|
|
43
|
+
"vec3 fxModelPos = particleCenter + (fxParticleXform * (fxVertexXform * vec4(position, 1.0))).xyz;",
|
|
44
|
+
];
|
|
45
|
+
if (withNormal) {
|
|
46
|
+
lines.push("mat3 fxNormalXform = mat3(fxParticleXform) * mat3(fxVertexXform);");
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
// `#include <project_vertex>` replacement: projects `fxModelPos`, records camera distance, feeds alpha-hash.
|
|
51
|
+
function projectFromModelPos() {
|
|
52
|
+
return [
|
|
53
|
+
"vec4 mvPosition = modelViewMatrix * vec4(fxModelPos, 1.0);",
|
|
54
|
+
"p_cameraDistance = length(mvPosition.xyz);",
|
|
55
|
+
"gl_Position = projectionMatrix * mvPosition;",
|
|
56
|
+
"#ifdef USE_ALPHAHASH",
|
|
57
|
+
" vPosition = fxModelPos;",
|
|
58
|
+
"#endif",
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
61
|
+
/** Binds the artifact's live uniform slots into the Three shader (shared object -> live scrub). */
|
|
62
|
+
function bindUniforms(shader, render, timeUniform, deltaUniform, externalSlots) {
|
|
63
|
+
shader.uniforms["u_time"] = timeUniform;
|
|
64
|
+
shader.uniforms["u_deltaTime"] = deltaUniform;
|
|
65
|
+
for (const name in render.uniforms) {
|
|
66
|
+
const uniform = render.uniforms[name];
|
|
67
|
+
if ("external" in uniform) {
|
|
68
|
+
// External texture resolves per emitter: bind the material's durable slot, not the shared module slot.
|
|
69
|
+
shader.uniforms[name] = externalSlots[name];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Share the artifact's slot object so a scrub of `render.uniforms[name].value` is picked up next frame.
|
|
73
|
+
shader.uniforms[name] = uniform;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Binds `objectVelocity`/`objectAngularVelocity` into the Three shader. */
|
|
78
|
+
function bindObjectMotionUniforms(shader, velocityUniform, angularVelocityUniform) {
|
|
79
|
+
shader.uniforms["objectVelocity"] = velocityUniform;
|
|
80
|
+
shader.uniforms["objectAngularVelocity"] = angularVelocityUniform;
|
|
81
|
+
}
|
|
82
|
+
/** Deterministic djb2 hash of the artifact's GLSL - a content-addressed program cache key. */
|
|
83
|
+
function hashString(input) {
|
|
84
|
+
let hash = 5381;
|
|
85
|
+
for (let i = 0; i < input.length; i++) {
|
|
86
|
+
hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;
|
|
87
|
+
}
|
|
88
|
+
return (hash >>> 0).toString(36);
|
|
89
|
+
}
|
|
90
|
+
// Stable program-cache key from the artifact's GLSL: identical artifacts share one compiled program.
|
|
91
|
+
export function fxRenderArtifactCacheKey(render) {
|
|
92
|
+
return hashString([
|
|
93
|
+
render.lightingIntrinsics.join(","),
|
|
94
|
+
render.uniformDeclarations.join("|"),
|
|
95
|
+
render.vertex.varyingDeclarations.join("|"),
|
|
96
|
+
render.vertex.helperFunctions.join("|"),
|
|
97
|
+
render.vertex.body.join("|"),
|
|
98
|
+
render.fragment.varyingDeclarations.join("|"),
|
|
99
|
+
render.fragment.helperFunctions.join("|"),
|
|
100
|
+
render.fragment.body.join("|"),
|
|
101
|
+
Object.entries(render.outputs)
|
|
102
|
+
.map(([slot, expression]) => `${slot}=${expression}`)
|
|
103
|
+
.join("|"),
|
|
104
|
+
].join("\n"));
|
|
105
|
+
}
|
|
106
|
+
// The fragment alpha-discard for a render mode (operates on `diffuseColor`). `alphaThresholdExpression`
|
|
107
|
+
// is the hard cutoff (default 0.0075, clamped to [0,1] so an out-of-range graph expression can't force
|
|
108
|
+
// an always/never discard), applied in every mode except `opaque` - `opaque` returns early with no
|
|
109
|
+
// discard at all, so it keeps the GPU's early depth test. `blending` also drops near-zero fragments;
|
|
110
|
+
// `alphaHash` adds the stochastic `alphahash_fragment` discard (needs `alphahash_pars_fragment` +
|
|
111
|
+
// `vPosition` in scope).
|
|
112
|
+
function alphaDiscardLines(renderMode, alphaThresholdExpression) {
|
|
113
|
+
if (renderMode === "opaque") {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
const cutoff = `if (diffuseColor.a < clamp(${alphaThresholdExpression}, 0.0, 1.0)) discard;`;
|
|
117
|
+
switch (renderMode) {
|
|
118
|
+
case "blending":
|
|
119
|
+
return `if (diffuseColor.a < 0.0035) discard;\n${cutoff}`;
|
|
120
|
+
case "alphaHash":
|
|
121
|
+
return `${cutoff}\n#include <alphahash_fragment>`;
|
|
122
|
+
case "alphaTest":
|
|
123
|
+
return cutoff;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// The blend tail after the color is assembled: premultiply, then (blending mode only) fold `additivity`
|
|
127
|
+
// into the OUTPUT alpha so a single premultiplied `NormalBlending` interpolates over -> additive: the
|
|
128
|
+
// color stays premultiplied by the true alpha, but reducing `gl_FragColor.a` toward 0 preserves more of
|
|
129
|
+
// the destination (`a=0` => pure additive). `additivity` defaults to 0 (plain "over").
|
|
130
|
+
function blendTail(renderMode, outputs) {
|
|
131
|
+
const lines = ["gl_FragColor.rgb *= gl_FragColor.a;"];
|
|
132
|
+
if (renderMode === "blending") {
|
|
133
|
+
lines.push(`gl_FragColor.a *= (1.0 - clamp(${outputOrDefault(outputs, "additivity", "0.0")}, 0.0, 1.0));`);
|
|
134
|
+
}
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
// Builds an unlit artifact as a self-contained ShaderMaterial: fog uniforms/defines come from Three
|
|
138
|
+
// (`fog: true`), the tone-mapping + output-colorspace functions from Three's fragment prefix; the rest
|
|
139
|
+
// is ours. The fragment tail mirrors MeshBasic (`opaque_fragment` -> tonemapping -> colorspace -> fog
|
|
140
|
+
// -> premultiply) by reusing those exact ShaderChunks, so pixels match the former onBeforeCompile splice.
|
|
141
|
+
// Deliberately dropped vs MeshBasic: clipping planes, logarithmic depth, dithering (unused by particles).
|
|
142
|
+
// The view-space surface normal (`vNormal` -> `geometryNormal`) is set up unconditionally (a small
|
|
143
|
+
// constant cost) so the `geometryNormal` builtin - fresnel, rim, normal masks - works with no lighting
|
|
144
|
+
// node present; the lit builder does the same.
|
|
145
|
+
export function buildUnlitArtifactMaterial(render, varyings, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
146
|
+
const options = render.options ?? {};
|
|
147
|
+
const renderMode = options.renderMode ?? "blending";
|
|
148
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
149
|
+
if (!("albedo" in render.outputs)) {
|
|
150
|
+
throw new Error("buildUnlitArtifactMaterial: render artifact has no 'albedo' output");
|
|
151
|
+
}
|
|
152
|
+
const albedo = render.outputs["albedo"];
|
|
153
|
+
const { attributeDeclarations, varyingDeclarations, vertexAssignments } = declareVaryings(varyings);
|
|
154
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
155
|
+
const vertexShader = [
|
|
156
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
157
|
+
attributeDeclarations,
|
|
158
|
+
"varying vec2 p_uv;",
|
|
159
|
+
"uniform float u_time;",
|
|
160
|
+
"uniform float u_deltaTime;",
|
|
161
|
+
"uniform vec3 objectVelocity;",
|
|
162
|
+
"uniform vec3 objectAngularVelocity;",
|
|
163
|
+
"varying float p_cameraDistance;",
|
|
164
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): the canonical author space for
|
|
165
|
+
// normals. Written in the vertex stage, read as the geometryNormal/geometryTangent/worldPosition
|
|
166
|
+
// builtins in the fragment stage.
|
|
167
|
+
"varying vec3 vWorldNormal;",
|
|
168
|
+
"varying vec3 vWorldTangent;",
|
|
169
|
+
"varying vec3 vWorldPos;",
|
|
170
|
+
varyingDeclarations,
|
|
171
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
172
|
+
render.uniformDeclarations.join("\n"),
|
|
173
|
+
render.vertex.helperFunctions.join("\n"),
|
|
174
|
+
// `common` declares `vPosition` (under USE_ALPHAHASH); `fog_pars_vertex` declares `vFogDepth`;
|
|
175
|
+
// `normal_pars_vertex` declares `vNormal` (the surface normal for the `geometryNormal` builtin).
|
|
176
|
+
"#include <common>",
|
|
177
|
+
"#include <fog_pars_vertex>",
|
|
178
|
+
"#include <normal_pars_vertex>",
|
|
179
|
+
"void main() {",
|
|
180
|
+
"p_uv = uv;",
|
|
181
|
+
vertexAssignments,
|
|
182
|
+
render.vertex.body.join("\n"),
|
|
183
|
+
transformPreamble(render.outputs, true),
|
|
184
|
+
// Our objectNormal (replaces beginnormal_vertex); stock defaultnormal/normal_vertex build vNormal.
|
|
185
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
186
|
+
"#include <defaultnormal_vertex>",
|
|
187
|
+
"#include <normal_vertex>",
|
|
188
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): mat3(modelMatrix) is exact for a
|
|
189
|
+
// plane's frame under uniform scale; the tangent is the plane's local +X (the u axis), so billboard
|
|
190
|
+
// roll/orientation is baked in here ONCE for every consumer (normal maps, spherical normal, fresnel).
|
|
191
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
192
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
193
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
194
|
+
projectFromModelPos(),
|
|
195
|
+
"#include <fog_vertex>",
|
|
196
|
+
"}",
|
|
197
|
+
].join("\n");
|
|
198
|
+
// `opaque_fragment` below forces alpha to 1 when the material is opaque (the hash case,
|
|
199
|
+
// `transparent: false`), exactly as MeshBasic did.
|
|
200
|
+
const fragmentShader = [
|
|
201
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
202
|
+
"varying vec2 p_uv;",
|
|
203
|
+
"uniform float u_time;",
|
|
204
|
+
"uniform float u_deltaTime;",
|
|
205
|
+
"uniform vec3 objectVelocity;",
|
|
206
|
+
"uniform vec3 objectAngularVelocity;",
|
|
207
|
+
"varying float p_cameraDistance;",
|
|
208
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): the canonical author space for
|
|
209
|
+
// normals. Written in the vertex stage, read as the geometryNormal/geometryTangent/worldPosition
|
|
210
|
+
// builtins in the fragment stage.
|
|
211
|
+
"varying vec3 vWorldNormal;",
|
|
212
|
+
"varying vec3 vWorldTangent;",
|
|
213
|
+
"varying vec3 vWorldPos;",
|
|
214
|
+
varyingDeclarations,
|
|
215
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
216
|
+
render.uniformDeclarations.join("\n"),
|
|
217
|
+
render.fragment.helperFunctions.join("\n"),
|
|
218
|
+
"#include <common>",
|
|
219
|
+
"#include <alphahash_pars_fragment>",
|
|
220
|
+
"#include <fog_pars_fragment>",
|
|
221
|
+
// `normal_pars_fragment` declares the interpolated `vNormal` used for `geometryNormal` below.
|
|
222
|
+
"#include <normal_pars_fragment>",
|
|
223
|
+
"void main() {",
|
|
224
|
+
// Front-facing sign for the surface frame below (a back-facing fragment flips the normal).
|
|
225
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
226
|
+
// World-space surface frame + position - the canonical author space (NORMAL_SPACE_PLAN). The
|
|
227
|
+
// lighting intrinsics convert world->view at their own boundary; geometryTangent/worldPosition feed
|
|
228
|
+
// normal maps and camera-correct fresnel. Declared before the node body so any reader resolves.
|
|
229
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
230
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
231
|
+
"vec3 worldPosition = vWorldPos;",
|
|
232
|
+
render.fragment.body.join("\n"),
|
|
233
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
234
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
235
|
+
"vec3 outgoingLight = diffuseColor.rgb;",
|
|
236
|
+
"#include <opaque_fragment>",
|
|
237
|
+
"#include <tonemapping_fragment>",
|
|
238
|
+
"#include <colorspace_fragment>",
|
|
239
|
+
"#include <fog_fragment>",
|
|
240
|
+
blendTail(renderMode, render.outputs),
|
|
241
|
+
"}",
|
|
242
|
+
].join("\n");
|
|
243
|
+
// ShaderMaterial does not auto-merge fog uniforms (unlike the built-in materials), so seed the fog
|
|
244
|
+
// slots here; `bindUniforms` then wires the live clock + artifact slots by reference (live scrub).
|
|
245
|
+
const uniforms = { ...UniformsUtils.clone(UniformsLib.fog) };
|
|
246
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
247
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
248
|
+
const material = new ShaderMaterial({
|
|
249
|
+
name: "FXUnlitArtifactMaterial",
|
|
250
|
+
uniforms,
|
|
251
|
+
vertexShader,
|
|
252
|
+
fragmentShader,
|
|
253
|
+
fog: true,
|
|
254
|
+
...materialFlags(renderMode),
|
|
255
|
+
depthTest: true,
|
|
256
|
+
blending: NormalBlending,
|
|
257
|
+
side: DoubleSide,
|
|
258
|
+
forceSinglePass: true,
|
|
259
|
+
premultipliedAlpha: true,
|
|
260
|
+
});
|
|
261
|
+
material.customProgramCacheKey = () => `fx-unlit_${cacheKey}`;
|
|
262
|
+
return material;
|
|
263
|
+
}
|
|
264
|
+
// The lighting-node intrinsics the graph calls by name (`fn.raw(... "fxLambertShade" ...)`) but never
|
|
265
|
+
// defines - the frozen `fx_` cross-repo ABI. Each wraps the exact stock Lambert light sequence in a
|
|
266
|
+
// function of (color, WORLD-space normal) -> shaded color: the graph authors normals in world space
|
|
267
|
+
// (NORMAL_SPACE_PLAN), and each intrinsic converts world->view on its first line before Three's
|
|
268
|
+
// view-space light chain runs, so view space stays a private detail behind this ABI. A node emits a
|
|
269
|
+
// plain expression that can sit anywhere in the graph (mid-graph mix included). Placed AFTER the pars chunks (which supply the
|
|
270
|
+
// `ReflectedLight`/`LambertMaterial` types, the `RE_*` macros, `vViewPosition`, the light uniforms) and
|
|
271
|
+
// BEFORE `main`. `#pragma unroll_loop_start` inside a function is expanded by the renderer's shader-wide
|
|
272
|
+
// loop-unroll pass, so the per-light loops resolve here too.
|
|
273
|
+
const FX_LIGHTING_NODE_INTRINSICS = [
|
|
274
|
+
"vec4 fxLambertShade(vec4 diffuseColor, vec3 worldNormal) {",
|
|
275
|
+
" vec3 normal = normalize( mat3( viewMatrix ) * worldNormal );",
|
|
276
|
+
" float specularStrength = 1.0;",
|
|
277
|
+
" ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );",
|
|
278
|
+
" #include <lights_lambert_fragment>",
|
|
279
|
+
" #include <lights_fragment_begin>",
|
|
280
|
+
" #include <lights_fragment_end>",
|
|
281
|
+
" return vec4( reflectedLight.directDiffuse + reflectedLight.indirectDiffuse, diffuseColor.a );",
|
|
282
|
+
"}",
|
|
283
|
+
// Indirect-only diffuse (ambient + light probe / SH + hemisphere), no direct-light loop or shadows -
|
|
284
|
+
// the exact indirect block the old ambient builder inlined, wrapped in a function.
|
|
285
|
+
"vec4 fxAmbientShade(vec4 diffuseColor, vec3 worldNormal) {",
|
|
286
|
+
" vec3 normal = normalize( mat3( viewMatrix ) * worldNormal );",
|
|
287
|
+
" float specularStrength = 1.0;",
|
|
288
|
+
" ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );",
|
|
289
|
+
" #include <lights_lambert_fragment>",
|
|
290
|
+
" vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );",
|
|
291
|
+
" #if defined( USE_LIGHT_PROBES )",
|
|
292
|
+
" irradiance += getLightProbeIrradiance( lightProbe, normal );",
|
|
293
|
+
" #endif",
|
|
294
|
+
" #if ( NUM_HEMI_LIGHTS > 0 )",
|
|
295
|
+
" #pragma unroll_loop_start",
|
|
296
|
+
" for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {",
|
|
297
|
+
" irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], normal );",
|
|
298
|
+
" }",
|
|
299
|
+
" #pragma unroll_loop_end",
|
|
300
|
+
" #endif",
|
|
301
|
+
" RE_IndirectDiffuse( irradiance, vec3( 0.0 ), normal, vec3( 0.0 ), vec3( 0.0 ), material, reflectedLight );",
|
|
302
|
+
" return vec4( reflectedLight.indirectDiffuse, diffuseColor.a );",
|
|
303
|
+
"}",
|
|
304
|
+
].join("\n");
|
|
305
|
+
// The lit material variant ({@link buildArtifactMaterial} picks it when the capability is non-empty):
|
|
306
|
+
// the graph's `albedo` already carries the shaded color (a lighting node emitted `fxLambertShade(...)` /
|
|
307
|
+
// `fxAmbientShade(...)`), so `main` uses the flat unlit tail (`outgoingLight = diffuseColor.rgb`) - no
|
|
308
|
+
// stock light chain runs in `main`. The material supplies Three's full Lambert light infrastructure
|
|
309
|
+
// (light + fog uniforms, pars chunks, view-space `vNormal`/`vViewPosition` + per-particle shadow
|
|
310
|
+
// plumbing) so the intrinsics resolve. The `geometryNormal` builtin (the surface normal every fragment
|
|
311
|
+
// node may read, and a lighting node's default `normal` argument) is computed at the top of `main`. Shading
|
|
312
|
+
// normal/emission are node inputs, not surface slots.
|
|
313
|
+
function buildLitArtifactMaterial(render, varyings, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
314
|
+
const options = render.options ?? {};
|
|
315
|
+
const renderMode = options.renderMode ?? "blending";
|
|
316
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
317
|
+
if (!("albedo" in render.outputs)) {
|
|
318
|
+
throw new Error("buildLitArtifactMaterial: render artifact has no 'albedo' output");
|
|
319
|
+
}
|
|
320
|
+
const albedo = render.outputs["albedo"];
|
|
321
|
+
const { attributeDeclarations, varyingDeclarations, vertexAssignments } = declareVaryings(varyings);
|
|
322
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
323
|
+
const vertexShader = [
|
|
324
|
+
"#define LAMBERT",
|
|
325
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
326
|
+
attributeDeclarations,
|
|
327
|
+
"varying vec2 p_uv;",
|
|
328
|
+
"uniform float u_time;",
|
|
329
|
+
"uniform float u_deltaTime;",
|
|
330
|
+
"uniform vec3 objectVelocity;",
|
|
331
|
+
"uniform vec3 objectAngularVelocity;",
|
|
332
|
+
"varying float p_cameraDistance;",
|
|
333
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): the canonical author space for
|
|
334
|
+
// normals. Written in the vertex stage, read as the geometryNormal/geometryTangent/worldPosition
|
|
335
|
+
// builtins in the fragment stage.
|
|
336
|
+
"varying vec3 vWorldNormal;",
|
|
337
|
+
"varying vec3 vWorldTangent;",
|
|
338
|
+
"varying vec3 vWorldPos;",
|
|
339
|
+
"varying vec3 vViewPosition;",
|
|
340
|
+
varyingDeclarations,
|
|
341
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
342
|
+
render.uniformDeclarations.join("\n"),
|
|
343
|
+
render.vertex.helperFunctions.join("\n"),
|
|
344
|
+
"#include <common>",
|
|
345
|
+
"#include <fog_pars_vertex>",
|
|
346
|
+
"#include <normal_pars_vertex>",
|
|
347
|
+
"#include <shadowmap_pars_vertex>",
|
|
348
|
+
"void main() {",
|
|
349
|
+
"p_uv = uv;",
|
|
350
|
+
vertexAssignments,
|
|
351
|
+
render.vertex.body.join("\n"),
|
|
352
|
+
transformPreamble(render.outputs, true),
|
|
353
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
354
|
+
"#include <defaultnormal_vertex>",
|
|
355
|
+
"#include <normal_vertex>",
|
|
356
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): mat3(modelMatrix) is exact for a
|
|
357
|
+
// plane's frame under uniform scale; the tangent is the plane's local +X (the u axis), so billboard
|
|
358
|
+
// roll/orientation is baked in here ONCE for every consumer (normal maps, spherical normal, fresnel).
|
|
359
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
360
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
361
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
362
|
+
"vec3 transformed = position;",
|
|
363
|
+
projectFromModelPos(),
|
|
364
|
+
"vViewPosition = - mvPosition.xyz;",
|
|
365
|
+
"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined( USE_SHADOWMAP ) || defined( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0",
|
|
366
|
+
" vec4 worldPosition = modelMatrix * vec4(particleCenter, 1.0);",
|
|
367
|
+
"#endif",
|
|
368
|
+
"#include <shadowmap_vertex>",
|
|
369
|
+
"#include <fog_vertex>",
|
|
370
|
+
"}",
|
|
371
|
+
].join("\n");
|
|
372
|
+
const fragmentShader = [
|
|
373
|
+
"#define LAMBERT",
|
|
374
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
375
|
+
"varying vec2 p_uv;",
|
|
376
|
+
"uniform float u_time;",
|
|
377
|
+
"uniform float u_deltaTime;",
|
|
378
|
+
"uniform vec3 objectVelocity;",
|
|
379
|
+
"uniform vec3 objectAngularVelocity;",
|
|
380
|
+
"varying float p_cameraDistance;",
|
|
381
|
+
// World-space surface frame + position (NORMAL_SPACE_PLAN): the canonical author space for
|
|
382
|
+
// normals. Written in the vertex stage, read as the geometryNormal/geometryTangent/worldPosition
|
|
383
|
+
// builtins in the fragment stage.
|
|
384
|
+
"varying vec3 vWorldNormal;",
|
|
385
|
+
"varying vec3 vWorldTangent;",
|
|
386
|
+
"varying vec3 vWorldPos;",
|
|
387
|
+
varyingDeclarations,
|
|
388
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
389
|
+
render.uniformDeclarations.join("\n"),
|
|
390
|
+
render.fragment.helperFunctions.join("\n"),
|
|
391
|
+
"#include <common>",
|
|
392
|
+
"#include <packing>",
|
|
393
|
+
"#include <alphahash_pars_fragment>",
|
|
394
|
+
"#include <fog_pars_fragment>",
|
|
395
|
+
"#include <bsdfs>",
|
|
396
|
+
"#include <lights_pars_begin>",
|
|
397
|
+
"#include <normal_pars_fragment>",
|
|
398
|
+
"#include <lights_lambert_pars_fragment>",
|
|
399
|
+
"#include <shadowmap_pars_fragment>",
|
|
400
|
+
FX_LIGHTING_NODE_INTRINSICS,
|
|
401
|
+
"void main() {",
|
|
402
|
+
// Front-facing sign (matching `normal_fragment_begin` under DOUBLE_SIDED): a back-facing fragment
|
|
403
|
+
// flips the surface frame below.
|
|
404
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
405
|
+
// World-space surface frame + position - the canonical author space (NORMAL_SPACE_PLAN). The
|
|
406
|
+
// lighting intrinsics convert world->view at their own boundary; geometryTangent/worldPosition feed
|
|
407
|
+
// normal maps and camera-correct fresnel. Declared before the node body so any reader resolves.
|
|
408
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
409
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
410
|
+
"vec3 worldPosition = vWorldPos;",
|
|
411
|
+
render.fragment.body.join("\n"),
|
|
412
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
413
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
414
|
+
"vec3 outgoingLight = diffuseColor.rgb;",
|
|
415
|
+
"#include <opaque_fragment>",
|
|
416
|
+
"#include <tonemapping_fragment>",
|
|
417
|
+
"#include <colorspace_fragment>",
|
|
418
|
+
"#include <fog_fragment>",
|
|
419
|
+
blendTail(renderMode, render.outputs),
|
|
420
|
+
"}",
|
|
421
|
+
].join("\n");
|
|
422
|
+
const uniforms = UniformsUtils.merge([
|
|
423
|
+
UniformsLib.lights,
|
|
424
|
+
UniformsLib.fog,
|
|
425
|
+
]);
|
|
426
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
427
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
428
|
+
const material = new ShaderMaterial({
|
|
429
|
+
name: "FXLightingNodesArtifactMaterial",
|
|
430
|
+
uniforms,
|
|
431
|
+
vertexShader,
|
|
432
|
+
fragmentShader,
|
|
433
|
+
lights: true,
|
|
434
|
+
fog: true,
|
|
435
|
+
...materialFlags(renderMode),
|
|
436
|
+
depthTest: true,
|
|
437
|
+
blending: NormalBlending,
|
|
438
|
+
side: DoubleSide,
|
|
439
|
+
forceSinglePass: true,
|
|
440
|
+
premultipliedAlpha: true,
|
|
441
|
+
});
|
|
442
|
+
material.customProgramCacheKey = () => `fx-lit-nodes_${cacheKey}`;
|
|
443
|
+
return material;
|
|
444
|
+
}
|
|
445
|
+
// Single entry point the material driver calls: the render artifact's lighting capability
|
|
446
|
+
// ({@link FXRenderArtifact.lightingIntrinsics}) picks the variant. Empty => unlit (flat albedo, no
|
|
447
|
+
// light infrastructure); non-empty => the lit variant, which defines the intrinsics and lights up
|
|
448
|
+
// Three's infrastructure. There is no per-model dispatch - the shading lives in the graph.
|
|
449
|
+
export function buildArtifactMaterial(render, varyings, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
450
|
+
const build = render.lightingIntrinsics.length > 0 ? buildLitArtifactMaterial : buildUnlitArtifactMaterial;
|
|
451
|
+
return build(render, varyings, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots);
|
|
452
|
+
}
|
|
453
|
+
// VFX-mesh material path: a single, non-instanced mesh (compiled against the mesh render target).
|
|
454
|
+
// Everything below reuses the particle path's shared, attribute-free helpers (bindUniforms,
|
|
455
|
+
// alphaDiscardLines, blendTail, materialFlags, the lighting intrinsics, the cache key) but drops the
|
|
456
|
+
// three per-particle couplings: FX_CORE_PARTICLE_DEFINES, the `a_`/`p_` instanced-attribute varyings
|
|
457
|
+
// (declareVaryings), and the particle-center vertex epilogue. It never touches the particle builders.
|
|
458
|
+
// Mesh twin of transformPreamble with NO particle center: a mesh is one object, so its vertices
|
|
459
|
+
// project straight from model space. The two optional mat4 outputs are the mesh's object-local
|
|
460
|
+
// transform (identity when unwired); withNormal emits fxNormalXform for the real mesh normal.
|
|
461
|
+
function meshTransformPreamble(outputs, withNormal) {
|
|
462
|
+
const particleTransform = outputOrDefault(outputs, "particleTransform", "mat4(1.0)");
|
|
463
|
+
const vertexTransform = outputOrDefault(outputs, "vertexTransform", "mat4(1.0)");
|
|
464
|
+
const lines = [
|
|
465
|
+
`mat4 fxParticleXform = ${particleTransform};`,
|
|
466
|
+
`mat4 fxVertexXform = ${vertexTransform};`,
|
|
467
|
+
"vec3 fxModelPos = (fxParticleXform * (fxVertexXform * vec4(position, 1.0))).xyz;",
|
|
468
|
+
];
|
|
469
|
+
if (withNormal) {
|
|
470
|
+
lines.push("mat3 fxNormalXform = mat3(fxParticleXform) * mat3(fxVertexXform);");
|
|
471
|
+
}
|
|
472
|
+
return lines.join("\n");
|
|
473
|
+
}
|
|
474
|
+
// Mesh `#include <project_vertex>` replacement: plain object-space projection. No `p_cameraDistance`
|
|
475
|
+
// (a mesh has no per-particle camera-distance builtin); still feeds alpha-hash `vPosition`.
|
|
476
|
+
function meshProjectFromModelPos() {
|
|
477
|
+
return [
|
|
478
|
+
"vec4 mvPosition = modelViewMatrix * vec4(fxModelPos, 1.0);",
|
|
479
|
+
"gl_Position = projectionMatrix * mvPosition;",
|
|
480
|
+
"#ifdef USE_ALPHAHASH",
|
|
481
|
+
" vPosition = fxModelPos;",
|
|
482
|
+
"#endif",
|
|
483
|
+
].join("\n");
|
|
484
|
+
}
|
|
485
|
+
// Unlit mesh material: the flat-albedo twin of buildUnlitArtifactMaterial with the particle plumbing
|
|
486
|
+
// removed. The world surface frame (geometryNormal/geometryTangent/worldPosition) is built from the
|
|
487
|
+
// mesh's real normal, so fresnel/rim/normal-maps work with no lighting node present.
|
|
488
|
+
function buildMeshUnlitArtifactMaterial(render, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
489
|
+
const options = render.options ?? {};
|
|
490
|
+
const renderMode = options.renderMode ?? "blending";
|
|
491
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
492
|
+
if (!("albedo" in render.outputs)) {
|
|
493
|
+
throw new Error("buildMeshUnlitArtifactMaterial: render artifact has no 'albedo' output");
|
|
494
|
+
}
|
|
495
|
+
const albedo = render.outputs["albedo"];
|
|
496
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
497
|
+
const vertexShader = [
|
|
498
|
+
"varying vec2 p_uv;",
|
|
499
|
+
"uniform float u_time;",
|
|
500
|
+
"uniform float u_deltaTime;",
|
|
501
|
+
"uniform vec3 objectVelocity;",
|
|
502
|
+
"uniform vec3 objectAngularVelocity;",
|
|
503
|
+
"varying vec3 vWorldNormal;",
|
|
504
|
+
"varying vec3 vWorldTangent;",
|
|
505
|
+
"varying vec3 vWorldPos;",
|
|
506
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
507
|
+
render.uniformDeclarations.join("\n"),
|
|
508
|
+
render.vertex.helperFunctions.join("\n"),
|
|
509
|
+
"#include <common>",
|
|
510
|
+
"#include <fog_pars_vertex>",
|
|
511
|
+
"#include <normal_pars_vertex>",
|
|
512
|
+
"void main() {",
|
|
513
|
+
"p_uv = uv;",
|
|
514
|
+
render.vertex.body.join("\n"),
|
|
515
|
+
meshTransformPreamble(render.outputs, true),
|
|
516
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
517
|
+
"#include <defaultnormal_vertex>",
|
|
518
|
+
"#include <normal_vertex>",
|
|
519
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
520
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
521
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
522
|
+
meshProjectFromModelPos(),
|
|
523
|
+
"#include <fog_vertex>",
|
|
524
|
+
"}",
|
|
525
|
+
].join("\n");
|
|
526
|
+
const fragmentShader = [
|
|
527
|
+
"varying vec2 p_uv;",
|
|
528
|
+
"uniform float u_time;",
|
|
529
|
+
"uniform float u_deltaTime;",
|
|
530
|
+
"uniform vec3 objectVelocity;",
|
|
531
|
+
"uniform vec3 objectAngularVelocity;",
|
|
532
|
+
"varying vec3 vWorldNormal;",
|
|
533
|
+
"varying vec3 vWorldTangent;",
|
|
534
|
+
"varying vec3 vWorldPos;",
|
|
535
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
536
|
+
render.uniformDeclarations.join("\n"),
|
|
537
|
+
render.fragment.helperFunctions.join("\n"),
|
|
538
|
+
"#include <common>",
|
|
539
|
+
"#include <alphahash_pars_fragment>",
|
|
540
|
+
"#include <fog_pars_fragment>",
|
|
541
|
+
"#include <normal_pars_fragment>",
|
|
542
|
+
"void main() {",
|
|
543
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
544
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
545
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
546
|
+
"vec3 worldPosition = vWorldPos;",
|
|
547
|
+
render.fragment.body.join("\n"),
|
|
548
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
549
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
550
|
+
"vec3 outgoingLight = diffuseColor.rgb;",
|
|
551
|
+
"#include <opaque_fragment>",
|
|
552
|
+
"#include <tonemapping_fragment>",
|
|
553
|
+
"#include <colorspace_fragment>",
|
|
554
|
+
"#include <fog_fragment>",
|
|
555
|
+
blendTail(renderMode, render.outputs),
|
|
556
|
+
"}",
|
|
557
|
+
].join("\n");
|
|
558
|
+
const uniforms = { ...UniformsUtils.clone(UniformsLib.fog) };
|
|
559
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
560
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
561
|
+
const material = new ShaderMaterial({
|
|
562
|
+
name: "FXMeshUnlitArtifactMaterial",
|
|
563
|
+
uniforms,
|
|
564
|
+
vertexShader,
|
|
565
|
+
fragmentShader,
|
|
566
|
+
fog: true,
|
|
567
|
+
...materialFlags(renderMode),
|
|
568
|
+
depthTest: true,
|
|
569
|
+
blending: NormalBlending,
|
|
570
|
+
side: DoubleSide,
|
|
571
|
+
forceSinglePass: true,
|
|
572
|
+
premultipliedAlpha: true,
|
|
573
|
+
});
|
|
574
|
+
material.customProgramCacheKey = () => `fx-mesh-unlit_${cacheKey}`;
|
|
575
|
+
return material;
|
|
576
|
+
}
|
|
577
|
+
// Lit mesh material: the lighting-nodes twin of buildLitArtifactMaterial (Three's Lambert light
|
|
578
|
+
// infrastructure + the fx_ shade intrinsics) with the particle plumbing removed. The graph's `albedo`
|
|
579
|
+
// already carries the shaded color, so `main` uses the flat tail.
|
|
580
|
+
function buildMeshLitArtifactMaterial(render, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
581
|
+
const options = render.options ?? {};
|
|
582
|
+
const renderMode = options.renderMode ?? "blending";
|
|
583
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
584
|
+
if (!("albedo" in render.outputs)) {
|
|
585
|
+
throw new Error("buildMeshLitArtifactMaterial: render artifact has no 'albedo' output");
|
|
586
|
+
}
|
|
587
|
+
const albedo = render.outputs["albedo"];
|
|
588
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
589
|
+
const vertexShader = [
|
|
590
|
+
"#define LAMBERT",
|
|
591
|
+
"varying vec2 p_uv;",
|
|
592
|
+
"uniform float u_time;",
|
|
593
|
+
"uniform float u_deltaTime;",
|
|
594
|
+
"uniform vec3 objectVelocity;",
|
|
595
|
+
"uniform vec3 objectAngularVelocity;",
|
|
596
|
+
"varying vec3 vWorldNormal;",
|
|
597
|
+
"varying vec3 vWorldTangent;",
|
|
598
|
+
"varying vec3 vWorldPos;",
|
|
599
|
+
"varying vec3 vViewPosition;",
|
|
600
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
601
|
+
render.uniformDeclarations.join("\n"),
|
|
602
|
+
render.vertex.helperFunctions.join("\n"),
|
|
603
|
+
"#include <common>",
|
|
604
|
+
"#include <fog_pars_vertex>",
|
|
605
|
+
"#include <normal_pars_vertex>",
|
|
606
|
+
"#include <shadowmap_pars_vertex>",
|
|
607
|
+
"void main() {",
|
|
608
|
+
"p_uv = uv;",
|
|
609
|
+
render.vertex.body.join("\n"),
|
|
610
|
+
meshTransformPreamble(render.outputs, true),
|
|
611
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
612
|
+
"#include <defaultnormal_vertex>",
|
|
613
|
+
"#include <normal_vertex>",
|
|
614
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
615
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
616
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
617
|
+
"vec3 transformed = position;",
|
|
618
|
+
meshProjectFromModelPos(),
|
|
619
|
+
"vViewPosition = - mvPosition.xyz;",
|
|
620
|
+
"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined( USE_SHADOWMAP ) || defined( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0",
|
|
621
|
+
" vec4 worldPosition = modelMatrix * vec4(fxModelPos, 1.0);",
|
|
622
|
+
"#endif",
|
|
623
|
+
"#include <shadowmap_vertex>",
|
|
624
|
+
"#include <fog_vertex>",
|
|
625
|
+
"}",
|
|
626
|
+
].join("\n");
|
|
627
|
+
const fragmentShader = [
|
|
628
|
+
"#define LAMBERT",
|
|
629
|
+
"varying vec2 p_uv;",
|
|
630
|
+
"uniform float u_time;",
|
|
631
|
+
"uniform float u_deltaTime;",
|
|
632
|
+
"uniform vec3 objectVelocity;",
|
|
633
|
+
"uniform vec3 objectAngularVelocity;",
|
|
634
|
+
"varying vec3 vWorldNormal;",
|
|
635
|
+
"varying vec3 vWorldTangent;",
|
|
636
|
+
"varying vec3 vWorldPos;",
|
|
637
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
638
|
+
render.uniformDeclarations.join("\n"),
|
|
639
|
+
render.fragment.helperFunctions.join("\n"),
|
|
640
|
+
"#include <common>",
|
|
641
|
+
"#include <packing>",
|
|
642
|
+
"#include <alphahash_pars_fragment>",
|
|
643
|
+
"#include <fog_pars_fragment>",
|
|
644
|
+
"#include <bsdfs>",
|
|
645
|
+
"#include <lights_pars_begin>",
|
|
646
|
+
"#include <normal_pars_fragment>",
|
|
647
|
+
"#include <lights_lambert_pars_fragment>",
|
|
648
|
+
"#include <shadowmap_pars_fragment>",
|
|
649
|
+
FX_LIGHTING_NODE_INTRINSICS,
|
|
650
|
+
"void main() {",
|
|
651
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
652
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
653
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
654
|
+
"vec3 worldPosition = vWorldPos;",
|
|
655
|
+
render.fragment.body.join("\n"),
|
|
656
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
657
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
658
|
+
"vec3 outgoingLight = diffuseColor.rgb;",
|
|
659
|
+
"#include <opaque_fragment>",
|
|
660
|
+
"#include <tonemapping_fragment>",
|
|
661
|
+
"#include <colorspace_fragment>",
|
|
662
|
+
"#include <fog_fragment>",
|
|
663
|
+
blendTail(renderMode, render.outputs),
|
|
664
|
+
"}",
|
|
665
|
+
].join("\n");
|
|
666
|
+
const uniforms = UniformsUtils.merge([
|
|
667
|
+
UniformsLib.lights,
|
|
668
|
+
UniformsLib.fog,
|
|
669
|
+
]);
|
|
670
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
671
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
672
|
+
const material = new ShaderMaterial({
|
|
673
|
+
name: "FXMeshLightingNodesArtifactMaterial",
|
|
674
|
+
uniforms,
|
|
675
|
+
vertexShader,
|
|
676
|
+
fragmentShader,
|
|
677
|
+
lights: true,
|
|
678
|
+
fog: true,
|
|
679
|
+
...materialFlags(renderMode),
|
|
680
|
+
depthTest: true,
|
|
681
|
+
blending: NormalBlending,
|
|
682
|
+
side: DoubleSide,
|
|
683
|
+
forceSinglePass: true,
|
|
684
|
+
premultipliedAlpha: true,
|
|
685
|
+
});
|
|
686
|
+
material.customProgramCacheKey = () => `fx-mesh-lit-nodes_${cacheKey}`;
|
|
687
|
+
return material;
|
|
688
|
+
}
|
|
689
|
+
// Mesh entry point (the mesh twin of buildArtifactMaterial): the lighting capability picks flat vs lit.
|
|
690
|
+
// No `varyings` argument - a VFX mesh has no per-particle instanced attributes.
|
|
691
|
+
export function buildMeshArtifactMaterial(render, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
692
|
+
const build = render.lightingIntrinsics.length > 0
|
|
693
|
+
? buildMeshLitArtifactMaterial
|
|
694
|
+
: buildMeshUnlitArtifactMaterial;
|
|
695
|
+
return build(render, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots);
|
|
696
|
+
}
|
|
697
|
+
// The fragment tail that replaces the color output (opaque_fragment -> tonemapping -> ... ->
|
|
698
|
+
// blendTail) in the depth builders below. `packDepthToRGBA` (from `<packing>`) always packs RGBA:
|
|
699
|
+
// Three's shadow map only ever requests `RGBADepthPacking`, so `BasicDepthPacking` is unsupported.
|
|
700
|
+
const DEPTH_FRAGMENT_TAIL = [
|
|
701
|
+
"float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;",
|
|
702
|
+
"gl_FragColor = packDepthToRGBA( fragCoordZ );",
|
|
703
|
+
];
|
|
704
|
+
// Depth (shadow-caster) twin of buildArtifactMaterial: the SAME compiled render IR spliced into a
|
|
705
|
+
// customDepthMaterial so a cast shadow's silhouette honors the authored shape/deformation and the
|
|
706
|
+
// exact same alpha cutout (dissolve, spherical-clip, blend-mode trim - all via alphaDiscardLines) as
|
|
707
|
+
// the visible material. A stock MeshDepthMaterial would ignore all of that and, worse, collapse every
|
|
708
|
+
// instanced particle onto one un-instanced vertex. It mirrors buildUnlit/LitArtifactMaterial's whole
|
|
709
|
+
// vertex+fragment scope (every builtin the IR body reads must be declared, since the body is the same
|
|
710
|
+
// IR) and only swaps the color tail for the RGBA depth pack. The lit branch keeps Three's full Lambert
|
|
711
|
+
// light infrastructure so the fx_ shade intrinsics the graph calls in `albedo` still compile - only
|
|
712
|
+
// the discarded RGB depends on shading, and fxLambertShade returns the input alpha unchanged, so the
|
|
713
|
+
// depth cutout stays correct. Fog/logdepth/clipping-planes are dropped, same as the 4 color builders.
|
|
714
|
+
export function buildArtifactDepthMaterial(render, varyings, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
715
|
+
const options = render.options ?? {};
|
|
716
|
+
const renderMode = options.renderMode ?? "blending";
|
|
717
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
718
|
+
if (!("albedo" in render.outputs)) {
|
|
719
|
+
throw new Error("buildArtifactDepthMaterial: render artifact has no 'albedo' output");
|
|
720
|
+
}
|
|
721
|
+
const albedo = render.outputs["albedo"];
|
|
722
|
+
const lit = render.lightingIntrinsics.length > 0;
|
|
723
|
+
const { attributeDeclarations, varyingDeclarations, vertexAssignments } = declareVaryings(varyings);
|
|
724
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
725
|
+
const vertexShader = [
|
|
726
|
+
...(lit ? ["#define LAMBERT"] : []),
|
|
727
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
728
|
+
attributeDeclarations,
|
|
729
|
+
"varying vec2 p_uv;",
|
|
730
|
+
"uniform float u_time;",
|
|
731
|
+
"uniform float u_deltaTime;",
|
|
732
|
+
"uniform vec3 objectVelocity;",
|
|
733
|
+
"uniform vec3 objectAngularVelocity;",
|
|
734
|
+
"varying float p_cameraDistance;",
|
|
735
|
+
"varying vec3 vWorldNormal;",
|
|
736
|
+
"varying vec3 vWorldTangent;",
|
|
737
|
+
"varying vec3 vWorldPos;",
|
|
738
|
+
...(lit ? ["varying vec3 vViewPosition;"] : []),
|
|
739
|
+
// High-precision NDC z/w carried to the fragment for the depth pack (mirrors stock depth.glsl.js).
|
|
740
|
+
"varying vec2 vHighPrecisionZW;",
|
|
741
|
+
varyingDeclarations,
|
|
742
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
743
|
+
render.uniformDeclarations.join("\n"),
|
|
744
|
+
render.vertex.helperFunctions.join("\n"),
|
|
745
|
+
"#include <common>",
|
|
746
|
+
"#include <normal_pars_vertex>",
|
|
747
|
+
...(lit ? ["#include <shadowmap_pars_vertex>"] : []),
|
|
748
|
+
"void main() {",
|
|
749
|
+
"p_uv = uv;",
|
|
750
|
+
vertexAssignments,
|
|
751
|
+
render.vertex.body.join("\n"),
|
|
752
|
+
transformPreamble(render.outputs, true),
|
|
753
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
754
|
+
"#include <defaultnormal_vertex>",
|
|
755
|
+
"#include <normal_vertex>",
|
|
756
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
757
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
758
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
759
|
+
...(lit ? ["vec3 transformed = position;"] : []),
|
|
760
|
+
projectFromModelPos(),
|
|
761
|
+
...(lit
|
|
762
|
+
? [
|
|
763
|
+
"vViewPosition = - mvPosition.xyz;",
|
|
764
|
+
"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined( USE_SHADOWMAP ) || defined( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0",
|
|
765
|
+
" vec4 worldPosition = modelMatrix * vec4(particleCenter, 1.0);",
|
|
766
|
+
"#endif",
|
|
767
|
+
"#include <shadowmap_vertex>",
|
|
768
|
+
]
|
|
769
|
+
: []),
|
|
770
|
+
"vHighPrecisionZW = gl_Position.zw;",
|
|
771
|
+
"}",
|
|
772
|
+
].join("\n");
|
|
773
|
+
const fragmentShader = [
|
|
774
|
+
...(lit ? ["#define LAMBERT"] : []),
|
|
775
|
+
FX_CORE_PARTICLE_DEFINES,
|
|
776
|
+
"varying vec2 p_uv;",
|
|
777
|
+
"uniform float u_time;",
|
|
778
|
+
"uniform float u_deltaTime;",
|
|
779
|
+
"uniform vec3 objectVelocity;",
|
|
780
|
+
"uniform vec3 objectAngularVelocity;",
|
|
781
|
+
"varying float p_cameraDistance;",
|
|
782
|
+
"varying vec3 vWorldNormal;",
|
|
783
|
+
"varying vec3 vWorldTangent;",
|
|
784
|
+
"varying vec3 vWorldPos;",
|
|
785
|
+
"varying vec2 vHighPrecisionZW;",
|
|
786
|
+
varyingDeclarations,
|
|
787
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
788
|
+
render.uniformDeclarations.join("\n"),
|
|
789
|
+
render.fragment.helperFunctions.join("\n"),
|
|
790
|
+
"#include <common>",
|
|
791
|
+
"#include <packing>",
|
|
792
|
+
"#include <alphahash_pars_fragment>",
|
|
793
|
+
// Fragment include ORDER mirrors buildLit/UnlitArtifactMaterial exactly (minus fog): the only hard
|
|
794
|
+
// dependency is lights_lambert_pars_fragment needing bsdfs + lights_pars_begin declared before it.
|
|
795
|
+
...(lit
|
|
796
|
+
? [
|
|
797
|
+
"#include <bsdfs>",
|
|
798
|
+
"#include <lights_pars_begin>",
|
|
799
|
+
"#include <normal_pars_fragment>",
|
|
800
|
+
"#include <lights_lambert_pars_fragment>",
|
|
801
|
+
"#include <shadowmap_pars_fragment>",
|
|
802
|
+
FX_LIGHTING_NODE_INTRINSICS,
|
|
803
|
+
]
|
|
804
|
+
: ["#include <normal_pars_fragment>"]),
|
|
805
|
+
"void main() {",
|
|
806
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
807
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
808
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
809
|
+
"vec3 worldPosition = vWorldPos;",
|
|
810
|
+
render.fragment.body.join("\n"),
|
|
811
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
812
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
813
|
+
...DEPTH_FRAGMENT_TAIL,
|
|
814
|
+
"}",
|
|
815
|
+
].join("\n");
|
|
816
|
+
// A pure depth target: blending/transparency are meaningless, so force the depth flags regardless
|
|
817
|
+
// of the color material's render mode (never `materialFlags`). DoubleSide so a two-sided billboard
|
|
818
|
+
// or open mesh casts from both faces.
|
|
819
|
+
const uniforms = lit ? UniformsUtils.merge([UniformsLib.lights]) : {};
|
|
820
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
821
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
822
|
+
const material = new ShaderMaterial({
|
|
823
|
+
name: "FXArtifactDepthMaterial",
|
|
824
|
+
uniforms,
|
|
825
|
+
vertexShader,
|
|
826
|
+
fragmentShader,
|
|
827
|
+
...(lit ? { lights: true } : {}),
|
|
828
|
+
depthTest: true,
|
|
829
|
+
depthWrite: true,
|
|
830
|
+
side: DoubleSide,
|
|
831
|
+
});
|
|
832
|
+
material.customProgramCacheKey = () => `fx-depth_${cacheKey}`;
|
|
833
|
+
return material;
|
|
834
|
+
}
|
|
835
|
+
// Mesh twin of buildArtifactDepthMaterial (the mesh-path depth builder): mirrors buildMeshUnlit/
|
|
836
|
+
// LitArtifactMaterial's scope with the particle plumbing removed, swapping the color tail for the
|
|
837
|
+
// depth pack. Same lit/unlit internal branch. No `varyings` - a VFX mesh has no instanced attributes.
|
|
838
|
+
export function buildMeshArtifactDepthMaterial(render, timeUniform, deltaUniform, velocityUniform, angularVelocityUniform, externalSlots = {}) {
|
|
839
|
+
const options = render.options ?? {};
|
|
840
|
+
const renderMode = options.renderMode ?? "blending";
|
|
841
|
+
const alphaThreshold = outputOrDefault(render.outputs, "alphaThreshold", "0.0075");
|
|
842
|
+
if (!("albedo" in render.outputs)) {
|
|
843
|
+
throw new Error("buildMeshArtifactDepthMaterial: render artifact has no 'albedo' output");
|
|
844
|
+
}
|
|
845
|
+
const albedo = render.outputs["albedo"];
|
|
846
|
+
const lit = render.lightingIntrinsics.length > 0;
|
|
847
|
+
const cacheKey = fxRenderArtifactCacheKey(render);
|
|
848
|
+
const vertexShader = [
|
|
849
|
+
...(lit ? ["#define LAMBERT"] : []),
|
|
850
|
+
"varying vec2 p_uv;",
|
|
851
|
+
"uniform float u_time;",
|
|
852
|
+
"uniform float u_deltaTime;",
|
|
853
|
+
"uniform vec3 objectVelocity;",
|
|
854
|
+
"uniform vec3 objectAngularVelocity;",
|
|
855
|
+
"varying vec3 vWorldNormal;",
|
|
856
|
+
"varying vec3 vWorldTangent;",
|
|
857
|
+
"varying vec3 vWorldPos;",
|
|
858
|
+
...(lit ? ["varying vec3 vViewPosition;"] : []),
|
|
859
|
+
"varying vec2 vHighPrecisionZW;",
|
|
860
|
+
render.vertex.varyingDeclarations.join("\n"),
|
|
861
|
+
render.uniformDeclarations.join("\n"),
|
|
862
|
+
render.vertex.helperFunctions.join("\n"),
|
|
863
|
+
"#include <common>",
|
|
864
|
+
"#include <normal_pars_vertex>",
|
|
865
|
+
...(lit ? ["#include <shadowmap_pars_vertex>"] : []),
|
|
866
|
+
"void main() {",
|
|
867
|
+
"p_uv = uv;",
|
|
868
|
+
render.vertex.body.join("\n"),
|
|
869
|
+
meshTransformPreamble(render.outputs, true),
|
|
870
|
+
"vec3 objectNormal = fxNormalXform * normal;",
|
|
871
|
+
"#include <defaultnormal_vertex>",
|
|
872
|
+
"#include <normal_vertex>",
|
|
873
|
+
"vWorldNormal = normalize( mat3( modelMatrix ) * objectNormal );",
|
|
874
|
+
"vWorldTangent = normalize( mat3( modelMatrix ) * ( fxNormalXform * vec3( 1.0, 0.0, 0.0 ) ) );",
|
|
875
|
+
"vWorldPos = ( modelMatrix * vec4( fxModelPos, 1.0 ) ).xyz;",
|
|
876
|
+
...(lit ? ["vec3 transformed = position;"] : []),
|
|
877
|
+
meshProjectFromModelPos(),
|
|
878
|
+
...(lit
|
|
879
|
+
? [
|
|
880
|
+
"vViewPosition = - mvPosition.xyz;",
|
|
881
|
+
"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined( USE_SHADOWMAP ) || defined( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0",
|
|
882
|
+
" vec4 worldPosition = modelMatrix * vec4(fxModelPos, 1.0);",
|
|
883
|
+
"#endif",
|
|
884
|
+
"#include <shadowmap_vertex>",
|
|
885
|
+
]
|
|
886
|
+
: []),
|
|
887
|
+
"vHighPrecisionZW = gl_Position.zw;",
|
|
888
|
+
"}",
|
|
889
|
+
].join("\n");
|
|
890
|
+
const fragmentShader = [
|
|
891
|
+
...(lit ? ["#define LAMBERT"] : []),
|
|
892
|
+
"varying vec2 p_uv;",
|
|
893
|
+
"uniform float u_time;",
|
|
894
|
+
"uniform float u_deltaTime;",
|
|
895
|
+
"uniform vec3 objectVelocity;",
|
|
896
|
+
"uniform vec3 objectAngularVelocity;",
|
|
897
|
+
"varying vec3 vWorldNormal;",
|
|
898
|
+
"varying vec3 vWorldTangent;",
|
|
899
|
+
"varying vec3 vWorldPos;",
|
|
900
|
+
"varying vec2 vHighPrecisionZW;",
|
|
901
|
+
render.fragment.varyingDeclarations.join("\n"),
|
|
902
|
+
render.uniformDeclarations.join("\n"),
|
|
903
|
+
render.fragment.helperFunctions.join("\n"),
|
|
904
|
+
"#include <common>",
|
|
905
|
+
"#include <packing>",
|
|
906
|
+
"#include <alphahash_pars_fragment>",
|
|
907
|
+
// Fragment include ORDER mirrors buildMeshLit/UnlitArtifactMaterial exactly (minus fog).
|
|
908
|
+
...(lit
|
|
909
|
+
? [
|
|
910
|
+
"#include <bsdfs>",
|
|
911
|
+
"#include <lights_pars_begin>",
|
|
912
|
+
"#include <normal_pars_fragment>",
|
|
913
|
+
"#include <lights_lambert_pars_fragment>",
|
|
914
|
+
"#include <shadowmap_pars_fragment>",
|
|
915
|
+
FX_LIGHTING_NODE_INTRINSICS,
|
|
916
|
+
]
|
|
917
|
+
: ["#include <normal_pars_fragment>"]),
|
|
918
|
+
"void main() {",
|
|
919
|
+
"float fxFaceDirection = gl_FrontFacing ? 1.0 : - 1.0;",
|
|
920
|
+
"vec3 geometryNormal = normalize( vWorldNormal ) * fxFaceDirection;",
|
|
921
|
+
"vec3 geometryTangent = normalize( vWorldTangent );",
|
|
922
|
+
"vec3 worldPosition = vWorldPos;",
|
|
923
|
+
render.fragment.body.join("\n"),
|
|
924
|
+
`vec4 diffuseColor = ${albedo};`,
|
|
925
|
+
alphaDiscardLines(renderMode, alphaThreshold),
|
|
926
|
+
...DEPTH_FRAGMENT_TAIL,
|
|
927
|
+
"}",
|
|
928
|
+
].join("\n");
|
|
929
|
+
const uniforms = lit ? UniformsUtils.merge([UniformsLib.lights]) : {};
|
|
930
|
+
bindUniforms({ uniforms }, render, timeUniform, deltaUniform, externalSlots);
|
|
931
|
+
bindObjectMotionUniforms({ uniforms }, velocityUniform, angularVelocityUniform);
|
|
932
|
+
const material = new ShaderMaterial({
|
|
933
|
+
name: "FXMeshArtifactDepthMaterial",
|
|
934
|
+
uniforms,
|
|
935
|
+
vertexShader,
|
|
936
|
+
fragmentShader,
|
|
937
|
+
...(lit ? { lights: true } : {}),
|
|
938
|
+
depthTest: true,
|
|
939
|
+
depthWrite: true,
|
|
940
|
+
side: DoubleSide,
|
|
941
|
+
});
|
|
942
|
+
material.customProgramCacheKey = () => `fx-mesh-depth_${cacheKey}`;
|
|
943
|
+
return material;
|
|
944
|
+
}
|