reze-engine 0.61.3 → 0.61.5

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.
Files changed (105) hide show
  1. package/dist/engine.d.ts +74 -9
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +260 -34
  4. package/dist/graph/presets/body.js +1 -1
  5. package/dist/graph/presets/cloth_rough.js +1 -1
  6. package/dist/graph/presets/cloth_smooth.js +1 -1
  7. package/dist/graph/presets/eye.d.ts.map +1 -1
  8. package/dist/graph/presets/eye.js +19 -0
  9. package/dist/graph/presets/face.js +1 -1
  10. package/dist/graph/presets/hair.js +1 -1
  11. package/dist/graph/presets/metal.js +1 -1
  12. package/dist/graph/presets/pool_floor.d.ts +3 -0
  13. package/dist/graph/presets/pool_floor.d.ts.map +1 -0
  14. package/dist/graph/presets/pool_floor.js +42 -0
  15. package/dist/graph/presets/water.d.ts +3 -0
  16. package/dist/graph/presets/water.d.ts.map +1 -0
  17. package/dist/graph/presets/water.js +31 -0
  18. package/dist/graph/registry.d.ts.map +1 -1
  19. package/dist/graph/registry.js +11 -0
  20. package/dist/graph/slots.d.ts.map +1 -1
  21. package/dist/graph/slots.js +19 -5
  22. package/dist/physics/autofit.d.ts +147 -0
  23. package/dist/physics/autofit.d.ts.map +1 -0
  24. package/dist/physics/autofit.js +501 -0
  25. package/dist/shaders/materials/common.d.ts.map +1 -1
  26. package/dist/shaders/materials/common.js +10 -9
  27. package/dist/shaders/materials/nodes.d.ts +1 -1
  28. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  29. package/dist/shaders/materials/nodes.js +12 -0
  30. package/dist/shaders/passes/field-blit.d.ts +26 -0
  31. package/dist/shaders/passes/field-blit.d.ts.map +1 -0
  32. package/dist/shaders/passes/field-blit.js +65 -0
  33. package/dist/shaders/passes/ground-noise.d.ts +7 -0
  34. package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
  35. package/dist/shaders/passes/ground-noise.js +88 -0
  36. package/dist/shaders/passes/particles.js +6 -6
  37. package/dist/shaders/passes/scene-contract.js +1 -1
  38. package/dist/shaders/passes/sim.d.ts +34 -0
  39. package/dist/shaders/passes/sim.d.ts.map +1 -0
  40. package/dist/shaders/passes/sim.js +169 -0
  41. package/dist/shaders/passes/subsurface.d.ts +34 -0
  42. package/dist/shaders/passes/subsurface.d.ts.map +1 -0
  43. package/dist/shaders/passes/subsurface.js +134 -0
  44. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  45. package/dist/shaders/scene-light-api.js +11 -0
  46. package/dist/shaders/score-api.d.ts +10 -0
  47. package/dist/shaders/score-api.d.ts.map +1 -0
  48. package/dist/shaders/score-api.js +114 -0
  49. package/dist/shaders/shadow-api.d.ts +29 -0
  50. package/dist/shaders/shadow-api.d.ts.map +1 -0
  51. package/dist/shaders/shadow-api.js +112 -0
  52. package/package.json +1 -1
  53. package/src/engine.ts +275 -36
  54. package/src/graph/presets/body.ts +1 -1
  55. package/src/graph/presets/cloth_rough.ts +1 -1
  56. package/src/graph/presets/cloth_smooth.ts +1 -1
  57. package/src/graph/presets/eye.ts +19 -0
  58. package/src/graph/presets/face.ts +1 -1
  59. package/src/graph/presets/hair.ts +1 -1
  60. package/src/graph/presets/metal.ts +1 -1
  61. package/src/graph/registry.ts +11 -0
  62. package/src/graph/slots.ts +19 -5
  63. package/src/shaders/materials/common.ts +10 -9
  64. package/src/shaders/materials/nodes.ts +12 -0
  65. package/src/shaders/passes/particles.ts +6 -6
  66. package/src/shaders/passes/scene-contract.ts +1 -1
  67. package/src/shaders/passes/subsurface.ts +136 -0
  68. package/src/shaders/scene-light-api.ts +11 -0
  69. package/dist/_dbg-loader.mjs +0 -1100
  70. package/dist/physics-debug.d.ts +0 -30
  71. package/dist/physics-debug.d.ts.map +0 -1
  72. package/dist/physics-debug.js +0 -526
  73. package/dist/shaders/materials/body.d.ts +0 -2
  74. package/dist/shaders/materials/body.d.ts.map +0 -1
  75. package/dist/shaders/materials/body.js +0 -95
  76. package/dist/shaders/materials/cloth_rough.d.ts +0 -2
  77. package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
  78. package/dist/shaders/materials/cloth_rough.js +0 -69
  79. package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
  80. package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
  81. package/dist/shaders/materials/cloth_smooth.js +0 -61
  82. package/dist/shaders/materials/default.d.ts +0 -2
  83. package/dist/shaders/materials/default.d.ts.map +0 -1
  84. package/dist/shaders/materials/default.js +0 -43
  85. package/dist/shaders/materials/eye.d.ts +0 -2
  86. package/dist/shaders/materials/eye.d.ts.map +0 -1
  87. package/dist/shaders/materials/eye.js +0 -60
  88. package/dist/shaders/materials/face.d.ts +0 -2
  89. package/dist/shaders/materials/face.d.ts.map +0 -1
  90. package/dist/shaders/materials/face.js +0 -95
  91. package/dist/shaders/materials/hair.d.ts +0 -2
  92. package/dist/shaders/materials/hair.d.ts.map +0 -1
  93. package/dist/shaders/materials/hair.js +0 -90
  94. package/dist/shaders/materials/metal.d.ts +0 -2
  95. package/dist/shaders/materials/metal.d.ts.map +0 -1
  96. package/dist/shaders/materials/metal.js +0 -77
  97. package/dist/shaders/materials/mmd_classic.d.ts +0 -2
  98. package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
  99. package/dist/shaders/materials/mmd_classic.js +0 -66
  100. package/dist/shaders/materials/stockings.d.ts +0 -2
  101. package/dist/shaders/materials/stockings.d.ts.map +0 -1
  102. package/dist/shaders/materials/stockings.js +0 -122
  103. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  104. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  105. package/dist/shaders/passes/physics-debug.js +0 -69
@@ -139,10 +139,18 @@ ${discard}
139
139
  ${gate}
140
140
  let l = -light.lights[0].direction.xyz;
141
141
  let sun = select(light.lights[0].color.xyz * light.lights[0].color.w, modelLight.sun.rgb, modelLight.sun.w > 0.5);
142
+ let shadow = sampleShadow(input.worldPos, n);
142
143
  // The world: flat colour, or the HDRI's irradiance at this normal — which
143
144
  // is what makes a loaded sky actually light her instead of only backing her.
144
- let amb = rzWorldAmbient(n) + modelLight.fill.rgb;
145
- let shadow = sampleShadow(input.worldPos, n);
145
+ // The model's fill is NOT here: see the epilogue.
146
+ //
147
+ // A model given a flat sky (setModelFlatSky - the cast, for an anime look)
148
+ // takes the sky's AVERAGE instead: its colour and brightness from every
149
+ // direction alike. The directional sky shaded a face like PBR - soft
150
+ // realistic gradients the toon ramp never drew - where the game gives its
151
+ // characters one flat base light. A flat World is its own average, so a
152
+ // scene without an HDRI renders exactly as before.
153
+ let amb = select(rzWorldAmbient(n), rzWorldAmbientAvg(), modelLight.opts.x > 0.5);
146
154
  let tex_color = tex_s.rgb;
147
155
  // The normal the lamps' diffuse layer is lit by, for a principled walk to
148
156
  // compute it on the way (rzLightsDiffuseOnce). n is final from here on.
@@ -186,7 +194,13 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
186
194
  // painted sheet — is Blender's Emission shader, which no lamp reaches; adding
187
195
  // the lamps to it turned X340's floor shadow, a white-RGB picture at a soft
188
196
  // alpha, into a glowing disc under the spot above it.
189
- const LIT = takesLight ? ` + rzLightsDiffuseOnce(input.worldPos, n) * albedo` : ""
197
+ //
198
+ // The model's fill (Engine.setModelFill) rides here too, as brightness: the
199
+ // surface colour times the fill, on every pixel of her alike. It used to
200
+ // enter the graph's ambient, where an NPR ramp read it as LIGHT — sliding it
201
+ // moved her shadows, and under a hard step flipped whole regions at once.
202
+ // After the graph it lifts her evenly and leaves every shadow where it is.
203
+ const LIT = takesLight ? ` + (rzLightsDiffuseOnce(input.worldPos, n) + modelLight.fill.rgb) * albedo` : ""
190
204
 
191
205
  const ALBEDO = ` let albedo = tex_color;
192
206
  `
@@ -206,13 +220,13 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
206
220
 
207
221
  var out: FSOut;
208
222
  out.color = vec4f(final_color${LIT}${BURN}, outAlpha);
209
- ${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
223
+ ${FOG} out.mask = vec4f(1.0, 1.0, _rzSubsurface, out.color.a);
210
224
  ${ID_WRITE} return out;
211
225
  `
212
226
  }
213
227
  return `${ALBEDO} var out: FSOut;
214
228
  out.color = vec4f(final_color${LIT}${BURN}, ${alphaBase});
215
- ${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
229
+ ${FOG} out.mask = vec4f(1.0, 1.0, _rzSubsurface, out.color.a);
216
230
  ${ID_WRITE} return out;
217
231
  `
218
232
  }
@@ -189,11 +189,12 @@ fn _rzCastShadow(wp: vec3f) -> f32 {
189
189
  // binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
190
190
  @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
191
191
  // The light this model takes apart from the scene's — see Engine.setModelFill
192
- // and Engine.setModelSun. fill is added to its ambient, zero for a model
193
- // nobody gave one; sun replaces the scene's sun colour while its w is set,
192
+ // and Engine.setModelSun. fill brightens it after its graph (fill ×
193
+ // surface colour, graph/slots.ts), zero for a model nobody gave one; sun replaces the scene's sun colour while its w is set,
194
194
  // which is how a stage keeps the daylight its game lit it by while the cast
195
- // keeps the key the scene set for them.
196
- struct ModelLight { fill: vec4f, sun: vec4f }
195
+ // keeps the key the scene set for them. opts.x (Engine.setModelFlatSky) gives
196
+ // it the sky's average in place of its shape - see graph/slots.ts.
197
+ struct ModelLight { fill: vec4f, sun: vec4f, opts: vec4f }
197
198
  @group(1) @binding(1) var<uniform> modelLight: ModelLight;
198
199
  @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
199
200
  @group(2) @binding(1) var<uniform> material: MaterialUniforms;
@@ -329,17 +330,17 @@ const COMMON_VS_WGSL = /* wgsl */ `
329
330
  // Location 0: final radiance+alpha (blended into rg11b10ufloat; the HDR target
330
331
  // has no alpha channel, but the blend equation still uses the .a you write here
331
332
  // as the src-alpha factor that premultiplies rgb into the HDR target).
332
- // Location 1: auxiliary rg8unorm carrying
333
+ // Location 1: auxiliary rgba8unorm carrying
333
334
  // .r = bloom mask (1 = contributes to bloom, 0 = skip — e.g. ground).
334
335
  // .g = accumulated canvas alpha — the channel that used to live in hdr.a
335
336
  // before the switch to rg11b10ufloat. Sampled by composite to
336
337
  // un-premultiply color for tonemap and to set the final drawable alpha
337
338
  // (needed for the `premultiplied` canvas alphaMode that blends the
338
339
  // WebGPU surface over the page background).
339
- // FS output at location 1 must be vec4f — the blend state references src.a, and
340
- // WebGPU requires the fragment output to provide an alpha component even though
341
- // the rg8unorm target only stores .r and .g (extra components are discarded).
342
- // Materials write mask = vec4f(1.0, 1.0, 0.0, color.a); ground writes
340
+ // .b = subsurface strength ÷ 4 — what a graph's `subsurface` node set, 0 for
341
+ // everything else. Read by the scattering pass (passes/subsurface.ts).
342
+ // FS output at location 1 must be vec4f — the blend state references src.a.
343
+ // Materials write mask = vec4f(1.0, 1.0, _rzSubsurface, color.a); ground writes
343
344
  // vec4f(0.0, 1.0, 0.0, edgeFade). With src.a coming from the 4th component and
344
345
  // src-alpha blending enabled:
345
346
  // out.r = mask_r · src.a + dst.r · (1-src.a) (bloom mask, weighted by alpha)
@@ -654,6 +654,18 @@ fn shader_to_rgb_diffuse(n: vec3f, l: vec3f, sun_rgb: vec3f, ambient_rgb: vec3f,
654
654
  return luminance_rec709_linear(shader_to_rgb_lit(n, l, sun_rgb, ambient_rgb, shadow));
655
655
  }
656
656
 
657
+ // ─── SUBSURFACE node ────────────────────────────────────────────────
658
+ // Marks this fragment for the screen-space scattering pass (passes/subsurface.ts)
659
+ // and passes its colour through. The strength lands in the aux target's .b via
660
+ // the epilogue, stored as a quarter so an 8-bit channel carries 0–4: 1 is
661
+ // ray-mmd's skin width, 0 leaves the pixel out of the pass.
662
+ var<private> _rzSubsurface: f32 = 0.0;
663
+
664
+ fn rz_subsurface(strength: f32, color: vec3f) -> vec3f {
665
+ _rzSubsurface = clamp(strength, 0.0, 4.0) * 0.25;
666
+ return color;
667
+ }
668
+
657
669
  // ─── BUMP node ──────────────────────────────────────────────────────
658
670
  // Screen-space bump from a scalar height field. Needs dFdx/dFdy which
659
671
  // WGSL provides as dpdx/dpdy.
@@ -391,13 +391,13 @@ struct FSOut {
391
391
  // writes it; a blended one writes all four, which is what not declaring it
392
392
  // would have meant.
393
393
  @builtin(sample_mask) samples: u32,
394
- // The scene's aux target: (bloom mask, coverage). Materials write it, so a
395
- // particle that skipped it would punch a hole in the mask of whatever it drew
396
- // over.
394
+ // The scene's aux target: (bloom mask, coverage, subsurface). Materials write
395
+ // it, so a particle that skipped it would punch a hole in the mask of whatever
396
+ // it drew over. A particle is never skin, so its .b is 0.
397
397
  //
398
- // vec4f even though the target is rg8unorm and only .rg land: that target's
399
- // blend factors reference SrcAlpha, and a fragment with no alpha channel is
400
- // rejected outright — "reading alpha but it is missing from fragment output".
398
+ // vec4f because that target's blend factors reference SrcAlpha, and a fragment
399
+ // with no alpha channel is rejected outright — "reading alpha but it is
400
+ // missing from fragment output".
401
401
  // The material shaders declare vec4f here for the same reason.
402
402
  @location(1) mask: vec4f,
403
403
  // Declared whenever the pass carries the attachment, and padded rather than
@@ -127,7 +127,7 @@ const ADD_KEEP_ALPHA: GPUBlendState = {
127
127
  alpha: { srcFactor: "zero", dstFactor: "one", operation: "add" },
128
128
  }
129
129
 
130
- /** Both channels sum. rg8unorm clamps at 1, which is the saturation alpha-over
130
+ /** Both channels sum. rgba8unorm clamps at 1, which is the saturation alpha-over
131
131
  * would have reached anyway. */
132
132
  const ADD_BOTH: GPUBlendState = {
133
133
  color: { srcFactor: "one", dstFactor: "one", operation: "add" },
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Screen-space subsurface scattering — ray-mmd's skin pass (PostProcessScattering.fxsub,
3
+ * SSSGaussBlurPS), ported constant for constant.
4
+ *
5
+ * A skin surface is not shaded by the light at its own pixel alone: light that
6
+ * enters a little way off comes back out here, red furthest. ray-mmd models that
7
+ * as a separable blur of the lit skin in screen space, one axis per pass, with a
8
+ * per-channel Gaussian (red widest) and a depth reject so light never crosses from
9
+ * a forearm onto the body behind it. The width is a length ON THE SURFACE, so it
10
+ * is divided by depth, and it narrows where the surface turns away from the view.
11
+ * Then a share of the unblurred picture is laid back over it — the "spike" that
12
+ * keeps pores, painted lines and the terminator from dissolving.
13
+ *
14
+ * What marks a pixel as skin is the scene pass's aux .b, written by the graph's
15
+ * `subsurface` node (0 everywhere else), as a quarter of its strength. The strength
16
+ * scales the radius, as ray-mmd's sssStrength does; 1 is ray-mmd's skin, 4 the most.
17
+ *
18
+ * One departure, and it is a routing one: ray-mmd blurs the lit colour BEFORE the
19
+ * albedo multiply and adds specular after. This engine resolves one HDR colour per
20
+ * pixel, so the blur runs over the finished colour. At this radius — a few pixels,
21
+ * with the spike keeping 14–20% of the original — the texture is not visibly
22
+ * softened; the lighting is.
23
+ *
24
+ * Two entry points share the module: X reads the HDR resolve and writes the
25
+ * scratch target, Y reads the scratch and writes back INTO the HDR resolve. The
26
+ * spike mix (lerp toward the original) is done by the Y pipeline's blend state,
27
+ * with the blend constant holding ray-mmd's per-channel spike: the destination IS
28
+ * the original, so no third copy of the frame is needed.
29
+ */
30
+
31
+ /** ray-mmd, skin profile (index 1): profileSpikeRadArr[1].xyz × (1 − sssAmount),
32
+ * with sssAmount = customA = 0.6 in Materials/Skin/material_skin.fx. */
33
+ export const SSS_SPIKE: [number, number, number] = [0.35 * 0.4, 0.4 * 0.4, 0.5 * 0.4]
34
+
35
+ export const SUBSURFACE_WGSL = /* wgsl */ `
36
+ @group(0) @binding(0) var srcTex: texture_2d<f32>;
37
+ @group(0) @binding(1) var maskTex: texture_2d<f32>;
38
+ @group(0) @binding(2) var depthTex: texture_depth_multisampled_2d;
39
+ // dofU[2] = (projA, projB): the same z-buffer inversion the composite uses.
40
+ @group(0) @binding(3) var<uniform> dofU: array<vec4f, 3>;
41
+ // sssU[0] = (direction.xy, radius, _); sssU[1] = (tanHalfFov·aspect, tanHalfFov, width, height).
42
+ @group(0) @binding(4) var<uniform> sssU: array<vec4f, 2>;
43
+
44
+ struct VsOut { @builtin(position) pos: vec4f };
45
+
46
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> VsOut {
47
+ let p = vec2f(f32((i << 1u) & 2u), f32(i & 2u));
48
+ var o: VsOut;
49
+ o.pos = vec4f(p * 2.0 - 1.0, 0.0, 1.0);
50
+ return o;
51
+ }
52
+
53
+ fn size() -> vec2i { return vec2i(sssU[1].zw); }
54
+
55
+ fn texel(uv: vec2f) -> vec2i {
56
+ return clamp(vec2i(floor(uv * sssU[1].zw)), vec2i(0), size() - 1);
57
+ }
58
+
59
+ fn linearDepth(c: vec2i) -> f32 {
60
+ let z = textureLoad(depthTex, c, 0);
61
+ return clamp(dofU[2].y / (z - dofU[2].x), 0.05, 100000.0);
62
+ }
63
+
64
+ // View-space position of a texel centre — ray-mmd's GetViewPosition, with y up.
65
+ fn viewPos(c: vec2i, d: f32) -> vec3f {
66
+ let uv = (vec2f(c) + 0.5) / sssU[1].zw;
67
+ let ndc = vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
68
+ return vec3f(ndc * sssU[1].xy * d, d);
69
+ }
70
+
71
+ fn skin(c: vec2i) -> f32 { return textureLoad(maskTex, c, 0).b; }
72
+
73
+ fn blur(fragPos: vec4f) -> vec4f {
74
+ let c = vec2i(fragPos.xy);
75
+ let amount = skin(c) * 4.0;
76
+ let dir = sssU[0].xy;
77
+ let uv = (vec2f(c) + 0.5) / sssU[1].zw;
78
+ let centre = textureLoad(srcTex, c, 0);
79
+
80
+ // ray-mmd, verbatim: six offsets, and the skin variances per channel.
81
+ // A var, not a const: a const array cannot be indexed by the loop counter.
82
+ var offsets = array<f32, 6>(0.352, 0.719, 1.117, 1.579, 2.177, 3.213);
83
+ const profileVar = vec3f(9.6, 2.8, 2.2);
84
+ // mSSSSScale at the controller's default; it sets both the width and how hard
85
+ // a depth step rejects.
86
+ const SSSS_SCALE: f32 = 0.04;
87
+
88
+ let d = linearDepth(c);
89
+
90
+ // The surface's normal, rebuilt from depth (ray-mmd reads it from its G-buffer;
91
+ // this engine keeps none). Only its angle to the view matters: the blur narrows
92
+ // along an axis the surface is turning away on.
93
+ let s = size();
94
+ let cx = min(c + vec2i(1, 0), s - 1);
95
+ let cy = min(c + vec2i(0, 1), s - 1);
96
+ let P = viewPos(c, d);
97
+ let N = cross(viewPos(cx, linearDepth(cx)) - P, viewPos(cy, linearDepth(cy)) - P);
98
+ let nxz = select(vec2f(0.0, 1.0), normalize(N.xz), dot(N.xz, N.xz) > 1e-12);
99
+ let nyz = select(vec2f(0.0, 1.0), normalize(N.yz), dot(N.yz, N.yz) > 1e-12);
100
+ let scaleX = abs(dot(nxz, normalize(P.xz)));
101
+ let scaleY = abs(dot(nyz, normalize(P.yz)));
102
+ let perspectiveScale = max(dot(dir, vec2f(scaleX, scaleY)), 0.3);
103
+
104
+ let radius = 0.0055 * amount * perspectiveScale / SSSS_SCALE;
105
+ let step = dir * radius / d;
106
+
107
+ var weightSum = vec3f(1.0);
108
+ var colour = centre.rgb;
109
+ for (var i = 0; i < 6; i++) {
110
+ let o = offsets[i] / 5.5 * step;
111
+ for (var side = 0; side < 2; side++) {
112
+ let tc = texel(select(uv - o, uv + o, side == 0));
113
+ // Skin only: ray-mmd zeroes a non-skin pixel's depth so the depth term
114
+ // rejects it; the mask says the same thing directly.
115
+ let isSkin = select(0.0, 1.0, skin(tc) > 0.004);
116
+ let dd = (linearDepth(tc) - d) * 1000.0 * SSSS_SCALE;
117
+ let w = exp(-(offsets[i] * offsets[i] + dd * dd) / profileVar) * isSkin;
118
+ weightSum += w;
119
+ colour += w * textureLoad(srcTex, tc, 0).rgb;
120
+ }
121
+ }
122
+ return vec4f(colour / weightSum, centre.a);
123
+ }
124
+
125
+ @fragment fn fsX(@builtin(position) pos: vec4f) -> @location(0) vec4f {
126
+ // Nothing reads the scratch target off skin: the Y pass rejects those taps.
127
+ if (skin(vec2i(pos.xy)) <= 0.004) { return vec4f(0.0); }
128
+ return blur(pos);
129
+ }
130
+
131
+ @fragment fn fsY(@builtin(position) pos: vec4f) -> @location(0) vec4f {
132
+ // Off skin the HDR resolve must stay exactly as the scene left it.
133
+ if (skin(vec2i(pos.xy)) <= 0.004) { discard; }
134
+ return blur(pos);
135
+ }
136
+ `
@@ -37,6 +37,17 @@ fn rzWorldAmbient(n: vec3f) -> vec3f {
37
37
  + ${u}.sh[8].xyz * (x * x - y * y);
38
38
  return max(c, vec3f(0.0));
39
39
  }
40
+
41
+ /**
42
+ * The world's light averaged over every direction: its colour and brightness,
43
+ * with no shape. Every term but sh[0] averages to zero over the sphere (x, xy,
44
+ * 3z²-1 ... all do), so the average is sh[0] itself; a flat world is its own.
45
+ * What an anime cast takes from a sky - see ModelLight.opts.
46
+ */
47
+ fn rzWorldAmbientAvg() -> vec3f {
48
+ if (${u}.sh[0].w < 0.5) { return ${u}.ambientColor.xyz; }
49
+ return max(${u}.sh[0].xyz, vec3f(0.0));
50
+ }
40
51
  `
41
52
 
42
53
  /**