@driftengine/core 4.9.0 → 4.11.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/CHANGELOG.json +133 -11
- package/README.md +43 -38
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -4
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +189 -4
- package/dist/render/backend/webgl2/renderer.js +614 -66
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +24 -4
- package/dist/render/backend/webgpu/buffers.js +116 -59
- 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/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +20 -4
- package/dist/render/backend/webgpu/drawCommand.js +11 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +137 -20
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +28 -1
- package/dist/render/backend/webgpu/pipelineCache.js +46 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +200 -35
- package/dist/render/backend/webgpu/renderer.js +993 -168
- 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/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +242 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -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 +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +14 -0
- package/dist/render/mesh.js +44 -14
- package/dist/render/pointLightSelection.d.ts +4 -4
- 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/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- 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 +21 -0
- package/dist/render/shaders/flat/index.js +42 -7
- package/dist/render/shaders/flat/layered.d.ts +19 -0
- package/dist/render/shaders/flat/layered.js +115 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +58 -21
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/models.js +1 -1
- 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 +14 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +42 -20
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +59 -27
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3616 -3042
- package/dist/render/shaders/generated/flat.wgsl.js +3688 -3113
- 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/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceLayers.d.ts +39 -0
- package/dist/render/surfaceLayers.js +26 -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 +68 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/package.json +6 -6
- package/src/index.ts +12 -4
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +723 -79
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +195 -70
- 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/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +25 -4
- package/src/render/backend/webgpu/flatPass.ts +212 -41
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +53 -7
- package/src/render/backend/webgpu/renderer.ts +1201 -202
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +364 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +72 -14
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- 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 +63 -7
- package/src/render/shaders/flat/layered.ts +115 -0
- package/src/render/shaders/flat/main.ts +58 -21
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +14 -3
- package/src/render/shaders/flat/overlay.ts +42 -20
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +59 -27
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3688 -3113
- 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/outputTransform.ts +30 -14
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceLayers.ts +57 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +124 -27
- package/src/render/vertexPacking.ts +109 -0
- 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
|
@@ -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
|
+
`;
|
|
@@ -59,7 +59,7 @@ const bool PHYSICAL_SPECULAR = PHYSICAL_SPECULAR_ON; // wgsl:override
|
|
|
59
59
|
* A model's numbers, two vectors a material — the lit stage's uniform budget has no more room
|
|
60
60
|
* (surfaceModel.ts) — and whether a model map is bound, in the last component.
|
|
61
61
|
*/
|
|
62
|
-
uniform vec4 uModelParams[2];
|
|
62
|
+
uniform vec4 uModelParams[2]; // wgsl:material
|
|
63
63
|
/* The model's own channels, in the albedo's layout: what each means is each model's to say. */
|
|
64
64
|
uniform highp sampler2DArray uModelMap;
|
|
65
65
|
|
|
@@ -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,23 @@
|
|
|
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
|
+
/* A layered material's texels, blended by its mask before the frame turns them: layered.ts. */
|
|
19
|
+
if (LAYERED && layered()) {
|
|
20
|
+
mat3 layeredFrame = tangentFrame(n, vWorldPos, frameAt, vTangent, hasTangents);
|
|
21
|
+
return normalize(mix(n, normalize(layeredFrame * layeredTexel()), uNormalStrength));
|
|
22
|
+
}
|
|
12
23
|
vec3 mapped = texture(uNormalMap, at).xyz * 2.0 - 1.0;
|
|
13
24
|
/*
|
|
14
25
|
* z rebuilt where none is stored. A tangent-space normal points out of its surface, so a
|
|
@@ -19,7 +30,7 @@ vec3 normalMapped(vec3 n, vec3 at) {
|
|
|
19
30
|
* lying flat has its z near zero either way. A texel with z above zero is left as it was.
|
|
20
31
|
*/
|
|
21
32
|
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,
|
|
33
|
+
mat3 tbn = tangentFrame(n, vWorldPos, frameAt, vTangent, hasTangents);
|
|
23
34
|
return normalize(mix(n, normalize(tbn * mapped), uNormalStrength));
|
|
24
35
|
}
|
|
25
36
|
`;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A draw's surface overlay in the lit stage: the rim, the dissolve and the wrinkles
|
|
3
|
-
* `surfaceOverlay.ts` packs into `uOverlay`. Called from `main` at
|
|
4
|
-
* before anything is shaded, the wrinkles after the normal map,
|
|
3
|
+
* `surfaceOverlay.ts` packs into `uOverlay`. Called from `main` at four places — the dissolve's cut
|
|
4
|
+
* before anything is shaded, the wrinkles after the normal map, a blending rim on the base colour
|
|
5
|
+
* once the normal is final and before any light reads the colour, and the glow beside emission.
|
|
5
6
|
*
|
|
6
7
|
* **A lit switch, `SURFACE_OVERLAY`**, off until a draw first sets an overlay and then on for good,
|
|
7
8
|
* as `litSwitchesGlsl`'s are; declared after the models so every switch before it keeps its id.
|
|
@@ -19,6 +20,7 @@ export function overlayGlsl(on: boolean): string {
|
|
|
19
20
|
const bool SURFACE_OVERLAY = false; // wgsl:override
|
|
20
21
|
bool overlayCuts(vec2 uv) { return false; }
|
|
21
22
|
vec3 overlayWrinkle(vec3 mapped, vec3 unmapped, vec2 uv) { return mapped; }
|
|
23
|
+
vec3 overlayRimBase(vec3 base, vec2 uv, vec3 n) { return base; }
|
|
22
24
|
vec3 overlayGlow(vec2 uv, vec3 n) { return vec3(0.0); }
|
|
23
25
|
`;
|
|
24
26
|
}
|
|
@@ -26,7 +28,7 @@ vec3 overlayGlow(vec2 uv, vec3 n) { return vec3(0.0); }
|
|
|
26
28
|
/* Whether this program carries a draw's overlay. Off but where one has been set: overlay.ts. */
|
|
27
29
|
const bool SURFACE_OVERLAY = true; // wgsl:override
|
|
28
30
|
/* The overlay's fifteen vectors; packSurfaceOverlay in surfaceOverlay.ts says what each holds. */
|
|
29
|
-
uniform vec4 uOverlay[15];
|
|
31
|
+
uniform vec4 uOverlay[15]; // wgsl:material
|
|
30
32
|
|
|
31
33
|
/* Whether the atlas is what the refraction slot holds: not where this draw refracts or is glass. */
|
|
32
34
|
bool overlayImagesBound() {
|
|
@@ -84,8 +86,42 @@ vec2 overlayScreen() {
|
|
|
84
86
|
return vec2(eye.x / depth + uClusterFrustum.w, eye.y / depth + 1.0) * 0.5;
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/*
|
|
90
|
+
* The rim's weight here, 0 to 1 before the pulse and the mask: the edge leaning toward normals that
|
|
91
|
+
* face up, raised to the contrast, times the noise laid in screen space. Its strength is the
|
|
92
|
+
* caller's: an added rim takes it times its light, a blending one times its colour.
|
|
93
|
+
*/
|
|
94
|
+
float overlayRimWeight(vec2 uv, vec3 n) {
|
|
95
|
+
vec3 toEye = normalize(uCameraPos - vWorldPos);
|
|
96
|
+
float edge = pow(1.0 - clamp(dot(n, toEye), 0.0, 1.0), max(uOverlay[1].x, 1e-4));
|
|
97
|
+
float up = pow(clamp(n.y * 0.5 + 0.5, 0.0, 1.0), max(uOverlay[1].y, 1e-4));
|
|
98
|
+
float rim = pow(edge * up, max(uOverlay[1].z, 1e-4));
|
|
99
|
+
float time = uSurfaceScene.x;
|
|
100
|
+
if (uOverlay[2].x > 0.0 && overlayImagesBound()) {
|
|
101
|
+
vec2 at = overlayScreen() * uOverlay[3].z + uOverlay[3].xy * time;
|
|
102
|
+
rim *= overlayImage(uOverlay[2], at).r;
|
|
103
|
+
}
|
|
104
|
+
rim = min(rim, 1.0);
|
|
105
|
+
rim *= mix(uOverlay[4].x, uOverlay[4].y, 0.5 + 0.5 * sin(uOverlay[3].w * time));
|
|
106
|
+
if (uOverlay[5].x > 0.0 && overlayImagesBound()) {
|
|
107
|
+
rim *= 1.0 - uOverlay[1].w * overlayImage(uOverlay[5], uv).r;
|
|
108
|
+
}
|
|
109
|
+
return rim;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* A blending rim: the base colour pulled toward the rim's by the weight times the alpha, held at
|
|
114
|
+
* one, before any light reads it, so the edge is lit as the surface is. The colour arrives already
|
|
115
|
+
* held at one a channel. Nothing where the rim adds instead, or where there is none.
|
|
116
|
+
*/
|
|
117
|
+
vec3 overlayRimBase(vec3 base, vec2 uv, vec3 n) {
|
|
118
|
+
if (uOverlay[0].w <= 0.0 || uOverlay[4].z < 0.5) return base;
|
|
119
|
+
float weight = clamp(uOverlay[0].w * overlayRimWeight(uv, n), 0.0, 1.0);
|
|
120
|
+
return mix(base, uOverlay[0].rgb, weight);
|
|
121
|
+
}
|
|
122
|
+
|
|
87
123
|
/* What the overlay adds to the light the surface gives off: the dissolve's band, the colour laid
|
|
88
|
-
over through its noise, and
|
|
124
|
+
over through its noise, and an adding rim. Zero where the draw sets none of them. */
|
|
89
125
|
vec3 overlayGlow(vec2 uv, vec3 n) {
|
|
90
126
|
vec3 glow = vec3(0.0);
|
|
91
127
|
float noise = overlayDissolveNoise(uv);
|
|
@@ -94,22 +130,8 @@ vec3 overlayGlow(vec2 uv, vec3 n) {
|
|
|
94
130
|
glow += uOverlay[8].rgb * uOverlay[8].w * band + uOverlay[9].rgb * uOverlay[9].w * noise;
|
|
95
131
|
}
|
|
96
132
|
float strength = uOverlay[0].w;
|
|
97
|
-
if (strength > 0.0) {
|
|
98
|
-
|
|
99
|
-
float edge = pow(1.0 - clamp(dot(n, toEye), 0.0, 1.0), max(uOverlay[1].x, 1e-4));
|
|
100
|
-
float up = pow(clamp(n.y * 0.5 + 0.5, 0.0, 1.0), max(uOverlay[1].y, 1e-4));
|
|
101
|
-
float rim = pow(edge * up, max(uOverlay[1].z, 1e-4));
|
|
102
|
-
float time = uSurfaceScene.x;
|
|
103
|
-
if (uOverlay[2].x > 0.0 && overlayImagesBound()) {
|
|
104
|
-
vec2 at = overlayScreen() * uOverlay[3].z + uOverlay[3].xy * time;
|
|
105
|
-
rim *= overlayImage(uOverlay[2], at).r;
|
|
106
|
-
}
|
|
107
|
-
rim = min(rim, 1.0);
|
|
108
|
-
rim *= mix(uOverlay[4].x, uOverlay[4].y, 0.5 + 0.5 * sin(uOverlay[3].w * time));
|
|
109
|
-
if (uOverlay[5].x > 0.0 && overlayImagesBound()) {
|
|
110
|
-
rim *= 1.0 - uOverlay[1].w * overlayImage(uOverlay[5], uv).r;
|
|
111
|
-
}
|
|
112
|
-
glow += uOverlay[0].rgb * strength * rim;
|
|
133
|
+
if (strength > 0.0 && uOverlay[4].z < 0.5) {
|
|
134
|
+
glow += uOverlay[0].rgb * strength * overlayRimWeight(uv, n);
|
|
113
135
|
}
|
|
114
136
|
return glow;
|
|
115
137
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* picture and is its own change.
|
|
14
14
|
*/
|
|
15
15
|
import { MAX_SHADOW_FILTER_TAPS } from '../../renderQuality.ts';
|
|
16
|
-
import {
|
|
16
|
+
import { LIT_OUTPUT_TRANSFORM_GLSL } from '../outputTransform.ts';
|
|
17
17
|
import { LAMP_GLASS_GLSL } from './lampGlass.ts';
|
|
18
18
|
|
|
19
19
|
export const POINTSHADOW_GLSL = `/**
|
|
@@ -344,7 +344,7 @@ ${LAMP_GLASS_GLSL}
|
|
|
344
344
|
|
|
345
345
|
layout(location = 0) out vec4 outColor;
|
|
346
346
|
|
|
347
|
-
${
|
|
347
|
+
${LIT_OUTPUT_TRANSFORM_GLSL}
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
350
|
* A fixed Poisson disk avoids the grid-shaped edge left by square 3x3 PCF.
|
|
@@ -121,9 +121,20 @@ uniform sampler2D uRefractScene;
|
|
|
121
121
|
* gives up, and four separate floats cost half the shadowed lights such a device can have. The tints
|
|
122
122
|
* stay colours of their own. Zero transmission is not glass — see glass.ts.
|
|
123
123
|
*/
|
|
124
|
-
uniform vec4 uSeeThrough;
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
uniform vec4 uSeeThrough; // wgsl:material
|
|
125
|
+
/*
|
|
126
|
+
* What a metre of the medium leaves of each channel (rgb), and in w which way this frame's rows run
|
|
127
|
+
* up the screen: +1 where the first row is the bottom (WebGL2), -1 where it is the top (WebGPU). The
|
|
128
|
+
* bend is taken in view space, whose y is up whatever the framebuffer says, so it is turned here to
|
|
129
|
+
* the rows a screen coordinate counts. A vec4 where a vec3 already spent the row.
|
|
130
|
+
*/
|
|
131
|
+
uniform vec4 uRefractTint; // wgsl:material
|
|
132
|
+
/*
|
|
133
|
+
* A pane's colour (rgb), and in w how much light from behind an opaque thin surface lets through:
|
|
134
|
+
* \`SurfaceMaterial.diffuseTransmission\`, read where the draw is not glass. A vec4 where a vec3
|
|
135
|
+
* already spent the row.
|
|
136
|
+
*/
|
|
137
|
+
uniform vec4 uGlassTint; // wgsl:material
|
|
127
138
|
flat in int vHasTangents;
|
|
128
139
|
|
|
129
140
|
/**
|
|
@@ -154,7 +165,7 @@ uniform mediump sampler2DArray uAlbedo;
|
|
|
154
165
|
* card. Its draw culls nothing, and a back face is lit as a front, with the shading normal turned
|
|
155
166
|
* toward the viewer.
|
|
156
167
|
*/
|
|
157
|
-
uniform ivec4 uMaterialFlags;
|
|
168
|
+
uniform ivec4 uMaterialFlags; // wgsl:material
|
|
158
169
|
/**
|
|
159
170
|
* Alpha below which a textured fragment is thrown away entirely, 0 to disable.
|
|
160
171
|
*
|
|
@@ -168,13 +179,17 @@ uniform ivec4 uMaterialFlags;
|
|
|
168
179
|
* else changes.
|
|
169
180
|
*
|
|
170
181
|
* **Four numbers in one row**: x is the cutoff, y how this frame resolves the edge (0 the hard test,
|
|
171
|
-
* 1 a dither the temporal resolve averages, 2 alpha-to-coverage; see \`cutoutDither.ts\`), z
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
182
|
+
* 1 a dither the temporal resolve averages, 2 alpha-to-coverage; see \`cutoutDither.ts\`), z unused,
|
|
183
|
+
* w the material's lighting channels — a mask a light shades it only where its own shares a bit
|
|
184
|
+
* with (\`SurfaceMaterial.lightChannels\`, 1 unless a material names others). One vector rather than
|
|
185
|
+
* a float and three more, because every loose scalar is a row of WebGL2's 224 that the light budget
|
|
186
|
+
* gives up.
|
|
187
|
+
*
|
|
188
|
+
* **z was the frame the dither's pattern is offset by until 4.10.0**, and that made every material
|
|
189
|
+
* new every frame: a material is kept on WebGPU by its numbers, and one of them changed with the
|
|
190
|
+
* clock. The frame is the pass's, in \`uHighlightMin.w\`.
|
|
176
191
|
*/
|
|
177
|
-
uniform vec4 uCutout;
|
|
192
|
+
uniform vec4 uCutout; // wgsl:material
|
|
178
193
|
/**
|
|
179
194
|
* A draw's own ambient, as nine spherical-harmonic coefficients of incoming radiance, red green blue
|
|
180
195
|
* in turn, twenty-seven floats, and in the last of the twenty-eight a 1 where they are on — 0 is the
|
|
@@ -182,7 +197,7 @@ uniform vec4 uCutout;
|
|
|
182
197
|
* and the basis, and its tests hold this to it. Seven rows of the material block, which is per draw
|
|
183
198
|
* as far as anything here is: \`setAmbientSH\` moves it between two draws as \`setMaterial\` does.
|
|
184
199
|
*/
|
|
185
|
-
uniform vec4 uAmbientSH[7];
|
|
200
|
+
uniform vec4 uAmbientSH[7]; // wgsl:material
|
|
186
201
|
|
|
187
202
|
/** The ambient a surface facing \`n\` receives from \`uAmbientSH\`: \`ambientFromSH\`'s arithmetic. */
|
|
188
203
|
vec3 ambientHarmonics(vec3 n) {
|
|
@@ -229,7 +244,7 @@ uniform highp sampler2DArray uNormalMap;
|
|
|
229
244
|
* shadow samplers, where a profile with shadows off should declare none of them; it is the wrong
|
|
230
245
|
* tool for one image on a shader that already declares one.
|
|
231
246
|
*/
|
|
232
|
-
uniform float uNormalStrength;
|
|
247
|
+
uniform float uNormalStrength; // wgsl:material
|
|
233
248
|
|
|
234
249
|
/**
|
|
235
250
|
* Occlusion, roughness and metallic in one image. glTF's packing, because that is what an import
|
|
@@ -250,7 +265,7 @@ uniform highp sampler2DArray uOrmMap;
|
|
|
250
265
|
* occlusionTexture.strength is: 1 + strength * (texel - 1). A multiply at 0 means fully occluded,
|
|
251
266
|
* which is the opposite of what a caller asking for no occlusion means.
|
|
252
267
|
*/
|
|
253
|
-
uniform vec3 uOrmScale;
|
|
268
|
+
uniform vec3 uOrmScale; // wgsl:material
|
|
254
269
|
/**
|
|
255
270
|
* 1 when \`uEnvironment\` holds the GGX-prefiltered cube, 0 when it holds the capture's box chain.
|
|
256
271
|
*
|
|
@@ -266,7 +281,7 @@ uniform vec3 uOrmScale;
|
|
|
266
281
|
* (\`uniformVectorBudget.ts\`). y is how much of the environment the material mirrors, z how
|
|
267
282
|
* bright what it mirrors is.
|
|
268
283
|
*/
|
|
269
|
-
uniform vec3 uEnvironmentDials;
|
|
284
|
+
uniform vec3 uEnvironmentDials; // wgsl:material
|
|
270
285
|
|
|
271
286
|
/**
|
|
272
287
|
* Where a surface glows, and in what colour.
|
|
@@ -288,7 +303,7 @@ uniform highp sampler2DArray uEmissiveMap;
|
|
|
288
303
|
* The counterpart of \`uOrmScale\`: a factor that multiplies a texture is not a value, and this is
|
|
289
304
|
* where the multiplier lives once an image supplies the shape.
|
|
290
305
|
*/
|
|
291
|
-
uniform vec3 uEmissiveScale;
|
|
306
|
+
uniform vec3 uEmissiveScale; // wgsl:material
|
|
292
307
|
|
|
293
308
|
#if DIRECTIONAL_SHADOWS
|
|
294
309
|
/*
|
|
@@ -375,7 +390,7 @@ uniform vec4 uSurfaceScene;
|
|
|
375
390
|
* draw moves it), y the night factor emission is gated on. One row where two floats were two
|
|
376
391
|
* (\`uniformVectorBudget.ts\`).
|
|
377
392
|
*/
|
|
378
|
-
uniform vec2 uEmission;
|
|
393
|
+
uniform vec2 uEmission; // wgsl:material
|
|
379
394
|
#if NIGHT_EMISSIVE
|
|
380
395
|
/**
|
|
381
396
|
* Emission that only shows where the dominant light does not reach. 0 is off and is the default.
|
|
@@ -400,7 +415,7 @@ uniform float uNightEmissive;
|
|
|
400
415
|
* of that to change one channel is how a renderer ends up with two shaders that
|
|
401
416
|
* disagree about the sun. The caller owns the blend state; this owns the number.
|
|
402
417
|
*/
|
|
403
|
-
uniform float uOpacity;
|
|
418
|
+
uniform float uOpacity; // wgsl:material
|
|
404
419
|
|
|
405
420
|
/**
|
|
406
421
|
* Two per-draw switches on how a fragment is *written* rather than what colour it is, sharing one
|
|
@@ -431,17 +446,30 @@ uniform float uOpacity;
|
|
|
431
446
|
* the grain is mirrored between backends and nothing else about it differs. What it gives up: a
|
|
432
447
|
* crossfade reads as grain for as long as it lasts, which is why it should be short; and a blended
|
|
433
448
|
* draw that asks for it is dithered too, since the test is a discard.
|
|
449
|
+
*
|
|
450
|
+
* **\`.z\` — whether this draw spends its own opacity on the same screen door**: 1 keeps the cells
|
|
451
|
+
* under \`vAlpha\` (an instance's \`MeshInstances.alphas\`, a vertex's channel alpha) and blends none
|
|
452
|
+
* of it, so an opaque or cut-out draw can fade one instance at a time. 0 is off. **The same pattern
|
|
453
|
+
* as the fade, and that is the point**: a pixel is kept where its one threshold sits under both, so
|
|
454
|
+
* a crossfade at \`t\` and \`-t\` of an instance at opacity \`a\` still covers each pixel of the
|
|
455
|
+
* instance's share \`a\` once. Two independent patterns would leave holes where they disagree.
|
|
434
456
|
*/
|
|
435
|
-
uniform
|
|
457
|
+
uniform vec3 uWriteMode; // wgsl:material
|
|
436
458
|
|
|
437
|
-
/**
|
|
438
|
-
|
|
459
|
+
/** Where this fragment's cell of the 8×8 Bayer pattern falls, in (0, 1). Integer arithmetic,
|
|
460
|
+
per the device rule. */
|
|
461
|
+
float ditherThreshold() {
|
|
439
462
|
uvec2 p = uvec2(gl_FragCoord.xy) & 7u;
|
|
440
463
|
uint a = p.x ^ p.y;
|
|
441
464
|
uint b = p.y;
|
|
442
465
|
uint cell = ((a & 1u) << 5) | ((b & 1u) << 4) | ((a & 2u) << 2) | ((b & 2u) << 1) |
|
|
443
466
|
((a & 4u) >> 1) | ((b & 4u) >> 2);
|
|
444
|
-
|
|
467
|
+
return (float(cell) + 0.5) / 64.0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Whether this fragment is one \`uWriteMode.y\` keeps. */
|
|
471
|
+
bool ditherKeeps(float amount) {
|
|
472
|
+
float threshold = ditherThreshold();
|
|
445
473
|
return amount > 0.0 ? threshold < amount : threshold >= -amount;
|
|
446
474
|
}
|
|
447
475
|
|
|
@@ -465,7 +493,7 @@ bool ditherKeeps(float amount) {
|
|
|
465
493
|
* one uniform skips the untaken side rather than computing both and discarding one, which is
|
|
466
494
|
* only true because nothing here reads it through a *varying*-dependent branch.
|
|
467
495
|
*/
|
|
468
|
-
uniform int uLightingEnabled;
|
|
496
|
+
uniform int uLightingEnabled; // wgsl:material
|
|
469
497
|
/**
|
|
470
498
|
* How this draw meets the medium: the atmospheric haze and the underwater tint both, folded
|
|
471
499
|
* together because both are the *camera's* medium rather than a property of the surface — see
|
|
@@ -487,7 +515,7 @@ uniform int uLightingEnabled;
|
|
|
487
515
|
* is what "exactly as its colour says" has to mean for it; the cost is that such a draw will
|
|
488
516
|
* not tint if the camera goes under water, unlike everything else in the frame.
|
|
489
517
|
*/
|
|
490
|
-
uniform int uFogEnabled;
|
|
518
|
+
uniform int uFogEnabled; // wgsl:material
|
|
491
519
|
|
|
492
520
|
/**
|
|
493
521
|
* A pass-level scale over the grain the geometry declared, 0 to 1.
|
|
@@ -497,7 +525,7 @@ uniform int uFogEnabled;
|
|
|
497
525
|
* imported model that declares nothing is drawn with the pass's own answer, and a scene
|
|
498
526
|
* can dial a whole draw down without rebuilding its geometry.
|
|
499
527
|
*/
|
|
500
|
-
uniform float uGrain;
|
|
528
|
+
uniform float uGrain; // wgsl:material
|
|
501
529
|
/*
|
|
502
530
|
* The material's own relief: how strong, and how many bumps to a metre.
|
|
503
531
|
*
|
|
@@ -506,7 +534,7 @@ uniform float uGrain;
|
|
|
506
534
|
* between them, and the geometry carrying them is identical in all three cases. See
|
|
507
535
|
* Renderer.setSurfaceRelief. x is the strength and y the bumps to a metre, in one row.
|
|
508
536
|
*/
|
|
509
|
-
uniform vec2 uRelief;
|
|
537
|
+
uniform vec2 uRelief; // wgsl:material
|
|
510
538
|
/**
|
|
511
539
|
* How hard the bound surface texture's own luminance turns the shading normal, 0 for not at all.
|
|
512
540
|
*
|
|
@@ -529,7 +557,7 @@ uniform vec2 uRelief;
|
|
|
529
557
|
* 0..1 like its neighbours for the same reason: this is an amplitude against a luminance
|
|
530
558
|
* gradient, not a fraction of anything.
|
|
531
559
|
*/
|
|
532
|
-
uniform float uTextureRelief;
|
|
560
|
+
uniform float uTextureRelief; // wgsl:material
|
|
533
561
|
|
|
534
562
|
/**
|
|
535
563
|
* How much of the environment this material mirrors, 0 to 1.
|
|
@@ -664,8 +692,12 @@ uniform vec3 uProbeGridSets;
|
|
|
664
692
|
/**
|
|
665
693
|
* A world-space box that lights up regardless of time of day: its corners, and in the far corner's
|
|
666
694
|
* w the gain — one row where a loose float was another (\`uniformVectorBudget.ts\`).
|
|
695
|
+
*
|
|
696
|
+
* **And in the near corner's w the frame the cutout dither's pattern is offset by**, for the same
|
|
697
|
+
* reason: a row the lit stage already pays for, holding a number the pass sets and no material does.
|
|
698
|
+
* See \`uCutout\`.
|
|
667
699
|
*/
|
|
668
|
-
uniform
|
|
700
|
+
uniform vec4 uHighlightMin;
|
|
669
701
|
uniform vec4 uHighlightMax;
|
|
670
702
|
|
|
671
703
|
/*
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's maps placed by where a point is in the world rather than by the mesh's texture
|
|
3
|
+
* coordinates: `SurfaceMaterial.projection`, packed into `uWorldUv` by `surfaceProjection.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Planar** takes the two horizontal axes, `x` and `z`, at so many repeats a metre: a floor whose
|
|
6
|
+
* mesh carries no useful coordinates, or one coordinate for the whole of it, shows its texture at
|
|
7
|
+
* the size the material asks. **Triplanar** reads every map three times, on the planes across `x`,
|
|
8
|
+
* `y` and `z`, and blends them by how squarely the surface faces each — the geometric normal raised
|
|
9
|
+
* to a sharpness — so a wall, a cliff and a rock wear it as a floor does. A normal map on three
|
|
10
|
+
* planes is turned into the world by the whiteout blend, each plane's tangent-space normal added to
|
|
11
|
+
* the surface's own swizzled into that plane, which keeps the detail of each without a tangent
|
|
12
|
+
* frame per plane.
|
|
13
|
+
*
|
|
14
|
+
* **A lit switch, `WORLD_UVS`**, off until a material first asks and then on for good, declared
|
|
15
|
+
* last so every switch before it keeps its id. Compiled out, its vector is not declared and its
|
|
16
|
+
* calls are constants, so a scene that never asks pays nothing.
|
|
17
|
+
*
|
|
18
|
+
* **What it gives up**: a projection is the world's, so a mesh that moves slides under its own
|
|
19
|
+
* texture, which is right for ground and wrong for a crate; triplanar reads each map three times;
|
|
20
|
+
* and a map that is an array reads one layer for the whole surface, the one the face names.
|
|
21
|
+
*/
|
|
22
|
+
export function worldUvGlsl(on: boolean): string {
|
|
23
|
+
if (!on) {
|
|
24
|
+
return /* glsl */ `
|
|
25
|
+
const bool WORLD_UVS = false; // wgsl:override
|
|
26
|
+
bool projected() { return false; }
|
|
27
|
+
bool triplanar() { return false; }
|
|
28
|
+
vec2 planarAt() { return vec2(0.0); }
|
|
29
|
+
vec4 triplanarAlbedo(float layer) { return vec4(0.0); }
|
|
30
|
+
vec3 triplanarOrm(float layer) { return vec3(0.0); }
|
|
31
|
+
vec3 triplanarEmissive(float layer) { return vec3(0.0); }
|
|
32
|
+
vec3 triplanarNormal(vec3 n, float layer) { return n; }
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
return /* glsl */ `
|
|
36
|
+
/* Whether this program places maps by the world. Off but where a material has asked: worldUv.ts. */
|
|
37
|
+
const bool WORLD_UVS = true; // wgsl:override
|
|
38
|
+
/* x: 0 the mesh's coordinates, 1 planar, 2 triplanar; y: repeats a metre; z: the blend's sharpness. */
|
|
39
|
+
uniform vec4 uWorldUv; // wgsl:material
|
|
40
|
+
|
|
41
|
+
bool projected() {
|
|
42
|
+
return uWorldUv.x > 0.5;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
bool triplanar() {
|
|
46
|
+
return uWorldUv.x > 1.5;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* The horizontal axes, scaled: u along +x, v along +z. */
|
|
50
|
+
vec2 planarAt() {
|
|
51
|
+
return vWorldPos.xz * uWorldUv.y;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* How much each plane counts: the geometric normal's components, sharpened, summing to one. */
|
|
55
|
+
vec3 triplanarWeights() {
|
|
56
|
+
vec3 w = pow(abs(normalize(vNormal)), vec3(uWorldUv.z));
|
|
57
|
+
return w / max(w.x + w.y + w.z, 1e-5);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Each map read on the three planes and blended: a function a map rather than one taking the
|
|
62
|
+
* sampler, whose precision differs between them. Under a branch on a uniform, as every map read
|
|
63
|
+
* is, so the implicit level each read takes is legal: AGENTS.md 2026-08-07.
|
|
64
|
+
*/
|
|
65
|
+
vec4 triplanarAlbedo(float layer) {
|
|
66
|
+
vec3 w = triplanarWeights();
|
|
67
|
+
vec3 p = vWorldPos * uWorldUv.y;
|
|
68
|
+
return texture(uAlbedo, vec3(p.zy, layer)) * w.x + texture(uAlbedo, vec3(p.xz, layer)) * w.y +
|
|
69
|
+
texture(uAlbedo, vec3(p.xy, layer)) * w.z;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
vec3 triplanarOrm(float layer) {
|
|
73
|
+
vec3 w = triplanarWeights();
|
|
74
|
+
vec3 p = vWorldPos * uWorldUv.y;
|
|
75
|
+
return texture(uOrmMap, vec3(p.zy, layer)).rgb * w.x +
|
|
76
|
+
texture(uOrmMap, vec3(p.xz, layer)).rgb * w.y + texture(uOrmMap, vec3(p.xy, layer)).rgb * w.z;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
vec3 triplanarEmissive(float layer) {
|
|
80
|
+
vec3 w = triplanarWeights();
|
|
81
|
+
vec3 p = vWorldPos * uWorldUv.y;
|
|
82
|
+
return texture(uEmissiveMap, vec3(p.zy, layer)).rgb * w.x +
|
|
83
|
+
texture(uEmissiveMap, vec3(p.xz, layer)).rgb * w.y +
|
|
84
|
+
texture(uEmissiveMap, vec3(p.xy, layer)).rgb * w.z;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* A tangent-space normal off the map, its z rebuilt where a two-channel map stored none. */
|
|
88
|
+
vec3 triplanarTexel(vec2 at, float layer) {
|
|
89
|
+
vec3 t = texture(uNormalMap, vec3(at, layer)).xyz * 2.0 - 1.0;
|
|
90
|
+
t.z = t.z > 0.0 ? t.z : sqrt(max(0.0, 1.0 - dot(t.xy, t.xy)));
|
|
91
|
+
return t;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* The normal map on three planes, by the whiteout blend: each plane's texel added to the surface
|
|
96
|
+
* normal swizzled into that plane, its z scaled by the normal's own, and swizzled back. Each plane's
|
|
97
|
+
* coordinates run along the same world axes from both sides, so a texel's axes are the world's and
|
|
98
|
+
* nothing is flipped; what that gives up is a texture mirrored on a plane's negative side.
|
|
99
|
+
*/
|
|
100
|
+
vec3 triplanarNormal(vec3 n, float layer) {
|
|
101
|
+
vec3 w = triplanarWeights();
|
|
102
|
+
vec3 p = vWorldPos * uWorldUv.y;
|
|
103
|
+
vec3 tx = triplanarTexel(p.zy, layer);
|
|
104
|
+
vec3 ty = triplanarTexel(p.xz, layer);
|
|
105
|
+
vec3 tz = triplanarTexel(p.xy, layer);
|
|
106
|
+
tx = vec3(tx.xy + n.zy, abs(tx.z) * n.x);
|
|
107
|
+
ty = vec3(ty.xy + n.xz, abs(ty.z) * n.y);
|
|
108
|
+
tz = vec3(tz.xy + n.xy, abs(tz.z) * n.z);
|
|
109
|
+
vec3 blended = normalize(tx.zyx * w.x + ty.xzy * w.y + tz.xyz * w.z);
|
|
110
|
+
return normalize(mix(n, blended, uNormalStrength));
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
}
|