@driftengine/core 4.10.0 → 4.11.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/CHANGELOG.json +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +23 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/surfaceLayers.d.ts +107 -0
- package/dist/render/surfaceLayers.js +82 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- package/src/render/backend/webgpu/buffers.ts +153 -54
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +622 -164
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +23 -3
- package/src/render/shaders/flat/overlay.ts +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/surfaceLayers.ts +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -18,9 +18,14 @@ import { modelsGlsl } from './models.ts';
|
|
|
18
18
|
import { AREA_LIGHT_GLSL } from './areaLight.ts';
|
|
19
19
|
import { NORMAL_MAP_GLSL } from './normalMap.ts';
|
|
20
20
|
import { DRIFT_LIGHT_GLSL } from './driftLight.ts';
|
|
21
|
+
import { LIGHTMAP_OPACITY_STEPS } from '../../instances.ts';
|
|
21
22
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.ts';
|
|
22
23
|
import { overlayGlsl } from './overlay.ts';
|
|
23
24
|
import { reflectionSurfaceGlsl } from './reflectionSurface.ts';
|
|
25
|
+
import { worldUvGlsl } from './worldUv.ts';
|
|
26
|
+
import { movingSunGlsl } from './movingSun.ts';
|
|
27
|
+
import { layeredGlsl } from './layered.ts';
|
|
28
|
+
import { MAP_READS_GLSL } from './mapReads.ts';
|
|
24
29
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.ts';
|
|
25
30
|
import { BONE_ANIMATION_GLSL } from '../boneAnimation.ts';
|
|
26
31
|
import { FULL_LIGHT_BUDGET, type LightBudget } from '../../uniformVectorBudget.ts';
|
|
@@ -338,10 +343,16 @@ void main() {
|
|
|
338
343
|
vAlpha = aInstanceTint.w;
|
|
339
344
|
vThickness = 1.0;
|
|
340
345
|
if (LIGHTMAP_REGIONS) {
|
|
346
|
+
/*
|
|
347
|
+
* The opacity's transparency rides the U offset as whole steps of 2, the offset itself under 1
|
|
348
|
+
* (\`MeshInstances.lightmapRegions\`): taken off, and none where the instance is opaque.
|
|
349
|
+
*/
|
|
350
|
+
float steps = floor(aInstanceTint.z * 0.5);
|
|
351
|
+
float offsetU = aInstanceTint.z - 2.0 * steps;
|
|
341
352
|
/* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
|
|
342
|
-
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x +
|
|
353
|
+
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + offsetU);
|
|
343
354
|
vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
|
|
344
|
-
vAlpha = 1.0;
|
|
355
|
+
vAlpha = 1.0 - steps / ${LIGHTMAP_OPACITY_STEPS.toFixed(1)};
|
|
345
356
|
}
|
|
346
357
|
#else
|
|
347
358
|
vSkyDirect = aChannel.y;
|
|
@@ -508,6 +519,21 @@ export interface FlatShaderOptions {
|
|
|
508
519
|
* the frame's materials' reflections. True when absent. See `reflectionSurfaceGlsl`.
|
|
509
520
|
*/
|
|
510
521
|
readonly reflectionMaps?: boolean;
|
|
522
|
+
/**
|
|
523
|
+
* Whether it places a material's maps by the world, `uWorldUv` and all (`SurfaceMaterial
|
|
524
|
+
* .projection`). True when absent. See `worldUvGlsl`.
|
|
525
|
+
*/
|
|
526
|
+
readonly worldUvs?: boolean;
|
|
527
|
+
/**
|
|
528
|
+
* Whether it reads the sun's moving layer through a matrix of its own, \`uMovingLightViewProj\`
|
|
529
|
+
* and all. True when absent. See \`movingSunGlsl\`.
|
|
530
|
+
*/
|
|
531
|
+
readonly movingSun?: boolean;
|
|
532
|
+
/**
|
|
533
|
+
* Whether it blends a material's layers by a mask, \`uLayers\` and all (\`SurfaceMaterial.layers\`).
|
|
534
|
+
* True when absent. See \`layeredGlsl\`.
|
|
535
|
+
*/
|
|
536
|
+
readonly layered?: boolean;
|
|
511
537
|
/**
|
|
512
538
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
513
539
|
*
|
|
@@ -619,6 +645,8 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
619
645
|
surfaceEffects: options.surfaceEffects ?? true,
|
|
620
646
|
driftLight: options.driftLight ?? true,
|
|
621
647
|
}),
|
|
648
|
+
/* Depth zero and ahead of the sun's lookup, which calls it; its switch takes the next id. */
|
|
649
|
+
movingSunGlsl(options.movingSun ?? true),
|
|
622
650
|
LOBES_GLSL,
|
|
623
651
|
POINTSHADOW_GLSL,
|
|
624
652
|
DIRECTIONALSHADOW_GLSL,
|
|
@@ -644,8 +672,6 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
644
672
|
* and `main` is unconditional by construction.
|
|
645
673
|
*/
|
|
646
674
|
TANGENT_FRAME_GLSL,
|
|
647
|
-
/* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
|
|
648
|
-
NORMAL_MAP_GLSL,
|
|
649
675
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
650
676
|
CUTOUT_COVERAGE_GLSL,
|
|
651
677
|
CUTOUT_DITHER_GLSL,
|
|
@@ -657,6 +683,15 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
657
683
|
overlayGlsl(options.surfaceOverlay ?? true),
|
|
658
684
|
/* After the overlay, so its switch takes the next id. See reflectionSurface.ts. */
|
|
659
685
|
reflectionSurfaceGlsl(options.reflectionMaps ?? true),
|
|
686
|
+
/* After the reflection maps, so its switch takes the next id; main calls it. worldUv.ts. */
|
|
687
|
+
worldUvGlsl(options.worldUvs ?? true),
|
|
688
|
+
/* After the projection, so its switch takes the next id; the normal map below calls it. */
|
|
689
|
+
layeredGlsl(options.layered ?? true),
|
|
690
|
+
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
691
|
+
MAP_READS_GLSL,
|
|
692
|
+
/* After the frame and the projection it calls, unconditional for the same reason as the
|
|
693
|
+
frame. See normalMap.ts. */
|
|
694
|
+
NORMAL_MAP_GLSL,
|
|
660
695
|
/* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
|
|
661
696
|
AREA_LIGHT_GLSL,
|
|
662
697
|
MAIN_GLSL,
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material blended from layers by a mask: `SurfaceMaterial.layers`, packed into `uLayers` by
|
|
3
|
+
* `surfaceLayers.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Layer `i` is layer `i` of the material's arrays**, each read at its own repeat of where the
|
|
6
|
+
* layers are laid: the mesh's coordinates, or the world's horizontal plane where the material has a
|
|
7
|
+
* projection (`worldUv.ts`). The mask's channels weigh layers 1 to 4 — red, green, blue, alpha — and
|
|
8
|
+
* come from a map read where a shading model's would be, from the ORM array's layer past the layers,
|
|
9
|
+
* or from the vertex colour; an added mask placed as the layers are raises one layer's weight, and
|
|
10
|
+
* one layer may be weighed by how much the surface faces up instead of by its channel.
|
|
11
|
+
*
|
|
12
|
+
* **Two ways to combine them.** Laid over, each layer covers everything before it, so a layer's
|
|
13
|
+
* share is its weight times one less each later weight, and the base keeps what none took. Summed,
|
|
14
|
+
* the base is mixed toward each layer times its weight by the weights' sum held to 1, so each
|
|
15
|
+
* layer's share is its weight times that and the base keeps the rest. The shares are worked out once
|
|
16
|
+
* a pixel, in `layeredBegin`; colour, roughness and occlusion are blended by them, and the normal
|
|
17
|
+
* map's tangent-space texels too, before the frame turns them into the world — around a normal read
|
|
18
|
+
* at the mesh's own coordinates, where the material lays one under the layers.
|
|
19
|
+
*
|
|
20
|
+
* **Every layer present is read, whatever its share.** A read whose level is implicit is legal only
|
|
21
|
+
* in uniform control flow (AGENTS.md 2026-08-07), so the loop is bounded by the layer count, a
|
|
22
|
+
* uniform, and never skips on a share, which is the pixel's own.
|
|
23
|
+
*
|
|
24
|
+
* **A lit switch, `LAYERED`**, off until a material first asks, declared last so every switch before
|
|
25
|
+
* it keeps its id. Compiled out, its four vectors are not declared and its calls are constants.
|
|
26
|
+
*/
|
|
27
|
+
export function layeredGlsl(on: boolean): string {
|
|
28
|
+
if (!on) {
|
|
29
|
+
return /* glsl */ `
|
|
30
|
+
const bool LAYERED = false; // wgsl:override
|
|
31
|
+
bool layered() { return false; }
|
|
32
|
+
void layeredBegin() {}
|
|
33
|
+
vec3 layeredColor(vec3 color) { return color; }
|
|
34
|
+
vec4 layeredAlbedo() { return vec4(1.0); }
|
|
35
|
+
vec3 layeredOrm() { return vec3(1.0); }
|
|
36
|
+
vec3 layeredTexel() { return vec3(0.0, 0.0, 1.0); }
|
|
37
|
+
bool layeredMeshNormal() { return false; }
|
|
38
|
+
vec3 layeredMeshTexel() { return vec3(0.0, 0.0, 1.0); }
|
|
39
|
+
bool layeredGlows() { return false; }
|
|
40
|
+
vec3 layeredEmissive() { return vec3(0.0); }
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
return /* glsl */ `
|
|
44
|
+
/* Whether this program blends a material's layers. Off but where one has asked: layered.ts. */
|
|
45
|
+
const bool LAYERED = true; // wgsl:override
|
|
46
|
+
/*
|
|
47
|
+
* Four vectors, as surfaceLayers.ts packs them: the five repeats, base first (x y z w, then x); the
|
|
48
|
+
* count (y), the glowing layer or -1 (z) and where the mask is, 0 a map, 1 the ORM array, 2 the
|
|
49
|
+
* vertex colour (w); the blend, 0 over and 1 summed (x), whether a mesh normal is laid under the
|
|
50
|
+
* layers (y), the added mask's layer or -1 (z) and its repeat (w); its intensity (x), the facing
|
|
51
|
+
* layer or -1 (y), its bias (z) and its sharpness (w).
|
|
52
|
+
*/
|
|
53
|
+
uniform vec4 uLayers[4]; // wgsl:material
|
|
54
|
+
|
|
55
|
+
/* Each layer's share of this point, the base's and layers 1 to 4's: worked out once, in layeredBegin. */
|
|
56
|
+
float lShareBase;
|
|
57
|
+
vec4 lShare;
|
|
58
|
+
|
|
59
|
+
bool layered() {
|
|
60
|
+
return uLayers[1].y > 0.5;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
float layerRepeat(int i) {
|
|
64
|
+
return i < 4 ? uLayers[0][i] : uLayers[1].x;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Where the layers are laid: the world's horizontal plane under a projection, the mesh's otherwise. */
|
|
68
|
+
vec2 layerPlace() {
|
|
69
|
+
if (WORLD_UVS && projected()) return planarAt();
|
|
70
|
+
return vUv.xy;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
vec3 layerAt(int i) {
|
|
74
|
+
return vec3(layerPlace() * layerRepeat(i), float(i));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* The weights as the material keeps them: the mask's channels, at the mesh's own coordinates. */
|
|
78
|
+
vec4 layerWeights() {
|
|
79
|
+
float source = uLayers[1].w;
|
|
80
|
+
if (source > 1.5) return vec4(vColor, 0.0);
|
|
81
|
+
if (source > 0.5) return texture(uOrmMap, vec3(vUv.xy, uLayers[1].y));
|
|
82
|
+
return texture(uModelMap, vec3(vUv.xy, 0.0));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Which of layers 1 to 4 a packed layer number names, as a vector of one 1: none for -1. */
|
|
86
|
+
vec4 layerPick(float layer) {
|
|
87
|
+
return vec4(equal(vec4(layer), vec4(1.0, 2.0, 3.0, 4.0)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* The weights, changed where the material asks and zero past the count, and every share from them.
|
|
92
|
+
* Under a branch on uniforms, as its reads are.
|
|
93
|
+
*/
|
|
94
|
+
void layeredBegin() {
|
|
95
|
+
vec4 m = layerWeights();
|
|
96
|
+
if (uLayers[2].z > 0.5) {
|
|
97
|
+
/* The ORM array's layer past the layers, and past the mask where the array carries that too. */
|
|
98
|
+
float at = uLayers[1].y + (abs(uLayers[1].w - 1.0) < 0.5 ? 1.0 : 0.0);
|
|
99
|
+
float added = texture(uOrmMap, vec3(layerPlace() * uLayers[2].w, at)).r * uLayers[3].x;
|
|
100
|
+
m = mix(m, clamp(m + added, 0.0, 1.0), layerPick(uLayers[2].z));
|
|
101
|
+
}
|
|
102
|
+
if (uLayers[3].y > 0.5) {
|
|
103
|
+
float up = normalize(vNormal).y * 0.5 + 0.5;
|
|
104
|
+
float facing = clamp(uLayers[3].z + uLayers[3].w * up, 0.0, 1.0);
|
|
105
|
+
m = mix(m, vec4(facing), layerPick(uLayers[3].y));
|
|
106
|
+
}
|
|
107
|
+
float n = uLayers[1].y;
|
|
108
|
+
m *= vec4(n > 1.5 ? 1.0 : 0.0, n > 2.5 ? 1.0 : 0.0, n > 3.5 ? 1.0 : 0.0, n > 4.5 ? 1.0 : 0.0);
|
|
109
|
+
if (uLayers[2].x > 0.5) {
|
|
110
|
+
float taken = min(m.x + m.y + m.z + m.w, 1.0);
|
|
111
|
+
lShare = m * taken;
|
|
112
|
+
lShareBase = 1.0 - taken;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
/* Each over everything before it: a layer keeps its weight of what every later one leaves. */
|
|
116
|
+
float keep = 1.0;
|
|
117
|
+
lShare.w = m.w * keep;
|
|
118
|
+
keep *= 1.0 - m.w;
|
|
119
|
+
lShare.z = m.z * keep;
|
|
120
|
+
keep *= 1.0 - m.z;
|
|
121
|
+
lShare.y = m.y * keep;
|
|
122
|
+
keep *= 1.0 - m.y;
|
|
123
|
+
lShare.x = m.x * keep;
|
|
124
|
+
keep *= 1.0 - m.x;
|
|
125
|
+
lShareBase = keep;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
float layerShare(int i) {
|
|
129
|
+
if (i == 0) return lShareBase;
|
|
130
|
+
return lShare[i - 1];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* The vertex colour's part in the albedo: none where it is the mask instead. */
|
|
134
|
+
vec3 layeredColor(vec3 color) {
|
|
135
|
+
return uLayers[1].w > 1.5 ? vec3(1.0) : color;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Colour by the shares, and the base layer's coverage, which a cutout reads. */
|
|
139
|
+
vec4 layeredAlbedo() {
|
|
140
|
+
vec4 sum = vec4(0.0);
|
|
141
|
+
for (int i = 0; i < 5; i++) {
|
|
142
|
+
if (float(i) >= uLayers[1].y) break;
|
|
143
|
+
vec4 t = texture(uAlbedo, layerAt(i));
|
|
144
|
+
sum.rgb += t.rgb * layerShare(i);
|
|
145
|
+
if (i == 0) sum.a = t.a;
|
|
146
|
+
}
|
|
147
|
+
return sum;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
vec3 layeredOrm() {
|
|
151
|
+
vec3 sum = vec3(0.0);
|
|
152
|
+
for (int i = 0; i < 5; i++) {
|
|
153
|
+
if (float(i) >= uLayers[1].y) break;
|
|
154
|
+
sum += texture(uOrmMap, layerAt(i)).rgb * layerShare(i);
|
|
155
|
+
}
|
|
156
|
+
return sum;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* A tangent-space texel off the normal array, its z rebuilt where a two-channel map stored none. */
|
|
160
|
+
vec3 layerNormalTexel(vec3 at) {
|
|
161
|
+
vec3 t = texture(uNormalMap, at).xyz * 2.0 - 1.0;
|
|
162
|
+
t.z = t.z > 0.0 ? t.z : sqrt(max(0.0, 1.0 - dot(t.xy, t.xy)));
|
|
163
|
+
return t;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* The tangent-space texels blended by the shares. */
|
|
167
|
+
vec3 layeredTexel() {
|
|
168
|
+
vec3 sum = vec3(0.0);
|
|
169
|
+
for (int i = 0; i < 5; i++) {
|
|
170
|
+
if (float(i) >= uLayers[1].y) break;
|
|
171
|
+
sum += layerNormalTexel(layerAt(i)) * layerShare(i);
|
|
172
|
+
}
|
|
173
|
+
float len = length(sum);
|
|
174
|
+
return len > 1e-5 ? sum / len : vec3(0.0, 0.0, 1.0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Whether a normal read at the mesh's own coordinates lies under the layers. */
|
|
178
|
+
bool layeredMeshNormal() {
|
|
179
|
+
return uLayers[2].y > 0.5;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* That normal's texel: the normal array's layer past the layers, at the mesh's coordinates. */
|
|
183
|
+
vec3 layeredMeshTexel() {
|
|
184
|
+
return layerNormalTexel(vec3(vUv.xy, uLayers[1].y));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Whether the emissive map belongs to one layer rather than to the whole surface. */
|
|
188
|
+
bool layeredGlows() {
|
|
189
|
+
return uLayers[1].z > -0.5;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* The emissive map at its layer's place, glowing by that layer's share. */
|
|
193
|
+
vec3 layeredEmissive() {
|
|
194
|
+
int glow = int(uLayers[1].z + 0.5);
|
|
195
|
+
return texture(uEmissiveMap, layerAt(glow)).rgb * layerShare(glow);
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
@@ -7,6 +7,17 @@ export const MAIN_GLSL = `void main() {
|
|
|
7
7
|
* turns a float into an array index. A plain texture is an array of one, so layer 0.
|
|
8
8
|
*/
|
|
9
9
|
vec3 surfaceAt = vec3(vUv.xy, floor(vUv.z + 0.5));
|
|
10
|
+
/*
|
|
11
|
+
* The coordinates a map's frame and its cutout's level are measured along: the mesh's, or the
|
|
12
|
+
* world's where the material projects its maps (worldUv.ts) — in which case the mesh's tangents
|
|
13
|
+
* no longer run along them either. Triplanar reads its own three planes below; every other
|
|
14
|
+
* reader of surfaceAt takes the horizontal plane.
|
|
15
|
+
*/
|
|
16
|
+
vec2 frameAt = vUv.xy;
|
|
17
|
+
int frameTangents = vHasTangents;
|
|
18
|
+
projectSurface(surfaceAt, frameAt, frameTangents);
|
|
19
|
+
/* A layered material's mask, read once for every layer's share below: layered.ts. */
|
|
20
|
+
if (LAYERED && layered()) layeredBegin();
|
|
10
21
|
if (dot(vec4(vWorldPos, 1.0), uClipPlane) < 0.0) discard;
|
|
11
22
|
/*
|
|
12
23
|
* The layer's surface effects, zeros where the albedo array carries no table — see
|
|
@@ -26,6 +37,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
26
37
|
vec2 fxDx = dFdx(surfaceAt.xy);
|
|
27
38
|
vec2 fxDy = dFdy(surfaceAt.xy);
|
|
28
39
|
if (uWriteMode.y != 0.0 && !ditherKeeps(uWriteMode.y)) discard;
|
|
40
|
+
/* An opacity spent on the screen door instead of the blend: preamble.ts. 0 keeps nothing. */
|
|
41
|
+
if (uWriteMode.z != 0.0 && ditherThreshold() >= vAlpha) discard;
|
|
29
42
|
/* A draw's dissolve cuts the surface away before anything is shaded: overlay.ts. */
|
|
30
43
|
if (SURFACE_OVERLAY) {
|
|
31
44
|
if (overlayCuts(surfaceAt.xy)) discard;
|
|
@@ -63,14 +76,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
63
76
|
*/
|
|
64
77
|
float coverage = 1.0;
|
|
65
78
|
if (uMaterialFlags.x != 0) {
|
|
66
|
-
vec4 texel =
|
|
79
|
+
vec4 texel = albedoTexel(surfaceAt);
|
|
67
80
|
// Discarded before anything else is computed: a thrown-away fragment should not pay
|
|
68
81
|
// for the lighting it will never contribute to. The test credits alpha for the mip level
|
|
69
82
|
// it samples, so a needle averaged thin down the chain is not lost; see cutoutCoverage.ts.
|
|
70
83
|
// Behind a branch on a uniform, so a material with no cutoff pays nothing for it.
|
|
71
84
|
float kept = texel.a;
|
|
72
85
|
if (uCutout.x > 0.0) {
|
|
73
|
-
float tested = cutoutAlpha(texel.a,
|
|
86
|
+
float tested = cutoutAlpha(texel.a, frameAt * vec2(textureSize(uAlbedo, 0).xy));
|
|
74
87
|
if (uCutout.y == 0.0) {
|
|
75
88
|
if (tested < uCutout.x) discard;
|
|
76
89
|
} else {
|
|
@@ -89,6 +102,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
89
102
|
}
|
|
90
103
|
}
|
|
91
104
|
}
|
|
105
|
+
/* A layered material whose mask is the vertex colour takes no tint from it: layered.ts. */
|
|
106
|
+
if (LAYERED && layered()) albedo = layeredColor(albedo);
|
|
92
107
|
albedo *= texel.rgb;
|
|
93
108
|
coverage = kept;
|
|
94
109
|
}
|
|
@@ -115,7 +130,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
115
130
|
float ormRoughness = vRoughness;
|
|
116
131
|
float metal = 0.0;
|
|
117
132
|
if (uMaterialFlags.y != 0) {
|
|
118
|
-
vec3 t =
|
|
133
|
+
vec3 t = ormTexel(surfaceAt);
|
|
119
134
|
ormOcclusion = mix(1.0, t.r, uOrmScale.r);
|
|
120
135
|
ormRoughness = clamp(t.g * uOrmScale.g, 0.0, 1.0);
|
|
121
136
|
metal = clamp(t.b * uOrmScale.b, 0.0, 1.0);
|
|
@@ -156,6 +171,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
156
171
|
vec3 glassGlow = vec3(0.0);
|
|
157
172
|
float glassTransmission = uSeeThrough.z;
|
|
158
173
|
float glassFrost = uSeeThrough.w;
|
|
174
|
+
/*
|
|
175
|
+
* **A thin surface lets light from behind through, coloured by itself**: a banner, a leaf, a
|
|
176
|
+
* lampshade, an opaque material's \`diffuseTransmission\`, in the glass tint's spare lane. It
|
|
177
|
+
* gathers the light behind it in glassGlow, as a pane does, and adds it through its own colour,
|
|
178
|
+
* or through the \`transmissionColor\` it names in the tint's colour lanes (transmission.ts).
|
|
179
|
+
* None on glass, which has its own way through.
|
|
180
|
+
*/
|
|
181
|
+
float thin = glassTransmission > 0.0 ? 0.0 : uGlassTint.w;
|
|
182
|
+
bool seesBehind = glassTransmission > 0.0 || thin > 0.0;
|
|
159
183
|
|
|
160
184
|
/*
|
|
161
185
|
* The normal a refracting draw bends by: the shading normal, a normal map's tilt and every relief
|
|
@@ -197,7 +221,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
197
221
|
* below. The rule is about non-uniform branches, and this branch is on a uniform.
|
|
198
222
|
*/
|
|
199
223
|
vec3 unmapped = n;
|
|
200
|
-
if (uNormalStrength > 0.0) n = normalMapped(n, surfaceAt);
|
|
224
|
+
if (uNormalStrength > 0.0) n = normalMapped(n, surfaceAt, frameAt, frameTangents);
|
|
201
225
|
/* A draw's wrinkles, by region, over whatever the map made: overlay.ts. */
|
|
202
226
|
if (SURFACE_OVERLAY) n = overlayWrinkle(n, unmapped, surfaceAt.xy);
|
|
203
227
|
|
|
@@ -368,6 +392,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
368
392
|
*/
|
|
369
393
|
shadingNormal = n;
|
|
370
394
|
if (backFace) n = -n;
|
|
395
|
+
/* A blending rim, on the base colour every light below reads: overlay.ts. */
|
|
396
|
+
if (SURFACE_OVERLAY) albedo = overlayRimBase(albedo, surfaceAt.xy, n);
|
|
371
397
|
/*
|
|
372
398
|
* A model's surface, gathered once, on a branch on the switches alone (models.ts) — and whether
|
|
373
399
|
* there is one, asked once: every site below is a branch on these two, which a pipeline with no
|
|
@@ -450,7 +476,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
450
476
|
float sunFacing = smoothstep(0.0, 0.02, surfaceNdl);
|
|
451
477
|
float direct = ndl * sunShade * sunFacing;
|
|
452
478
|
/* Glass: the sun on the far side of a pane lights it through the glass. See glassGlow. */
|
|
453
|
-
if (
|
|
479
|
+
if (seesBehind) glassGlow += sunColor * max(-surfaceNdl, 0.0) * sunShade;
|
|
454
480
|
|
|
455
481
|
/*
|
|
456
482
|
* Hemispheric: sky above, ground below, mixed by which way the surface looks. An
|
|
@@ -1302,8 +1328,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1302
1328
|
* the glass, shaped and shadowed like any lamp, and is added to glassGlow once its shape is
|
|
1303
1329
|
* known below. For everything else a lamp behind the surface is skipped here, as always.
|
|
1304
1330
|
*/
|
|
1305
|
-
float backNdl =
|
|
1306
|
-
glassTransmission > 0.0 ? max(-dot(n, toLight / max(dist, 1e-4)), 0.0) : 0.0;
|
|
1331
|
+
float backNdl = seesBehind ? max(-dot(n, toLight / max(dist, 1e-4)), 0.0) : 0.0;
|
|
1307
1332
|
/* A model that sees light from behind keeps the lamps behind the surface. See models.ts. */
|
|
1308
1333
|
if (ndl <= 0.0 && backNdl <= 0.0 && !modelledBehind) continue;
|
|
1309
1334
|
|
|
@@ -1323,7 +1348,9 @@ export const MAIN_GLSL = `void main() {
|
|
|
1323
1348
|
#if POINT_SHADOWS
|
|
1324
1349
|
float grazing = 1.0 - ndl;
|
|
1325
1350
|
float normalOffset = (0.06 + dist * 0.05) * grazing * grazing + 0.02;
|
|
1326
|
-
|
|
1351
|
+
/* Toward a lamp behind a thin surface, which is in the map itself: away from it, the surface
|
|
1352
|
+
would stand in its own shadow. A pane is in no map, so glass keeps the usual side. */
|
|
1353
|
+
vec3 receiverPoint = vWorldPos + (thin > 0.0 && ndl <= 0.0 ? -n : n) * normalOffset;
|
|
1327
1354
|
|
|
1328
1355
|
/*
|
|
1329
1356
|
* uPointShadowWeight is how present the cubemap is, and it is the difference
|
|
@@ -1699,7 +1726,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1699
1726
|
float signedForm = areaSignedForm(n, seenCentre, right, up, seenHalf);
|
|
1700
1727
|
float form = mix(max(0.0, signedForm), abs(signedForm), uAreaLightTwoSided[a]);
|
|
1701
1728
|
/* The form factor is linear in the normal, so the one seen from behind is its negation. */
|
|
1702
|
-
float backForm =
|
|
1729
|
+
float backForm = seesBehind
|
|
1703
1730
|
? mix(max(0.0, -signedForm), abs(signedForm), uAreaLightTwoSided[a])
|
|
1704
1731
|
: 0.0;
|
|
1705
1732
|
if (form <= 0.0 && backForm <= 0.0) continue;
|
|
@@ -1873,7 +1900,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1873
1900
|
* \`driftLight.ts\`.
|
|
1874
1901
|
*/
|
|
1875
1902
|
if (driftShare > 0.0) {
|
|
1876
|
-
if (
|
|
1903
|
+
if (seesBehind) {
|
|
1877
1904
|
glassGlow += driftLightIrradiance(vWorldPos, -n) * (driftShare * uDriftLight.w);
|
|
1878
1905
|
}
|
|
1879
1906
|
/* The standard expression in its own order — albedo times the field, then the share — so a
|
|
@@ -1933,6 +1960,12 @@ export const MAIN_GLSL = `void main() {
|
|
|
1933
1960
|
* underneath: a ceiling emitting a dull warm haze over a pale panel cannot be expressed
|
|
1934
1961
|
* by scaling the panel, and scaling it gets the brightness right and the hue wrong.
|
|
1935
1962
|
*/
|
|
1963
|
+
/* What a thin surface lets through from behind, in its own colour or the one it names (below
|
|
1964
|
+
zero: none named, transmission.ts); a metal lets none. */
|
|
1965
|
+
if (thin > 0.0) {
|
|
1966
|
+
vec3 through = uGlassTint.r < 0.0 ? albedo : uGlassTint.rgb;
|
|
1967
|
+
lit += through * glassGlow * (thin * (1.0 - metal));
|
|
1968
|
+
}
|
|
1936
1969
|
diffuseAlone = lit;
|
|
1937
1970
|
vec3 emissiveTint = vEmissiveColor.r < 0.0 ? albedo : vEmissiveColor;
|
|
1938
1971
|
/*
|
|
@@ -1953,7 +1986,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1953
1986
|
*/
|
|
1954
1987
|
vec3 emissiveMapped = vec3(1.0);
|
|
1955
1988
|
if (uMaterialFlags.z != 0) {
|
|
1956
|
-
emissiveMapped =
|
|
1989
|
+
emissiveMapped = emissiveTexel(surfaceAt) * uEmissiveScale;
|
|
1957
1990
|
}
|
|
1958
1991
|
/* Pulse, flicker and fade from the layer's effects; exactly 1 where it names none. */
|
|
1959
1992
|
float fxGlow = fx4.z + fx5.x + fx5.w > 0.0 ? fxEmission(surfaceAt.z) : 1.0;
|
|
@@ -2097,7 +2130,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
2097
2130
|
* map here, on a branch on uniforms alone, which keeps its derivatives legal.
|
|
2098
2131
|
*/
|
|
2099
2132
|
vec3 refractN = shadingNormal;
|
|
2100
|
-
if (uLightingEnabled == 0 && uNormalStrength > 0.0) refractN = normalMapped(refractN, surfaceAt);
|
|
2133
|
+
if (uLightingEnabled == 0 && uNormalStrength > 0.0) refractN = normalMapped(refractN, surfaceAt, frameAt, frameTangents);
|
|
2101
2134
|
vec3 refractV = normalize(uCameraPos - vWorldPos);
|
|
2102
2135
|
ivec2 snapSize = textureSize(uRefractScene, 0);
|
|
2103
2136
|
vec2 screenUv = gl_FragCoord.xy / vec2(snapSize);
|
|
@@ -2134,8 +2167,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
2134
2167
|
* The glow of the lights behind it is added last, by how frosted it is.
|
|
2135
2168
|
*/
|
|
2136
2169
|
float fresnel = 0.04 + 0.96 * pow(1.0 - cosView, 5.0);
|
|
2137
|
-
shaded = mix(shaded, seen * uGlassTint, glassTransmission * (1.0 - fresnel)) +
|
|
2138
|
-
applyOutputTransform(glassGlow * uGlassTint * (glassFrost * glassTransmission) * (1.0 - fog));
|
|
2170
|
+
shaded = mix(shaded, seen * uGlassTint.rgb, glassTransmission * (1.0 - fresnel)) +
|
|
2171
|
+
applyOutputTransform(glassGlow * uGlassTint.rgb * (glassFrost * glassTransmission) * (1.0 - fog));
|
|
2139
2172
|
} else {
|
|
2140
2173
|
shaded = seen;
|
|
2141
2174
|
}
|
|
@@ -2147,9 +2180,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
2147
2180
|
* **It did not need MeshData.colors widened to four floats**, which is how the consumer who
|
|
2148
2181
|
* asked for it had costed the change -- widening colors moves every mesh producer, the
|
|
2149
2182
|
* container layout and every baked asset. Alpha is a factor on the draw's opacity, not a
|
|
2150
|
-
* component of its albedo, so it rides a lane instead and nothing existing moves.
|
|
2183
|
+
* component of its albedo, so it rides a lane instead and nothing existing moves. Where the draw
|
|
2184
|
+
* spends it on the screen door instead (\`uWriteMode.z\`), it was spent at the top and is 1 here.
|
|
2151
2185
|
*/
|
|
2152
|
-
float alpha = uOpacity * coverage * vAlpha;
|
|
2186
|
+
float alpha = uOpacity * coverage * (uWriteMode.z != 0.0 ? 1.0 : vAlpha);
|
|
2153
2187
|
|
|
2154
2188
|
/*
|
|
2155
2189
|
* **Order-independent transparency, when this draw is accumulating into it.**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each of a material's maps is read: a layered material's blend (`layered.ts`), the three
|
|
3
|
+
* planes of a triplanar projection (`worldUv.ts`), or the map itself at the surface's coordinates.
|
|
4
|
+
*
|
|
5
|
+
* **Functions rather than branches in `main`, for the bundle's sake.** The generated WGSL stores an
|
|
6
|
+
* item once however many of the sixteen permutations share it, and `main` differs in every one, so
|
|
7
|
+
* a line there is paid sixteen times and a line here once. Called from the same uniform branches the
|
|
8
|
+
* reads stood in, so their implicit levels are as legal as they were: AGENTS.md 2026-08-07.
|
|
9
|
+
*/
|
|
10
|
+
export const MAP_READS_GLSL = /* glsl */ `
|
|
11
|
+
/* The surface's coordinates moved onto the world's horizontal plane where a material projects them,
|
|
12
|
+
with the frame's coordinates and the mesh's tangents, which no longer run along them, set aside. */
|
|
13
|
+
void projectSurface(inout vec3 at, inout vec2 frameAt, inout int frameTangents) {
|
|
14
|
+
if (WORLD_UVS && projected()) {
|
|
15
|
+
at.xy = planarAt();
|
|
16
|
+
frameAt = at.xy;
|
|
17
|
+
frameTangents = 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
vec4 albedoTexel(vec3 at) {
|
|
22
|
+
if (LAYERED && layered()) return layeredAlbedo();
|
|
23
|
+
if (WORLD_UVS && triplanar()) return triplanarAlbedo(at.z);
|
|
24
|
+
return texture(uAlbedo, at);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
vec3 ormTexel(vec3 at) {
|
|
28
|
+
if (LAYERED && layered()) return layeredOrm();
|
|
29
|
+
if (WORLD_UVS && triplanar()) return triplanarOrm(at.z);
|
|
30
|
+
return texture(uOrmMap, at).rgb;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
vec3 emissiveTexel(vec3 at) {
|
|
34
|
+
if (LAYERED && layered() && layeredGlows()) return layeredEmissive();
|
|
35
|
+
if (WORLD_UVS && triplanar()) return triplanarEmissive(at.z);
|
|
36
|
+
return texture(uEmissiveMap, at).rgb;
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sun's moving layer read through a matrix of its own: the one the frame's dynamic shadow pass
|
|
3
|
+
* was drawn with, where that is not the static layer's.
|
|
4
|
+
*
|
|
5
|
+
* **Why a matrix of its own.** The static layer wants a square over the whole of what stands still,
|
|
6
|
+
* so distant roofs keep their shadows; what moves wants a tight one, so a character's shadow is
|
|
7
|
+
* sharp. One matrix for both is one of the two given up. A caller fits each as it likes and draws
|
|
8
|
+
* each pass with its own (`beginShadowPass(matrix, 'dynamic')`); the renderer keeps the dynamic
|
|
9
|
+
* pass's and the lit stage reads that layer through it, the receiver's position projected from the
|
|
10
|
+
* world here rather than carried in a second varying.
|
|
11
|
+
*
|
|
12
|
+
* **A lit switch, `MOVING_SUN`**, off until a frame's dynamic pass is drawn with a matrix the static
|
|
13
|
+
* layer does not share, then on for good. Declared beside the other lit switches, ahead of the
|
|
14
|
+
* shadow lookup that calls it; compiled out, the moving layer is read at the static layer's place,
|
|
15
|
+
* exactly as it always was, and its matrix and depth span are not declared at all.
|
|
16
|
+
*
|
|
17
|
+
* **What it gives up**: a receiver outside the static layer's square takes no sun shadow at all,
|
|
18
|
+
* moving casters included, since everything is faded by that square first; so the tight square
|
|
19
|
+
* belongs inside the wide one.
|
|
20
|
+
*/
|
|
21
|
+
export function movingSunGlsl(on: boolean): string {
|
|
22
|
+
if (!on) {
|
|
23
|
+
return /* glsl */ `
|
|
24
|
+
const bool MOVING_SUN = false; // wgsl:override
|
|
25
|
+
vec3 movingSunAt() { return vec3(0.0); }
|
|
26
|
+
float movingSunSpan() { return 1.0; }
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
return /* glsl */ `
|
|
30
|
+
/* Whether the moving layer has a matrix of its own. Off until a frame draws it with one: movingSun.ts. */
|
|
31
|
+
const bool MOVING_SUN = true; // wgsl:override
|
|
32
|
+
/* The dynamic shadow pass's world-to-light matrix, and the depth it spans in metres. */
|
|
33
|
+
uniform mat4 uMovingLightViewProj;
|
|
34
|
+
uniform float uMovingShadowDepthSpan;
|
|
35
|
+
|
|
36
|
+
/* The receiver in the moving layer's map: x and y across it, z its depth, all 0 to 1. */
|
|
37
|
+
vec3 movingSunAt() {
|
|
38
|
+
vec4 l = uMovingLightViewProj * vec4(vWorldPos, 1.0);
|
|
39
|
+
return l.xyz / l.w * 0.5 + 0.5;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
float movingSunSpan() {
|
|
43
|
+
return uMovingShadowDepthSpan;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
@@ -3,12 +3,32 @@
|
|
|
3
3
|
* lit block for every light and by an unlit refracting draw for the direction it bends, so a lit
|
|
4
4
|
* pane and an unlit distortion bend by one tilt rather than two transcriptions of it.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* The frame is built from `frameAt`, the coordinates the map was laid by — the mesh's, or the
|
|
7
|
+
* world's where a material projects them (`worldUv.ts`), with the mesh's tangents then set aside
|
|
8
|
+
* by `hasTangents`, since they run along coordinates the map no longer follows.
|
|
9
|
+
*
|
|
10
|
+
* Depth zero, after the tangent frame and the world's projection it calls; see `flatFrag`. **Only ever called under a branch
|
|
7
11
|
* on a uniform** — `uNormalStrength` and whatever gates its caller — which is what makes the
|
|
8
12
|
* derivatives inside `tangentFrame` and the implicit level of the read legal: AGENTS.md 2026-08-07.
|
|
9
13
|
*/
|
|
10
14
|
export const NORMAL_MAP_GLSL = /* glsl */ `
|
|
11
|
-
vec3 normalMapped(vec3 n, vec3 at) {
|
|
15
|
+
vec3 normalMapped(vec3 n, vec3 at, vec2 frameAt, int hasTangents) {
|
|
16
|
+
/* On three planes where the material projects them so: worldUv.ts. */
|
|
17
|
+
if (WORLD_UVS && triplanar()) return triplanarNormal(n, at.z);
|
|
18
|
+
/*
|
|
19
|
+
* A layered material's texels, blended by its mask before the frame turns them, around a normal
|
|
20
|
+
* read at the mesh's own coordinates and tangents where the material lays one under them, which is
|
|
21
|
+
* the surface's large shape the layers' detail rides on: layered.ts.
|
|
22
|
+
*/
|
|
23
|
+
if (LAYERED && layered()) {
|
|
24
|
+
vec3 under = n;
|
|
25
|
+
if (layeredMeshNormal()) {
|
|
26
|
+
mat3 meshFrame = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
27
|
+
under = normalize(meshFrame * layeredMeshTexel());
|
|
28
|
+
}
|
|
29
|
+
mat3 layeredFrame = tangentFrame(under, vWorldPos, frameAt, vTangent, hasTangents);
|
|
30
|
+
return normalize(mix(n, normalize(layeredFrame * layeredTexel()), uNormalStrength));
|
|
31
|
+
}
|
|
12
32
|
vec3 mapped = texture(uNormalMap, at).xyz * 2.0 - 1.0;
|
|
13
33
|
/*
|
|
14
34
|
* z rebuilt where none is stored. A tangent-space normal points out of its surface, so a
|
|
@@ -19,7 +39,7 @@ vec3 normalMapped(vec3 n, vec3 at) {
|
|
|
19
39
|
* lying flat has its z near zero either way. A texel with z above zero is left as it was.
|
|
20
40
|
*/
|
|
21
41
|
mapped.z = mapped.z > 0.0 ? mapped.z : sqrt(max(0.0, 1.0 - dot(mapped.xy, mapped.xy)));
|
|
22
|
-
mat3 tbn = tangentFrame(n, vWorldPos,
|
|
42
|
+
mat3 tbn = tangentFrame(n, vWorldPos, frameAt, vTangent, hasTangents);
|
|
23
43
|
return normalize(mix(n, normalize(tbn * mapped), uNormalStrength));
|
|
24
44
|
}
|
|
25
45
|
`;
|