@driftengine/core 4.8.5 → 4.8.7

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 (214) hide show
  1. package/CHANGELOG.json +4392 -4250
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +12 -4
  4. package/dist/index.js +12 -2
  5. package/dist/render/ambientHarmonics.d.ts +46 -0
  6. package/dist/render/ambientHarmonics.js +80 -0
  7. package/dist/render/anisotropicLobe.d.ts +10 -0
  8. package/dist/render/anisotropicLobe.js +19 -0
  9. package/dist/render/areaLights.d.ts +54 -0
  10. package/dist/render/areaLights.js +55 -0
  11. package/dist/render/backend/api.d.ts +43 -2
  12. package/dist/render/backend/webgl2/instanced.d.ts +2 -0
  13. package/dist/render/backend/webgl2/instanced.js +11 -2
  14. package/dist/render/backend/webgl2/renderer.d.ts +120 -11
  15. package/dist/render/backend/webgl2/renderer.js +356 -53
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
  19. package/dist/render/backend/webgl2/skinScatter.js +88 -26
  20. package/dist/render/backend/webgpu/buffers.js +2 -1
  21. package/dist/render/backend/webgpu/clusterBinner.js +3 -2
  22. package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
  23. package/dist/render/backend/webgpu/flatPass.js +15 -13
  24. package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
  25. package/dist/render/backend/webgpu/particlePass.js +81 -12
  26. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  27. package/dist/render/backend/webgpu/postPass.js +7 -2
  28. package/dist/render/backend/webgpu/renderer.d.ts +93 -8
  29. package/dist/render/backend/webgpu/renderer.js +483 -93
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  31. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  32. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  33. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
  34. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
  35. package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
  36. package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
  37. package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
  38. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
  39. package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
  40. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  41. package/dist/render/bloomChain.d.ts +44 -0
  42. package/dist/render/bloomChain.js +28 -0
  43. package/dist/render/bloomPass.d.ts +3 -1
  44. package/dist/render/bloomPass.js +16 -2
  45. package/dist/render/clusteredLights.d.ts +78 -6
  46. package/dist/render/clusteredLights.js +214 -13
  47. package/dist/render/driftLight/bake.d.ts +2 -0
  48. package/dist/render/driftLight/bake.js +1 -1
  49. package/dist/render/driftLight/falloff.d.ts +4 -2
  50. package/dist/render/driftLight/falloff.js +8 -2
  51. package/dist/render/fixtureAtlas.js +1 -1
  52. package/dist/render/gi/bounceLights.d.ts +3 -2
  53. package/dist/render/gi/bounceLights.js +8 -3
  54. package/dist/render/instanceCull.js +12 -0
  55. package/dist/render/instances.d.ts +47 -11
  56. package/dist/render/instances.js +24 -9
  57. package/dist/render/lightBudget.d.ts +38 -2
  58. package/dist/render/lightBudget.js +48 -5
  59. package/dist/render/lightmap.d.ts +116 -0
  60. package/dist/render/lightmap.js +140 -0
  61. package/dist/render/mesh.js +2 -1
  62. package/dist/render/particleBatch.d.ts +59 -3
  63. package/dist/render/particleBatch.js +53 -2
  64. package/dist/render/particleMaterial.d.ts +6 -1
  65. package/dist/render/particleMaterial.js +10 -2
  66. package/dist/render/particlePool.d.ts +32 -0
  67. package/dist/render/particlePool.js +9 -0
  68. package/dist/render/particleSort.d.ts +34 -0
  69. package/dist/render/particleSort.js +96 -0
  70. package/dist/render/pointLightSelection.d.ts +13 -0
  71. package/dist/render/pointLightSelection.js +3 -1
  72. package/dist/render/renderQuality.d.ts +8 -7
  73. package/dist/render/rendererHarness.js +3 -0
  74. package/dist/render/rgb9e5.d.ts +22 -0
  75. package/dist/render/rgb9e5.js +47 -0
  76. package/dist/render/sceneCapture.d.ts +29 -0
  77. package/dist/render/sceneCapture.js +36 -0
  78. package/dist/render/sceneTarget.d.ts +5 -0
  79. package/dist/render/sceneTarget.js +8 -1
  80. package/dist/render/screenSpaceReflection.d.ts +27 -1
  81. package/dist/render/screenSpaceReflection.js +9 -0
  82. package/dist/render/shaders/bloom.d.ts +8 -4
  83. package/dist/render/shaders/bloom.js +20 -3
  84. package/dist/render/shaders/bolt.d.ts +1 -1
  85. package/dist/render/shaders/bolt.js +7 -1
  86. package/dist/render/shaders/depth.js +11 -0
  87. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  88. package/dist/render/shaders/flat/anisotropic.js +37 -3
  89. package/dist/render/shaders/flat/areaLight.d.ts +10 -0
  90. package/dist/render/shaders/flat/areaLight.js +142 -0
  91. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  92. package/dist/render/shaders/flat/glassTint.js +3 -1
  93. package/dist/render/shaders/flat/index.d.ts +5 -0
  94. package/dist/render/shaders/flat/index.js +46 -6
  95. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  96. package/dist/render/shaders/flat/lobes.js +49 -43
  97. package/dist/render/shaders/flat/main.d.ts +1 -1
  98. package/dist/render/shaders/flat/main.js +158 -121
  99. package/dist/render/shaders/flat/models.d.ts +21 -6
  100. package/dist/render/shaders/flat/models.js +161 -17
  101. package/dist/render/shaders/flat/normalMap.d.ts +10 -0
  102. package/dist/render/shaders/flat/normalMap.js +25 -0
  103. package/dist/render/shaders/flat/preamble.js +84 -40
  104. package/dist/render/shaders/flat/skin.js +6 -5
  105. package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
  106. package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
  107. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  108. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  109. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  110. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  111. package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
  112. package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
  113. package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
  114. package/dist/render/shaders/generated/particle.wgsl.js +115 -1
  115. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
  116. package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
  117. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
  118. package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
  119. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  120. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  121. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  122. package/dist/render/shaders/particle.d.ts +17 -1
  123. package/dist/render/shaders/particle.js +133 -1
  124. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  126. package/dist/render/shaders/skinBlur.js +25 -3
  127. package/dist/render/shaders/ssrTrace.js +20 -1
  128. package/dist/render/shaders/water.d.ts +1 -1
  129. package/dist/render/shaders/water.js +12 -3
  130. package/dist/render/skinBlur.d.ts +8 -5
  131. package/dist/render/skinBlur.js +8 -5
  132. package/dist/render/skinScatter.d.ts +3 -1
  133. package/dist/render/skinScatter.js +3 -1
  134. package/dist/render/ssrPass.js +1 -0
  135. package/dist/render/surfaceModel.d.ts +49 -8
  136. package/dist/render/surfaceModel.js +54 -8
  137. package/dist/render/surfaceTexture.d.ts +38 -1
  138. package/dist/render/surfaceTexture.js +37 -2
  139. package/package.json +6 -6
  140. package/src/index.ts +26 -3
  141. package/src/render/ambientHarmonics.ts +92 -0
  142. package/src/render/anisotropicLobe.ts +29 -0
  143. package/src/render/areaLights.ts +97 -0
  144. package/src/render/backend/api.ts +61 -1
  145. package/src/render/backend/webgl2/instanced.ts +12 -2
  146. package/src/render/backend/webgl2/renderer.ts +470 -59
  147. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  148. package/src/render/backend/webgl2/skinScatter.ts +120 -43
  149. package/src/render/backend/webgpu/buffers.ts +2 -1
  150. package/src/render/backend/webgpu/clusterBinner.ts +3 -1
  151. package/src/render/backend/webgpu/flatPass.ts +18 -13
  152. package/src/render/backend/webgpu/particlePass.ts +119 -12
  153. package/src/render/backend/webgpu/postPass.ts +7 -2
  154. package/src/render/backend/webgpu/renderer.ts +589 -113
  155. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  156. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
  157. package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
  158. package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
  159. package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
  160. package/src/render/bloomChain.ts +69 -0
  161. package/src/render/bloomPass.ts +26 -2
  162. package/src/render/clusteredLights.ts +259 -12
  163. package/src/render/driftLight/bake.ts +3 -1
  164. package/src/render/driftLight/falloff.ts +13 -2
  165. package/src/render/fixtureAtlas.ts +1 -1
  166. package/src/render/gi/bounceLights.ts +12 -3
  167. package/src/render/instanceCull.ts +9 -0
  168. package/src/render/instances.ts +61 -12
  169. package/src/render/lightBudget.ts +88 -4
  170. package/src/render/lightmap.ts +243 -0
  171. package/src/render/mesh.ts +2 -1
  172. package/src/render/particleBatch.ts +95 -4
  173. package/src/render/particleMaterial.ts +11 -1
  174. package/src/render/particlePool.ts +36 -0
  175. package/src/render/particleSort.ts +133 -0
  176. package/src/render/pointLightSelection.ts +20 -1
  177. package/src/render/renderQuality.ts +8 -7
  178. package/src/render/rendererHarness.ts +3 -0
  179. package/src/render/rgb9e5.ts +52 -0
  180. package/src/render/sceneCapture.ts +56 -0
  181. package/src/render/sceneTarget.ts +18 -1
  182. package/src/render/screenSpaceReflection.ts +33 -1
  183. package/src/render/shaders/bloom.ts +20 -3
  184. package/src/render/shaders/bolt.ts +7 -1
  185. package/src/render/shaders/depth.ts +11 -0
  186. package/src/render/shaders/flat/anisotropic.ts +37 -3
  187. package/src/render/shaders/flat/areaLight.ts +142 -0
  188. package/src/render/shaders/flat/glassTint.ts +9 -2
  189. package/src/render/shaders/flat/index.ts +51 -6
  190. package/src/render/shaders/flat/lobes.ts +50 -43
  191. package/src/render/shaders/flat/main.ts +158 -121
  192. package/src/render/shaders/flat/models.ts +170 -21
  193. package/src/render/shaders/flat/normalMap.ts +25 -0
  194. package/src/render/shaders/flat/preamble.ts +84 -40
  195. package/src/render/shaders/flat/skin.ts +6 -5
  196. package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
  197. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  198. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  199. package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
  200. package/src/render/shaders/generated/particle.wgsl.ts +116 -1
  201. package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
  202. package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
  203. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  204. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  205. package/src/render/shaders/particle.ts +134 -1
  206. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  207. package/src/render/shaders/skinBlur.ts +25 -3
  208. package/src/render/shaders/ssrTrace.ts +20 -1
  209. package/src/render/shaders/water.ts +12 -3
  210. package/src/render/skinBlur.ts +8 -5
  211. package/src/render/skinScatter.ts +3 -1
  212. package/src/render/ssrPass.ts +1 -0
  213. package/src/render/surfaceModel.ts +82 -10
  214. package/src/render/surfaceTexture.ts +101 -3
@@ -4,7 +4,7 @@
4
4
  * The GLSL beside this file is the source of truth. A hand edit here is discarded by
5
5
  * the next generation, and `npm run wgsl:check` fails the build when this is stale.
6
6
  */
7
- export const WATER_FRAG_WGSL = "struct Uniforms {\n uGridHalf: vec2<f32>,\n uFoamGain: f32,\n uNadirOpacity: f32,\n uVisibility: f32,\n uMirror: f32,\n uDeepColor: vec3<f32>,\n uShallowColor: vec3<f32>,\n uDirectionalDir: vec3<f32>,\n uDirectionalColor: vec3<f32>,\n uAmbient: vec3<f32>,\n uLightCount: i32,\n uLightPos: array<vec3<f32>, 16>,\n uLightColor: array<vec3<f32>, 16>,\n uLightRadius: array<vec4<f32>, 16>,\n uLightWeight: array<vec4<f32>, 16>,\n uLightFalloff: i32,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n uReflectionEnabled: i32,\n uReflectionTexelSize: vec2<f32>,\n uReflectionFilterTaps: i32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vNormal_1: vec3<f32>;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> vCrest_1: f32;\nvar<private> vReflectionClip_1: vec4<f32>;\n@group(0) @binding(32) \nvar uReflectionMap_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uReflectionMap_s: sampler;\nvar<private> vGrid_1: vec2<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e116 = unnamed.uFogMode;\n if (_e116 == 1i) {\n let _e119 = unnamed.uFogFar;\n let _e121 = unnamed.uFogNear;\n span = max((_e119 - _e121), 0.0001f);\n let _e124 = (*dist);\n let _e126 = unnamed.uFogNear;\n let _e128 = span;\n ramp = clamp(((_e124 - _e126) / _e128), 0f, 1f);\n let _e132 = unnamed.uUnderwaterFactor;\n if (_e132 <= 0f) {\n let _e134 = ramp;\n return _e134;\n }\n let _e136 = unnamed.uUnderwaterFogDensity;\n let _e137 = (*dist);\n wetLinear = (_e136 * _e137);\n let _e139 = ramp;\n let _e140 = wetLinear;\n let _e142 = wetLinear;\n let _e148 = unnamed.uUnderwaterFactor;\n return mix(_e139, (1f - exp2(((-(_e140) * _e142) * 1.442695f))), _e148);\n }\n let _e150 = (*pointY);\n let _e152 = unnamed.uFogEyeY;\n let _e155 = unnamed.uFogHeightFalloff;\n t = ((_e150 - _e152) * _e155);\n let _e157 = t;\n let _e160 = t;\n denom = select(_e160, 0.0001f, (abs(_e157) < 0.0001f));\n let _e163 = unnamed.uFogDensity;\n let _e165 = (*dist);\n let _e167 = denom;\n let _e172 = denom;\n air = (1f - exp((((-(_e163) * _e165) * (1f - exp(-(_e167)))) / _e172)));\n let _e177 = unnamed.uUnderwaterFactor;\n if (_e177 <= 0f) {\n let _e179 = air;\n return _e179;\n }\n let _e181 = unnamed.uUnderwaterFogDensity;\n let _e182 = (*dist);\n wet = (_e181 * _e182);\n let _e184 = air;\n let _e185 = wet;\n let _e187 = wet;\n let _e193 = unnamed.uUnderwaterFactor;\n return mix(_e184, (1f - exp2(((-(_e185) * _e187) * 1.442695f))), _e193);\n}\n\nfn mediumColor_u0028_() -> vec3<f32> {\n let _e107 = unnamed.uFogColor;\n let _e109 = unnamed.uUnderwaterColor;\n let _e111 = unnamed.uUnderwaterFactor;\n return mix(_e107, _e109, vec3(_e111));\n}\n\nfn main_1() {\n var n: vec3<f32>;\n var view: vec3<f32>;\n var facing: f32;\n var fresnel: f32;\n var atmosphereColor: vec3<f32>;\n var col: vec3<f32>;\n var incident: vec3<f32>;\n var halfVec: vec3<f32>;\n var spec: f32;\n var highlight: vec3<f32>;\n var i: i32;\n var toLight: vec3<f32>;\n var lightDist: f32;\n var window: f32;\n var falloff: f32;\n var lightDir: vec3<f32>;\n var shaped: f32;\n var lampHalf: vec3<f32>;\n var foam: f32;\n var dist_1: f32;\n var fog: f32;\n var param: f32;\n var param_1: f32;\n var airOpacity: f32;\n var underwaterOpacity: f32;\n var opacity: f32;\n var reflected: vec3<f32>;\n var reflectionValid: f32;\n var reflectionUv: vec2<f32>;\n var border: vec2<f32>;\n var blurPixels: f32;\n var sceneReflection: vec3<f32>;\n var filterWeight: f32;\n var i_1: i32;\n var sampleUv: vec2<f32>;\n var indexable: array<vec2<f32>, 9>;\n var weight: f32;\n var indexable_1: array<f32, 9>;\n var sceneWeight: f32;\n var reflectionGain: f32;\n var reflectivity: f32;\n var reflectedRadiance: f32;\n var reflectionWeight: f32;\n var rimFraction: vec2<f32>;\n var rimT: f32;\n var rim: f32;\n var edge: f32;\n var phi_440_: bool;\n\n let _e153 = vNormal_1;\n n = normalize(_e153);\n let _e156 = unnamed.uCameraPos;\n let _e157 = vWorldPos_1;\n view = normalize((_e156 - _e157));\n let _e160 = n;\n let _e161 = view;\n facing = clamp(abs(dot(_e160, _e161)), 0f, 1f);\n let _e165 = facing;\n fresnel = (0.02f + (0.98f * pow((1f - _e165), 5f)));\n let _e170 = mediumColor_u0028_();\n atmosphereColor = _e170;\n let _e172 = unnamed.uDeepColor;\n let _e174 = unnamed.uShallowColor;\n let _e175 = vCrest_1;\n col = mix(_e172, _e174, vec3(clamp(((_e175 * 1.4f) + 0.4f), 0f, 1f)));\n let _e182 = unnamed.uAmbient;\n let _e184 = unnamed.uDirectionalColor;\n let _e185 = n;\n let _e187 = unnamed.uDirectionalDir;\n incident = (_e182 + ((_e184 * max(dot(_e185, _e187), 0f)) * 0.6f));\n let _e194 = unnamed.uDirectionalDir;\n let _e195 = view;\n halfVec = normalize((_e194 + _e195));\n let _e198 = n;\n let _e199 = halfVec;\n spec = pow(max(dot(_e198, _e199), 0f), 220f);\n let _e204 = unnamed.uDirectionalColor;\n let _e205 = spec;\n highlight = ((_e204 * _e205) * 1.6f);\n i = 0i;\n loop {\n let _e208 = i;\n if (_e208 < 16i) {\n let _e210 = i;\n let _e212 = unnamed.uLightCount;\n if (_e210 >= _e212) {\n break;\n }\n let _e214 = i;\n let _e217 = unnamed.uLightPos[_e214];\n let _e218 = vWorldPos_1;\n toLight = (_e217 - _e218);\n let _e220 = toLight;\n lightDist = length(_e220);\n let _e223 = unnamed.uLightFalloff;\n if (_e223 == 1i) {\n let _e225 = lightDist;\n let _e226 = i;\n let _e230 = unnamed.uLightRadius[_e226][0u];\n window = clamp((1f - pow((_e225 / max(_e230, 0.0001f)), 4f)), 0f, 1f);\n let _e236 = window;\n let _e237 = window;\n let _e239 = lightDist;\n let _e240 = lightDist;\n falloff = ((_e236 * _e237) / max((_e239 * _e240), 0.01f));\n } else {\n let _e244 = lightDist;\n let _e245 = i;\n let _e249 = unnamed.uLightRadius[_e245][0u];\n falloff = clamp((1f - (_e244 / max(_e249, 0.0001f))), 0f, 1f);\n }\n let _e254 = falloff;\n if (_e254 <= 0f) {\n continue;\n }\n let _e256 = toLight;\n let _e257 = lightDist;\n lightDir = (_e256 / vec3(max(_e257, 0.0001f)));\n let _e261 = falloff;\n let _e262 = i;\n let _e266 = unnamed.uLightWeight[_e262][0u];\n shaped = (_e261 * _e266);\n let _e268 = i;\n let _e271 = unnamed.uLightColor[_e268];\n let _e272 = n;\n let _e273 = lightDir;\n let _e277 = shaped;\n let _e280 = incident;\n incident = (_e280 + (((_e271 * max(dot(_e272, _e273), 0f)) * _e277) * 0.6f));\n let _e282 = lightDir;\n let _e283 = view;\n lampHalf = normalize((_e282 + _e283));\n let _e286 = i;\n let _e289 = unnamed.uLightColor[_e286];\n let _e290 = n;\n let _e291 = lampHalf;\n let _e296 = shaped;\n let _e299 = highlight;\n highlight = (_e299 + (((_e289 * pow(max(dot(_e290, _e291), 0f), 220f)) * _e296) * 1.6f));\n continue;\n } else {\n break;\n }\n continuing {\n let _e301 = i;\n i = (_e301 + 1i);\n }\n }\n let _e303 = incident;\n let _e304 = col;\n col = (_e304 * _e303);\n let _e306 = highlight;\n let _e307 = col;\n col = (_e307 + _e306);\n let _e309 = vCrest_1;\n let _e312 = unnamed.uFoamGain;\n foam = (smoothstep(0.55f, 0.85f, _e309) * _e312);\n let _e314 = col;\n let _e315 = foam;\n col = mix(_e314, vec3<f32>(0.92f, 0.96f, 1f), vec3((_e315 * 0.5f)));\n let _e319 = vWorldPos_1;\n let _e321 = unnamed.uCameraPos;\n dist_1 = distance(_e319, _e321);\n let _e323 = dist_1;\n param = _e323;\n let _e325 = vWorldPos_1[1u];\n param_1 = _e325;\n let _e326 = mediumFog_u0028_f1_u003b_f1_u003b((&param), (&param_1));\n fog = _e326;\n let _e327 = col;\n let _e328 = atmosphereColor;\n let _e329 = fog;\n col = mix(_e327, _e328, vec3(_e329));\n let _e333 = unnamed.uNadirOpacity;\n let _e334 = fresnel;\n airOpacity = mix(_e333, 0.96f, _e334);\n let _e337 = unnamed.uNadirOpacity;\n let _e339 = fresnel;\n underwaterOpacity = mix((_e337 * 0.55f), 0.88f, _e339);\n let _e341 = airOpacity;\n let _e342 = underwaterOpacity;\n let _e344 = unnamed.uUnderwaterFactor;\n opacity = mix(_e341, _e342, _e344);\n let _e346 = opacity;\n let _e347 = foam;\n opacity = mix(_e346, 0.96f, (_e347 * 0.7f));\n let _e350 = atmosphereColor;\n reflected = _e350;\n reflectionValid = 0f;\n let _e352 = unnamed.uReflectionEnabled;\n let _e353 = (_e352 != 0i);\n phi_440_ = _e353;\n if _e353 {\n let _e355 = vReflectionClip_1[3u];\n phi_440_ = (_e355 > 0f);\n }\n let _e358 = phi_440_;\n if _e358 {\n let _e359 = vReflectionClip_1;\n let _e362 = vReflectionClip_1[3u];\n reflectionUv = (((_e359.xy / vec2(_e362)) * 0.5f) + vec2(0.5f));\n let _e368 = n;\n let _e370 = fresnel;\n let _e373 = reflectionUv;\n reflectionUv = (_e373 + (_e368.xz * mix(0.008f, 0.022f, _e370)));\n let _e375 = reflectionUv;\n let _e376 = reflectionUv;\n border = min(_e375, (vec2(1f) - _e376));\n let _e381 = border[0u];\n let _e383 = border[1u];\n reflectionValid = smoothstep(0f, 0.025f, min(_e381, _e383));\n let _e386 = dist_1;\n blurPixels = (1.35f + min((_e386 * 0.022f), 3.5f));\n sceneReflection = vec3<f32>(0f, 0f, 0f);\n filterWeight = 0f;\n i_1 = 0i;\n loop {\n let _e390 = i_1;\n if (_e390 < 9i) {\n let _e392 = i_1;\n let _e394 = unnamed.uReflectionFilterTaps;\n if (_e392 >= _e394) {\n break;\n }\n let _e396 = reflectionUv;\n let _e397 = i_1;\n indexable = array<vec2<f32>, 9>(vec2<f32>(0f, 0f), vec2<f32>(-0.62f, -0.31f), vec2<f32>(0.54f, 0.43f), vec2<f32>(-0.34f, 0.76f), vec2<f32>(0.78f, -0.57f), vec2<f32>(-0.88f, 0.28f), vec2<f32>(0.22f, -0.91f), vec2<f32>(0.91f, 0.08f), vec2<f32>(-0.18f, 0.94f));\n let _e399 = indexable[_e397];\n let _e401 = unnamed.uReflectionTexelSize;\n let _e403 = blurPixels;\n sampleUv = (_e396 + ((_e399 * _e401) * _e403));\n let _e406 = i_1;\n indexable_1 = array<f32, 9>(0.28f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f);\n let _e408 = indexable_1[_e406];\n weight = _e408;\n let _e409 = sampleUv;\n let _e413 = textureSampleLevel(uReflectionMap_t, uReflectionMap_s, clamp(_e409, vec2(0f), vec2(1f)), 0f);\n let _e415 = weight;\n let _e417 = sceneReflection;\n sceneReflection = (_e417 + (_e413.xyz * _e415));\n let _e419 = weight;\n let _e420 = filterWeight;\n filterWeight = (_e420 + _e419);\n continue;\n } else {\n break;\n }\n continuing {\n let _e422 = i_1;\n i_1 = (_e422 + 1i);\n }\n }\n let _e424 = filterWeight;\n let _e426 = sceneReflection;\n sceneReflection = (_e426 / vec3(max(_e424, 0.001f)));\n let _e429 = atmosphereColor;\n let _e430 = sceneReflection;\n let _e431 = reflectionValid;\n reflected = mix(_e429, _e430, vec3(_e431));\n }\n let _e435 = unnamed.uReflectionEnabled;\n let _e437 = reflectionValid;\n sceneWeight = (f32(_e435) * _e437);\n let _e439 = sceneWeight;\n reflectionGain = mix(0.75f, 0.94f, _e439);\n let _e441 = fresnel;\n let _e443 = unnamed.uMirror;\n reflectivity = mix(_e441, 1f, _e443);\n let _e445 = reflectivity;\n let _e446 = reflectionGain;\n let _e448 = foam;\n let _e452 = opacity;\n reflectedRadiance = min(((_e445 * _e446) * (1f - (_e448 * 0.8f))), _e452);\n let _e454 = reflectedRadiance;\n let _e455 = opacity;\n reflectionWeight = (_e454 / max(_e455, 0.001f));\n let _e458 = col;\n let _e459 = reflected;\n let _e460 = reflectionWeight;\n col = mix(_e458, _e459, vec3(_e460));\n let _e463 = vGrid_1;\n let _e466 = unnamed.uGridHalf;\n rimFraction = (abs(_e463) / max(_e466, vec2<f32>(0.001f, 0.001f)));\n let _e470 = rimFraction[0u];\n let _e472 = rimFraction[1u];\n rimT = max(_e470, _e472);\n let _e474 = rimT;\n rim = (1f - smoothstep(0.9f, 1f, _e474));\n let _e477 = fog;\n edge = (1f - smoothstep(0.82f, 1f, _e477));\n let _e480 = col;\n let _e481 = opacity;\n let _e482 = edge;\n let _e484 = rim;\n let _e487 = unnamed.uVisibility;\n outColor = vec4<f32>(_e480.x, _e480.y, _e480.z, (((_e481 * _e482) * _e484) * _e487));\n return;\n}\n\n@fragment \nfn main(@location(1) vNormal: vec3<f32>, @location(0) vWorldPos: vec3<f32>, @location(2) vCrest: f32, @location(3) vReflectionClip: vec4<f32>, @location(4) vGrid: vec2<f32>) -> @location(0) vec4<f32> {\n vNormal_1 = vNormal;\n vWorldPos_1 = vWorldPos;\n vCrest_1 = vCrest;\n vReflectionClip_1 = vReflectionClip;\n vGrid_1 = vGrid;\n main_1();\n let _e11 = outColor;\n return _e11;\n}\n";
7
+ export const WATER_FRAG_WGSL = "struct Uniforms {\n uGridHalf: vec2<f32>,\n uFoamGain: f32,\n uNadirOpacity: f32,\n uVisibility: f32,\n uMirror: f32,\n uDeepColor: vec3<f32>,\n uShallowColor: vec3<f32>,\n uDirectionalDir: vec3<f32>,\n uDirectionalColor: vec3<f32>,\n uAmbient: vec3<f32>,\n uLightCount: i32,\n uLightPos: array<vec3<f32>, 16>,\n uLightColor: array<vec3<f32>, 16>,\n uLightRadius: array<vec4<f32>, 16>,\n uLightWeight: array<vec4<f32>, 16>,\n uLightFalloff: i32,\n uLightCone: array<vec4<f32>, 16>,\n uFogColor: vec3<f32>,\n uFogDensity: f32,\n uFogHeightFalloff: f32,\n uFogEyeY: f32,\n uUnderwaterColor: vec3<f32>,\n uUnderwaterFogDensity: f32,\n uUnderwaterFactor: f32,\n uFogMode: i32,\n uFogNear: f32,\n uFogFar: f32,\n uCameraPos: vec3<f32>,\n uReflectionEnabled: i32,\n uReflectionTexelSize: vec2<f32>,\n uReflectionFilterTaps: i32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\nvar<private> vNormal_1: vec3<f32>;\nvar<private> vWorldPos_1: vec3<f32>;\nvar<private> vCrest_1: f32;\nvar<private> vReflectionClip_1: vec4<f32>;\n@group(0) @binding(32) \nvar uReflectionMap_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uReflectionMap_s: sampler;\nvar<private> vGrid_1: vec2<f32>;\nvar<private> outColor: vec4<f32>;\n\nfn mediumFog_u0028_f1_u003b_f1_u003b(dist: ptr<function, f32>, pointY: ptr<function, f32>) -> f32 {\n var span: f32;\n var ramp: f32;\n var wetLinear: f32;\n var t: f32;\n var denom: f32;\n var air: f32;\n var wet: f32;\n\n let _e118 = unnamed.uFogMode;\n if (_e118 == 1i) {\n let _e121 = unnamed.uFogFar;\n let _e123 = unnamed.uFogNear;\n span = max((_e121 - _e123), 0.0001f);\n let _e126 = (*dist);\n let _e128 = unnamed.uFogNear;\n let _e130 = span;\n ramp = clamp(((_e126 - _e128) / _e130), 0f, 1f);\n let _e134 = unnamed.uUnderwaterFactor;\n if (_e134 <= 0f) {\n let _e136 = ramp;\n return _e136;\n }\n let _e138 = unnamed.uUnderwaterFogDensity;\n let _e139 = (*dist);\n wetLinear = (_e138 * _e139);\n let _e141 = ramp;\n let _e142 = wetLinear;\n let _e144 = wetLinear;\n let _e150 = unnamed.uUnderwaterFactor;\n return mix(_e141, (1f - exp2(((-(_e142) * _e144) * 1.442695f))), _e150);\n }\n let _e152 = (*pointY);\n let _e154 = unnamed.uFogEyeY;\n let _e157 = unnamed.uFogHeightFalloff;\n t = ((_e152 - _e154) * _e157);\n let _e159 = t;\n let _e162 = t;\n denom = select(_e162, 0.0001f, (abs(_e159) < 0.0001f));\n let _e165 = unnamed.uFogDensity;\n let _e167 = (*dist);\n let _e169 = denom;\n let _e174 = denom;\n air = (1f - exp((((-(_e165) * _e167) * (1f - exp(-(_e169)))) / _e174)));\n let _e179 = unnamed.uUnderwaterFactor;\n if (_e179 <= 0f) {\n let _e181 = air;\n return _e181;\n }\n let _e183 = unnamed.uUnderwaterFogDensity;\n let _e184 = (*dist);\n wet = (_e183 * _e184);\n let _e186 = air;\n let _e187 = wet;\n let _e189 = wet;\n let _e195 = unnamed.uUnderwaterFactor;\n return mix(_e186, (1f - exp2(((-(_e187) * _e189) * 1.442695f))), _e195);\n}\n\nfn mediumColor_u0028_() -> vec3<f32> {\n let _e109 = unnamed.uFogColor;\n let _e111 = unnamed.uUnderwaterColor;\n let _e113 = unnamed.uUnderwaterFactor;\n return mix(_e109, _e111, vec3(_e113));\n}\n\nfn main_1() {\n var n: vec3<f32>;\n var view: vec3<f32>;\n var facing: f32;\n var fresnel: f32;\n var atmosphereColor: vec3<f32>;\n var col: vec3<f32>;\n var incident: vec3<f32>;\n var halfVec: vec3<f32>;\n var spec: f32;\n var highlight: vec3<f32>;\n var i: i32;\n var toLight: vec3<f32>;\n var lightDist: f32;\n var exponent: f32;\n var reach: f32;\n var falloff: f32;\n var window: f32;\n var lightDir: vec3<f32>;\n var shaped: f32;\n var lampHalf: vec3<f32>;\n var foam: f32;\n var dist_1: f32;\n var fog: f32;\n var param: f32;\n var param_1: f32;\n var airOpacity: f32;\n var underwaterOpacity: f32;\n var opacity: f32;\n var reflected: vec3<f32>;\n var reflectionValid: f32;\n var reflectionUv: vec2<f32>;\n var border: vec2<f32>;\n var blurPixels: f32;\n var sceneReflection: vec3<f32>;\n var filterWeight: f32;\n var i_1: i32;\n var sampleUv: vec2<f32>;\n var indexable: array<vec2<f32>, 9>;\n var weight: f32;\n var indexable_1: array<f32, 9>;\n var sceneWeight: f32;\n var reflectionGain: f32;\n var reflectivity: f32;\n var reflectedRadiance: f32;\n var reflectionWeight: f32;\n var rimFraction: vec2<f32>;\n var rimT: f32;\n var rim: f32;\n var edge: f32;\n var phi_466_: bool;\n\n let _e157 = vNormal_1;\n n = normalize(_e157);\n let _e160 = unnamed.uCameraPos;\n let _e161 = vWorldPos_1;\n view = normalize((_e160 - _e161));\n let _e164 = n;\n let _e165 = view;\n facing = clamp(abs(dot(_e164, _e165)), 0f, 1f);\n let _e169 = facing;\n fresnel = (0.02f + (0.98f * pow((1f - _e169), 5f)));\n let _e174 = mediumColor_u0028_();\n atmosphereColor = _e174;\n let _e176 = unnamed.uDeepColor;\n let _e178 = unnamed.uShallowColor;\n let _e179 = vCrest_1;\n col = mix(_e176, _e178, vec3(clamp(((_e179 * 1.4f) + 0.4f), 0f, 1f)));\n let _e186 = unnamed.uAmbient;\n let _e188 = unnamed.uDirectionalColor;\n let _e189 = n;\n let _e191 = unnamed.uDirectionalDir;\n incident = (_e186 + ((_e188 * max(dot(_e189, _e191), 0f)) * 0.6f));\n let _e198 = unnamed.uDirectionalDir;\n let _e199 = view;\n halfVec = normalize((_e198 + _e199));\n let _e202 = n;\n let _e203 = halfVec;\n spec = pow(max(dot(_e202, _e203), 0f), 220f);\n let _e208 = unnamed.uDirectionalColor;\n let _e209 = spec;\n highlight = ((_e208 * _e209) * 1.6f);\n i = 0i;\n loop {\n let _e212 = i;\n if (_e212 < 16i) {\n let _e214 = i;\n let _e216 = unnamed.uLightCount;\n if (_e214 >= _e216) {\n break;\n }\n let _e218 = i;\n let _e221 = unnamed.uLightPos[_e218];\n let _e222 = vWorldPos_1;\n toLight = (_e221 - _e222);\n let _e224 = toLight;\n lightDist = length(_e224);\n let _e226 = i;\n let _e230 = unnamed.uLightCone[_e226][2u];\n exponent = _e230;\n let _e231 = exponent;\n if (_e231 > 0f) {\n let _e233 = lightDist;\n let _e234 = i;\n let _e238 = unnamed.uLightRadius[_e234][0u];\n reach = (_e233 / max(_e238, 0.0001f));\n let _e241 = reach;\n let _e242 = reach;\n let _e246 = exponent;\n falloff = pow(clamp((1f - (_e241 * _e242)), 0f, 1f), _e246);\n } else {\n let _e249 = unnamed.uLightFalloff;\n if (_e249 == 1i) {\n let _e251 = lightDist;\n let _e252 = i;\n let _e256 = unnamed.uLightRadius[_e252][0u];\n window = clamp((1f - pow((_e251 / max(_e256, 0.0001f)), 4f)), 0f, 1f);\n let _e262 = window;\n let _e263 = window;\n let _e265 = lightDist;\n let _e266 = lightDist;\n falloff = ((_e262 * _e263) / max((_e265 * _e266), 0.01f));\n } else {\n let _e270 = lightDist;\n let _e271 = i;\n let _e275 = unnamed.uLightRadius[_e271][0u];\n falloff = clamp((1f - (_e270 / max(_e275, 0.0001f))), 0f, 1f);\n }\n }\n let _e280 = falloff;\n if (_e280 <= 0f) {\n continue;\n }\n let _e282 = toLight;\n let _e283 = lightDist;\n lightDir = (_e282 / vec3(max(_e283, 0.0001f)));\n let _e287 = falloff;\n let _e288 = i;\n let _e292 = unnamed.uLightWeight[_e288][0u];\n shaped = (_e287 * _e292);\n let _e294 = i;\n let _e297 = unnamed.uLightColor[_e294];\n let _e298 = n;\n let _e299 = lightDir;\n let _e303 = shaped;\n let _e306 = incident;\n incident = (_e306 + (((_e297 * max(dot(_e298, _e299), 0f)) * _e303) * 0.6f));\n let _e308 = lightDir;\n let _e309 = view;\n lampHalf = normalize((_e308 + _e309));\n let _e312 = i;\n let _e315 = unnamed.uLightColor[_e312];\n let _e316 = n;\n let _e317 = lampHalf;\n let _e322 = shaped;\n let _e325 = highlight;\n highlight = (_e325 + (((_e315 * pow(max(dot(_e316, _e317), 0f), 220f)) * _e322) * 1.6f));\n continue;\n } else {\n break;\n }\n continuing {\n let _e327 = i;\n i = (_e327 + 1i);\n }\n }\n let _e329 = incident;\n let _e330 = col;\n col = (_e330 * _e329);\n let _e332 = highlight;\n let _e333 = col;\n col = (_e333 + _e332);\n let _e335 = vCrest_1;\n let _e338 = unnamed.uFoamGain;\n foam = (smoothstep(0.55f, 0.85f, _e335) * _e338);\n let _e340 = col;\n let _e341 = foam;\n col = mix(_e340, vec3<f32>(0.92f, 0.96f, 1f), vec3((_e341 * 0.5f)));\n let _e345 = vWorldPos_1;\n let _e347 = unnamed.uCameraPos;\n dist_1 = distance(_e345, _e347);\n let _e349 = dist_1;\n param = _e349;\n let _e351 = vWorldPos_1[1u];\n param_1 = _e351;\n let _e352 = mediumFog_u0028_f1_u003b_f1_u003b((&param), (&param_1));\n fog = _e352;\n let _e353 = col;\n let _e354 = atmosphereColor;\n let _e355 = fog;\n col = mix(_e353, _e354, vec3(_e355));\n let _e359 = unnamed.uNadirOpacity;\n let _e360 = fresnel;\n airOpacity = mix(_e359, 0.96f, _e360);\n let _e363 = unnamed.uNadirOpacity;\n let _e365 = fresnel;\n underwaterOpacity = mix((_e363 * 0.55f), 0.88f, _e365);\n let _e367 = airOpacity;\n let _e368 = underwaterOpacity;\n let _e370 = unnamed.uUnderwaterFactor;\n opacity = mix(_e367, _e368, _e370);\n let _e372 = opacity;\n let _e373 = foam;\n opacity = mix(_e372, 0.96f, (_e373 * 0.7f));\n let _e376 = atmosphereColor;\n reflected = _e376;\n reflectionValid = 0f;\n let _e378 = unnamed.uReflectionEnabled;\n let _e379 = (_e378 != 0i);\n phi_466_ = _e379;\n if _e379 {\n let _e381 = vReflectionClip_1[3u];\n phi_466_ = (_e381 > 0f);\n }\n let _e384 = phi_466_;\n if _e384 {\n let _e385 = vReflectionClip_1;\n let _e388 = vReflectionClip_1[3u];\n reflectionUv = (((_e385.xy / vec2(_e388)) * 0.5f) + vec2(0.5f));\n let _e394 = n;\n let _e396 = fresnel;\n let _e399 = reflectionUv;\n reflectionUv = (_e399 + (_e394.xz * mix(0.008f, 0.022f, _e396)));\n let _e401 = reflectionUv;\n let _e402 = reflectionUv;\n border = min(_e401, (vec2(1f) - _e402));\n let _e407 = border[0u];\n let _e409 = border[1u];\n reflectionValid = smoothstep(0f, 0.025f, min(_e407, _e409));\n let _e412 = dist_1;\n blurPixels = (1.35f + min((_e412 * 0.022f), 3.5f));\n sceneReflection = vec3<f32>(0f, 0f, 0f);\n filterWeight = 0f;\n i_1 = 0i;\n loop {\n let _e416 = i_1;\n if (_e416 < 9i) {\n let _e418 = i_1;\n let _e420 = unnamed.uReflectionFilterTaps;\n if (_e418 >= _e420) {\n break;\n }\n let _e422 = reflectionUv;\n let _e423 = i_1;\n indexable = array<vec2<f32>, 9>(vec2<f32>(0f, 0f), vec2<f32>(-0.62f, -0.31f), vec2<f32>(0.54f, 0.43f), vec2<f32>(-0.34f, 0.76f), vec2<f32>(0.78f, -0.57f), vec2<f32>(-0.88f, 0.28f), vec2<f32>(0.22f, -0.91f), vec2<f32>(0.91f, 0.08f), vec2<f32>(-0.18f, 0.94f));\n let _e425 = indexable[_e423];\n let _e427 = unnamed.uReflectionTexelSize;\n let _e429 = blurPixels;\n sampleUv = (_e422 + ((_e425 * _e427) * _e429));\n let _e432 = i_1;\n indexable_1 = array<f32, 9>(0.28f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f, 0.09f);\n let _e434 = indexable_1[_e432];\n weight = _e434;\n let _e435 = sampleUv;\n let _e439 = textureSampleLevel(uReflectionMap_t, uReflectionMap_s, clamp(_e435, vec2(0f), vec2(1f)), 0f);\n let _e441 = weight;\n let _e443 = sceneReflection;\n sceneReflection = (_e443 + (_e439.xyz * _e441));\n let _e445 = weight;\n let _e446 = filterWeight;\n filterWeight = (_e446 + _e445);\n continue;\n } else {\n break;\n }\n continuing {\n let _e448 = i_1;\n i_1 = (_e448 + 1i);\n }\n }\n let _e450 = filterWeight;\n let _e452 = sceneReflection;\n sceneReflection = (_e452 / vec3(max(_e450, 0.001f)));\n let _e455 = atmosphereColor;\n let _e456 = sceneReflection;\n let _e457 = reflectionValid;\n reflected = mix(_e455, _e456, vec3(_e457));\n }\n let _e461 = unnamed.uReflectionEnabled;\n let _e463 = reflectionValid;\n sceneWeight = (f32(_e461) * _e463);\n let _e465 = sceneWeight;\n reflectionGain = mix(0.75f, 0.94f, _e465);\n let _e467 = fresnel;\n let _e469 = unnamed.uMirror;\n reflectivity = mix(_e467, 1f, _e469);\n let _e471 = reflectivity;\n let _e472 = reflectionGain;\n let _e474 = foam;\n let _e478 = opacity;\n reflectedRadiance = min(((_e471 * _e472) * (1f - (_e474 * 0.8f))), _e478);\n let _e480 = reflectedRadiance;\n let _e481 = opacity;\n reflectionWeight = (_e480 / max(_e481, 0.001f));\n let _e484 = col;\n let _e485 = reflected;\n let _e486 = reflectionWeight;\n col = mix(_e484, _e485, vec3(_e486));\n let _e489 = vGrid_1;\n let _e492 = unnamed.uGridHalf;\n rimFraction = (abs(_e489) / max(_e492, vec2<f32>(0.001f, 0.001f)));\n let _e496 = rimFraction[0u];\n let _e498 = rimFraction[1u];\n rimT = max(_e496, _e498);\n let _e500 = rimT;\n rim = (1f - smoothstep(0.9f, 1f, _e500));\n let _e503 = fog;\n edge = (1f - smoothstep(0.82f, 1f, _e503));\n let _e506 = col;\n let _e507 = opacity;\n let _e508 = edge;\n let _e510 = rim;\n let _e513 = unnamed.uVisibility;\n outColor = vec4<f32>(_e506.x, _e506.y, _e506.z, (((_e507 * _e508) * _e510) * _e513));\n return;\n}\n\n@fragment \nfn main(@location(1) vNormal: vec3<f32>, @location(0) vWorldPos: vec3<f32>, @location(2) vCrest: f32, @location(3) vReflectionClip: vec4<f32>, @location(4) vGrid: vec2<f32>) -> @location(0) vec4<f32> {\n vNormal_1 = vNormal;\n vWorldPos_1 = vWorldPos;\n vCrest_1 = vCrest;\n vReflectionClip_1 = vReflectionClip;\n vGrid_1 = vGrid;\n main_1();\n let _e11 = outColor;\n return _e11;\n}\n";
8
8
  export const WATER_VERT_WGSL = "struct GerstnerSurface {\n offset: vec3<f32>,\n normal: vec3<f32>,\n crest: f32,\n}\n\nstruct Uniforms {\n uViewProj: mat4x4<f32>,\n uReflectionViewProj: mat4x4<f32>,\n uCameraPos: vec3<f32>,\n uTime: f32,\n uGridOrigin: vec2<f32>,\n uGridSpan: vec2<f32>,\n uGridHalf: vec2<f32>,\n uNearHalf: vec2<f32>,\n uGridForward: vec2<f32>,\n uWindDir: vec2<f32>,\n uWaveGain: f32,\n uWaterLevel: f32,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(4) member: vec2<f32>,\n @location(0) member_1: vec3<f32>,\n @location(1) member_2: vec3<f32>,\n @location(2) member_3: f32,\n @location(3) member_4: vec4<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> aGrid_1: vec2<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vGrid: vec2<f32>;\nvar<private> vWorldPos: vec3<f32>;\nvar<private> vNormal: vec3<f32>;\nvar<private> vCrest: f32;\nvar<private> vReflectionClip: vec4<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn gerstnerPhase_u0028_f1_u003b_vf2_u003b_vf2_u003b_f1_u003b(k: ptr<function, f32>, dir: ptr<function, vec2<f32>>, p: ptr<function, vec2<f32>>, time: ptr<function, f32>) -> f32 {\n var c: f32;\n\n let _e59 = (*k);\n c = sqrt((9.81f / _e59));\n let _e62 = (*k);\n let _e63 = (*dir);\n let _e64 = (*p);\n let _e66 = c;\n let _e67 = (*time);\n return (_e62 * (dot(_e63, _e64) - (_e66 * _e67)));\n}\n\nfn gerstnerWavenumber_u0028_i1_u003b(i: ptr<function, i32>) -> f32 {\n var indexable: array<vec4<f32>, 4>;\n\n let _e56 = (*i);\n indexable = array<vec4<f32>, 4>(vec4<f32>(1f, 0f, 0.115f, 34f), vec4<f32>(0.6f, 0.8f, 0.1f, 18f), vec4<f32>(-0.7f, 0.7f, 0.08f, 9f), vec4<f32>(0.2f, -0.98f, 0.06f, 5f));\n let _e59 = indexable[_e56][3u];\n return (6.2831855f / _e59);\n}\n\nfn gerstnerDirection_u0028_i1_u003b_vf2_u003b(i_1: ptr<function, i32>, windDir: ptr<function, vec2<f32>>) -> vec2<f32> {\n var indexable_1: array<vec4<f32>, 4>;\n\n let _e57 = (*i_1);\n indexable_1 = array<vec4<f32>, 4>(vec4<f32>(1f, 0f, 0.115f, 34f), vec4<f32>(0.6f, 0.8f, 0.1f, 18f), vec4<f32>(-0.7f, 0.7f, 0.08f, 9f), vec4<f32>(0.2f, -0.98f, 0.06f, 5f));\n let _e59 = indexable_1[_e57];\n let _e62 = (*windDir);\n return normalize(mix(normalize(_e59.xy), _e62, vec2(0.62f)));\n}\n\nfn gerstnerSurface_u0028_vf2_u003b_f1_u003b_vf2_u003b_f1_u003b(p_1: ptr<function, vec2<f32>>, time_1: ptr<function, f32>, windDir_1: ptr<function, vec2<f32>>, gain: ptr<function, f32>) -> GerstnerSurface {\n var s: GerstnerSurface;\n var i_2: i32;\n var dir_1: vec2<f32>;\n var param: i32;\n var param_1: vec2<f32>;\n var steepness: f32;\n var indexable_2: array<vec4<f32>, 4>;\n var k_1: f32;\n var param_2: i32;\n var f: f32;\n var param_3: f32;\n var param_4: vec2<f32>;\n var param_5: vec2<f32>;\n var param_6: f32;\n var a: f32;\n\n s.offset = vec3<f32>(0f, 0f, 0f);\n s.normal = vec3<f32>(0f, 1f, 0f);\n s.crest = 0f;\n i_2 = 0i;\n loop {\n let _e76 = i_2;\n if (_e76 < 4i) {\n let _e78 = i_2;\n param = _e78;\n let _e79 = (*windDir_1);\n param_1 = _e79;\n let _e80 = gerstnerDirection_u0028_i1_u003b_vf2_u003b((&param), (&param_1));\n dir_1 = _e80;\n let _e81 = i_2;\n indexable_2 = array<vec4<f32>, 4>(vec4<f32>(1f, 0f, 0.115f, 34f), vec4<f32>(0.6f, 0.8f, 0.1f, 18f), vec4<f32>(-0.7f, 0.7f, 0.08f, 9f), vec4<f32>(0.2f, -0.98f, 0.06f, 5f));\n let _e84 = indexable_2[_e81][2u];\n let _e85 = (*gain);\n steepness = (_e84 * _e85);\n let _e87 = i_2;\n param_2 = _e87;\n let _e88 = gerstnerWavenumber_u0028_i1_u003b((&param_2));\n k_1 = _e88;\n let _e89 = k_1;\n param_3 = _e89;\n let _e90 = dir_1;\n param_4 = _e90;\n let _e91 = (*p_1);\n param_5 = _e91;\n let _e92 = (*time_1);\n param_6 = _e92;\n let _e93 = gerstnerPhase_u0028_f1_u003b_vf2_u003b_vf2_u003b_f1_u003b((&param_3), (&param_4), (&param_5), (&param_6));\n f = _e93;\n let _e94 = steepness;\n let _e95 = k_1;\n a = (_e94 / _e95);\n let _e98 = dir_1[0u];\n let _e99 = a;\n let _e101 = f;\n let _e106 = s.offset[0u];\n s.offset[0u] = (_e106 + ((_e98 * _e99) * cos(_e101)));\n let _e110 = a;\n let _e111 = f;\n let _e116 = s.offset[1u];\n s.offset[1u] = (_e116 + (_e110 * sin(_e111)));\n let _e121 = dir_1[1u];\n let _e122 = a;\n let _e124 = f;\n let _e129 = s.offset[2u];\n s.offset[2u] = (_e129 + ((_e121 * _e122) * cos(_e124)));\n let _e134 = dir_1[0u];\n let _e135 = steepness;\n let _e137 = f;\n let _e142 = s.normal[0u];\n s.normal[0u] = (_e142 - ((_e134 * _e135) * cos(_e137)));\n let _e147 = dir_1[1u];\n let _e148 = steepness;\n let _e150 = f;\n let _e155 = s.normal[2u];\n s.normal[2u] = (_e155 - ((_e147 * _e148) * cos(_e150)));\n let _e159 = steepness;\n let _e160 = f;\n let _e165 = s.normal[1u];\n s.normal[1u] = (_e165 - (_e159 * sin(_e160)));\n let _e169 = f;\n let _e171 = steepness;\n let _e174 = s.crest;\n s.crest = (_e174 + (sin(_e169) * _e171));\n continue;\n } else {\n break;\n }\n continuing {\n let _e177 = i_2;\n i_2 = (_e177 + 1i);\n }\n }\n let _e179 = s;\n return _e179;\n}\n\nfn main_1() {\n var grid: vec2<f32>;\n var rimFraction: vec2<f32>;\n var rimDistance: f32;\n var waveFade: f32;\n var laid: vec2<f32>;\n var base: vec3<f32>;\n var sea: GerstnerSurface;\n var param_7: vec2<f32>;\n var param_8: f32;\n var param_9: vec2<f32>;\n var param_10: f32;\n var pos: vec3<f32>;\n\n let _e66 = aGrid_1;\n let _e68 = unnamed.uGridSpan;\n grid = (_e66 * _e68);\n let _e70 = grid;\n vGrid = _e70;\n let _e71 = grid;\n let _e74 = unnamed.uNearHalf;\n rimFraction = (abs(_e71) / max(_e74, vec2<f32>(0.0001f, 0.0001f)));\n let _e78 = rimFraction[0u];\n let _e80 = rimFraction[1u];\n rimDistance = max(_e78, _e80);\n let _e82 = rimDistance;\n waveFade = (1f - smoothstep(0.35f, 0.97f, _e82));\n let _e87 = unnamed.uGridForward[1u];\n let _e90 = unnamed.uGridForward[0u];\n let _e94 = grid[0u];\n let _e97 = unnamed.uGridForward;\n let _e99 = grid[1u];\n laid = ((vec2<f32>(_e87, -(_e90)) * _e94) + (_e97 * _e99));\n let _e104 = unnamed.uGridOrigin[0u];\n let _e106 = laid[0u];\n let _e109 = unnamed.uWaterLevel;\n let _e112 = unnamed.uGridOrigin[1u];\n let _e114 = laid[1u];\n base = vec3<f32>((_e104 + _e106), _e109, (_e112 + _e114));\n let _e117 = waveFade;\n let _e119 = unnamed.uWaveGain;\n let _e121 = base;\n param_7 = _e121.xz;\n let _e124 = unnamed.uTime;\n param_8 = _e124;\n let _e126 = unnamed.uWindDir;\n param_9 = _e126;\n param_10 = (_e117 * _e119);\n let _e127 = gerstnerSurface_u0028_vf2_u003b_f1_u003b_vf2_u003b_f1_u003b((&param_7), (&param_8), (&param_9), (&param_10));\n sea = _e127;\n let _e128 = base;\n let _e130 = sea.offset;\n pos = (_e128 + _e130);\n let _e132 = pos;\n vWorldPos = _e132;\n let _e134 = sea.normal;\n vNormal = normalize(_e134);\n let _e137 = sea.crest;\n vCrest = _e137;\n let _e139 = unnamed.uReflectionViewProj;\n let _e140 = pos;\n vReflectionClip = (_e139 * vec4<f32>(_e140.x, _e140.y, _e140.z, 1f));\n let _e147 = unnamed.uViewProj;\n let _e148 = pos;\n unnamed_1.gl_Position = (_e147 * vec4<f32>(_e148.x, _e148.y, _e148.z, 1f));\n return;\n}\n\n@vertex \nfn main(@location(0) aGrid: vec2<f32>) -> VertexOutput {\n aGrid_1 = aGrid;\n main_1();\n let _e10 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e10);\n let _e12 = vGrid;\n let _e13 = vWorldPos;\n let _e14 = vNormal;\n let _e15 = vCrest;\n let _e16 = vReflectionClip;\n let _e17 = unnamed_1.gl_Position;\n return VertexOutput(_e12, _e13, _e14, _e15, _e16, _e17);\n}\n";
9
9
  /**
10
10
  * What the transform assigned, so the renderer binds the same numbers.
@@ -14,7 +14,7 @@ export const WATER_VERT_WGSL = "struct GerstnerSurface {\n offset: vec3<f32>,
14
14
  export const WATER_BINDINGS = {
15
15
  "WATER_FRAG": {
16
16
  "uniforms": 1,
17
- "uniformSize": 1248,
17
+ "uniformSize": 1504,
18
18
  "fields": {
19
19
  "uGridHalf": {
20
20
  "offset": 0,
@@ -104,73 +104,80 @@ export const WATER_BINDINGS = {
104
104
  "size": 4,
105
105
  "type": "int"
106
106
  },
107
- "uFogColor": {
107
+ "uLightCone": {
108
108
  "offset": 1152,
109
+ "size": 256,
110
+ "type": "vec4",
111
+ "length": 16,
112
+ "stride": 16
113
+ },
114
+ "uFogColor": {
115
+ "offset": 1408,
109
116
  "size": 12,
110
117
  "type": "vec3"
111
118
  },
112
119
  "uFogDensity": {
113
- "offset": 1164,
120
+ "offset": 1420,
114
121
  "size": 4,
115
122
  "type": "float"
116
123
  },
117
124
  "uFogHeightFalloff": {
118
- "offset": 1168,
125
+ "offset": 1424,
119
126
  "size": 4,
120
127
  "type": "float"
121
128
  },
122
129
  "uFogEyeY": {
123
- "offset": 1172,
130
+ "offset": 1428,
124
131
  "size": 4,
125
132
  "type": "float"
126
133
  },
127
134
  "uUnderwaterColor": {
128
- "offset": 1184,
135
+ "offset": 1440,
129
136
  "size": 12,
130
137
  "type": "vec3"
131
138
  },
132
139
  "uUnderwaterFogDensity": {
133
- "offset": 1196,
140
+ "offset": 1452,
134
141
  "size": 4,
135
142
  "type": "float"
136
143
  },
137
144
  "uUnderwaterFactor": {
138
- "offset": 1200,
145
+ "offset": 1456,
139
146
  "size": 4,
140
147
  "type": "float"
141
148
  },
142
149
  "uFogMode": {
143
- "offset": 1204,
150
+ "offset": 1460,
144
151
  "size": 4,
145
152
  "type": "int"
146
153
  },
147
154
  "uFogNear": {
148
- "offset": 1208,
155
+ "offset": 1464,
149
156
  "size": 4,
150
157
  "type": "float"
151
158
  },
152
159
  "uFogFar": {
153
- "offset": 1212,
160
+ "offset": 1468,
154
161
  "size": 4,
155
162
  "type": "float"
156
163
  },
157
164
  "uCameraPos": {
158
- "offset": 1216,
165
+ "offset": 1472,
159
166
  "size": 12,
160
167
  "type": "vec3"
161
168
  },
162
169
  "uReflectionEnabled": {
163
- "offset": 1228,
170
+ "offset": 1484,
164
171
  "size": 4,
165
172
  "type": "int"
166
173
  },
167
174
  "uReflectionTexelSize": {
168
- "offset": 1232,
175
+ "offset": 1488,
169
176
  "size": 8,
170
177
  "type": "vec2"
171
178
  },
172
179
  "uReflectionFilterTaps": {
173
- "offset": 1240,
180
+ "offset": 1496,
174
181
  "size": 4,
175
182
  "type": "int"
176
183
  }
@@ -672,15 +672,19 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
672
672
  var sum = vec3<f32>(0.0);
673
673
  let count = u32(bake.lightCount);
674
674
  for (var i = 0u; i < count; i = i + 1u) {
675
- let at = bounceLights[i * 3u];
676
- let colour = bounceLights[i * 3u + 1u];
677
- let aim = bounceLights[i * 3u + 2u];
675
+ let at = bounceLights[i * 4u];
676
+ let colour = bounceLights[i * 4u + 1u];
677
+ let aim = bounceLights[i * 4u + 2u];
678
+ let lightExponent = bounceLights[i * 4u + 3u].x;
678
679
  let lightRadius = at.w;
679
680
  let toLight = at.xyz - p;
680
681
  let dist = length(toLight);
681
682
  let coneFalloff = smoothstep(aim.w, colour.w, dot(-toLight / max(dist, 1e-4), aim.xyz));
682
683
  var falloff: f32;
683
- if (bake.falloff == 1.0) {
684
+ if (lightExponent > 0.0) {
685
+ let reach = dist / max(lightRadius, 1e-4);
686
+ falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), lightExponent);
687
+ } else if (bake.falloff == 1.0) {
684
688
  let window = clamp(1.0 - pow(dist / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
685
689
  falloff = window * window / max(dist * dist, 0.01);
686
690
  } else {
@@ -689,7 +693,7 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
689
693
  if (falloff <= 0.0) { continue; }
690
694
  let ndl = max(dot(n, toLight / max(dist, 1e-4)), 0.0);
691
695
  if (ndl <= 0.0) { continue; }
692
- let shape = select(falloff * falloff, falloff, bake.falloff == 1.0) * coneFalloff;
696
+ let shape = select(falloff * falloff, falloff, lightExponent > 0.0 || bake.falloff == 1.0) * coneFalloff;
693
697
  if (shape <= 0.0) { continue; }
694
698
  let fixture = max(0.3, 2.0 * march.finestStep);
695
699
  let seen = probeLampVisible(p, n, toLight / max(dist, 1e-4), dist - fixture);
@@ -37,7 +37,7 @@
37
37
  export declare const PARTICLE_BLADES = 2;
38
38
  export declare const PARTICLE_VERTS: number;
39
39
  export declare const PARTICLE_INDICES: number;
40
- export declare const PARTICLE_VERT = "#version 300 es\n/** Per-vertex: the quad corner, and which blade of the cross it belongs to. */\nlayout(location = 0) in vec2 aCorner;\nlayout(location = 1) in float aBlade;\n/** Per-instance. */\nlayout(location = 2) in vec3 aPos;\nlayout(location = 3) in float aSize;\nlayout(location = 4) in float aSpin;\nlayout(location = 5) in vec3 aColor;\n/** Opacity, age as a share of life, and this particle's own seed. */\nlayout(location = 6) in float aAlpha;\nlayout(location = 7) in float aAge;\nlayout(location = 8) in float aSeed;\nlayout(location = 9) in vec3 aVelocity;\n\nuniform mat4 uViewProj;\nuniform vec3 uCameraPos;\n/**\n * How far a particle is stretched along its own motion, in seconds of travel.\n *\n * Zero for smoke, which has no direction worth seeing. A spark is the opposite\n * case: an ember crossing a metre in a frame drawn as a round dot reads as a\n * floating pixel, and the same ember drawn as a short streak along its velocity\n * reads as a thrown spark. The eye gets its motion cue from the shape.\n */\nuniform float uStretchSec;\n/**\n * 1 to turn a single quad to face the camera, 0 to draw the world-fixed cross.\n *\n * **Camera-facing is the default, and the cross is the option.** It was the other way\n * round until a caller drew an additive glow with a camera looking down -Z and got a\n * bright straight line through the middle of every sprite. The cross is two blades at\n * right angles in *world* space: blade 0 spans X, blade 1 spans Z. Look down either\n * axis and that blade is edge-on, and a disc squeezed into a one-pixel column does not\n * lose the energy it was drawn with \u2014 it puts all of it in that column. Alpha-blended\n * smoke half hides it behind its own noise. Additive has nothing to hide it with.\n *\n * Fading a blade out as it turns edge-on fixes that one case and not the matching one:\n * both blades contain world Y, so a camera looking straight down sees *both* edge-on\n * and the puff is a plus sign of two lines, or nothing at all once they are faded. A\n * camera-facing quad has no angle at which it thins, which is why it is what particle\n * systems generally draw.\n *\n * The cross is still worth keeping. Two blades give real parallax when a camera moves\n * through a puff, and that is a volume cue a single quad cannot fake \u2014 worth having in\n * a ground-level world whose camera never looks far off the horizontal, where the\n * edge-on blade stays a thin sliver inside soft smoke rather than a line across a\n * spark. It also costs a brightness that swings with the view: a cross covers one\n * sprite of area seen down an axis and about 1.4 seen from the diagonal, so a puff\n * quietly pulses as the camera orbits it. A camera-facing quad covers the same area\n * from everywhere.\n */\nuniform float uCameraFacing;\n\nout vec2 vUv;\nout vec3 vColor;\nout float vAlpha;\nout float vAge;\nout float vSeed;\nout vec3 vWorldPos;\nout vec3 vNormal;\n\nvoid main() {\n float s = sin(aSpin);\n float c = cos(aSpin);\n // Roll within the blade's own plane, so the two blades never look like one\n // rigid object turning.\n vec2 rolled = vec2(aCorner.x * c - aCorner.y * s, aCorner.x * s + aCorner.y * c);\n\n /*\n * The plane the corners are laid out in, built from the particle's centre rather than\n * from the corner being transformed, so all four corners of a quad share one basis and\n * the quad stays planar and rectangular.\n */\n vec3 across;\n vec3 up;\n float bladeGain;\n if (uCameraFacing > 0.5) {\n vec3 toCamera = normalize(uCameraPos - aPos);\n /* Any reference not parallel to the view. World up serves everywhere except looking\n straight up or down it, where the two are the same line and the cross product\n vanishes; +Z takes over there. */\n vec3 reference = abs(toCamera.y) > 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n across = normalize(cross(reference, toCamera));\n up = cross(toCamera, across);\n /*\n * One quad, not two. Both blades face the camera here, so they would land on exactly\n * the same pixels and the second would be nothing but the first drawn again: double\n * the fill for double the brightness no caller asked for. Collapsing it to zero area\n * costs nothing \u2014 the rasteriser drops a degenerate triangle before it shades \u2014 and\n * leaves the vertex and index buffers the shape both backends already build, so a\n * batch picks its facing without either one rebuilding its geometry.\n */\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else {\n across = aBlade < 0.5 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0);\n up = vec3(0.0, 1.0, 0.0);\n bladeGain = 1.0;\n }\n vec3 offset = (across * rolled.x + up * rolled.y) * aSize;\n\n /*\n * Stretched along travel. Applied as an extra displacement on the component of\n * the corner that already points along the motion, so a stationary particle is\n * untouched and a fast one lengthens without also getting fatter.\n */\n if (uStretchSec > 0.0) {\n vec3 travel = aVelocity * uStretchSec;\n float along = dot(normalize(offset + vec3(1e-6)), normalize(travel + vec3(1e-6)));\n offset += travel * along * 0.5;\n }\n\n /* bladeGain applied here, after the stretch rather than before it, so a dropped blade\n is degenerate whatever the stretch added: all four of its corners land on aPos. */\n vec3 world = aPos + offset * bladeGain;\n vUv = aCorner;\n vColor = aColor;\n vAlpha = aAlpha;\n vAge = aAge;\n vSeed = aSeed;\n vWorldPos = world;\n /*\n * The blade's own facing, flipped toward the viewer. A cross has no meaningful\n * outward normal, and a puff of smoke is not a surface \u2014 but it does have to be\n * *lit*, and a normal facing the camera is the standard, honest cheat for a\n * volume approximated by cards: it makes the puff read as a lump catching light\n * from wherever the light is, rather than as a flat panel that goes black at\n * some angles.\n */\n vec3 face = cross(across, up);\n vec3 toEye = normalize(uCameraPos - world);\n vNormal = dot(face, toEye) < 0.0 ? -face : face;\n\n gl_Position = uViewProj * vec4(world, 1.0);\n}\n";
40
+ export declare const PARTICLE_VERT = "#version 300 es\n/** Per-vertex: the quad corner, and which blade of the cross it belongs to. */\nlayout(location = 0) in vec2 aCorner;\nlayout(location = 1) in float aBlade;\n/** Per-instance. */\nlayout(location = 2) in vec3 aPos;\nlayout(location = 3) in float aSize;\nlayout(location = 4) in float aSpin;\nlayout(location = 5) in vec3 aColor;\n/** Opacity, age as a share of life, and this particle's own seed. */\nlayout(location = 6) in float aAlpha;\nlayout(location = 7) in float aAge;\nlayout(location = 8) in float aSeed;\nlayout(location = 9) in vec3 aVelocity;\n/**\n * A sprite's flipbook frame and its half-height: `ParticleInstances.frames` and `heights`. The\n * procedural materials read neither, and a height of 0 is a square, its height its width.\n */\nlayout(location = 10) in vec2 aSprite;\n\nuniform mat4 uViewProj;\nuniform vec3 uCameraPos;\n/**\n * How far a particle is stretched along its own motion, in seconds of travel.\n *\n * Zero for smoke, which has no direction worth seeing. A spark is the opposite\n * case: an ember crossing a metre in a frame drawn as a round dot reads as a\n * floating pixel, and the same ember drawn as a short streak along its velocity\n * reads as a thrown spark. The eye gets its motion cue from the shape.\n */\nuniform float uStretchSec;\n/**\n * 1 to turn a single quad to face the camera, 0 to draw the world-fixed cross.\n *\n * **Camera-facing is the default, and the cross is the option.** It was the other way\n * round until a caller drew an additive glow with a camera looking down -Z and got a\n * bright straight line through the middle of every sprite. The cross is two blades at\n * right angles in *world* space: blade 0 spans X, blade 1 spans Z. Look down either\n * axis and that blade is edge-on, and a disc squeezed into a one-pixel column does not\n * lose the energy it was drawn with \u2014 it puts all of it in that column. Alpha-blended\n * smoke half hides it behind its own noise. Additive has nothing to hide it with.\n *\n * Fading a blade out as it turns edge-on fixes that one case and not the matching one:\n * both blades contain world Y, so a camera looking straight down sees *both* edge-on\n * and the puff is a plus sign of two lines, or nothing at all once they are faded. A\n * camera-facing quad has no angle at which it thins, which is why it is what particle\n * systems generally draw.\n *\n * The cross is still worth keeping. Two blades give real parallax when a camera moves\n * through a puff, and that is a volume cue a single quad cannot fake \u2014 worth having in\n * a ground-level world whose camera never looks far off the horizontal, where the\n * edge-on blade stays a thin sliver inside soft smoke rather than a line across a\n * spark. It also costs a brightness that swings with the view: a cross covers one\n * sprite of area seen down an axis and about 1.4 seen from the diagonal, so a puff\n * quietly pulses as the camera orbits it. A camera-facing quad covers the same area\n * from everywhere.\n */\nuniform float uCameraFacing;\n/** How far each particle is moved toward the eye, in metres: `ParticleBatchOptions.cameraOffset`. */\nuniform float uCameraOffset;\n\nout vec2 vUv;\nout vec3 vColor;\nout float vAlpha;\nout float vAge;\nout float vSeed;\nout vec3 vWorldPos;\nout vec3 vNormal;\n/** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */\nout float vFrame;\nout float vViewDepth;\n\nvoid main() {\n float s = sin(aSpin);\n float c = cos(aSpin);\n // Roll within the blade's own plane, so the two blades never look like one\n // rigid object turning.\n vec2 rolled = vec2(aCorner.x * c - aCorner.y * s, aCorner.x * s + aCorner.y * c);\n\n /*\n * The plane the corners are laid out in, built from the particle's centre rather than\n * from the corner being transformed, so all four corners of a quad share one basis and\n * the quad stays planar and rectangular.\n */\n vec3 across;\n vec3 up;\n float bladeGain;\n /*\n * **Along its travel**, `facing: 'velocity'` (2): the quad's up is the velocity seen across\n * the view, so a streak of light or a stretched spark lies along its motion and still faces the\n * eye. A particle at rest has no such direction and faces the camera as the default does.\n */\n vec3 viewer = normalize(uCameraPos - aPos);\n vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);\n if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {\n up = normalize(alongView);\n across = cross(up, viewer);\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else if (uCameraFacing > 0.5) {\n vec3 toCamera = normalize(uCameraPos - aPos);\n /* Any reference not parallel to the view. World up serves everywhere except looking\n straight up or down it, where the two are the same line and the cross product\n vanishes; +Z takes over there. */\n vec3 reference = abs(toCamera.y) > 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n across = normalize(cross(reference, toCamera));\n up = cross(toCamera, across);\n /*\n * One quad, not two. Both blades face the camera here, so they would land on exactly\n * the same pixels and the second would be nothing but the first drawn again: double\n * the fill for double the brightness no caller asked for. Collapsing it to zero area\n * costs nothing \u2014 the rasteriser drops a degenerate triangle before it shades \u2014 and\n * leaves the vertex and index buffers the shape both backends already build, so a\n * batch picks its facing without either one rebuilding its geometry.\n */\n bladeGain = aBlade < 0.5 ? 1.0 : 0.0;\n } else {\n across = aBlade < 0.5 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0);\n up = vec3(0.0, 1.0, 0.0);\n bladeGain = 1.0;\n }\n vec3 offset = (across * rolled.x + up * rolled.y) * aSize;\n /*\n * **A sprite of two sizes** \u2014 a half-width and a half-height \u2014 rolls as a rectangle: the corner\n * is scaled first and turned after. Only where a height was given, so a square particle keeps\n * the arithmetic it always had to the bit.\n */\n if (aSprite.y > 0.0) {\n vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);\n vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);\n offset = across * turned.x + up * turned.y;\n }\n\n /*\n * Stretched along travel. Applied as an extra displacement on the component of\n * the corner that already points along the motion, so a stationary particle is\n * untouched and a fast one lengthens without also getting fatter.\n */\n if (uStretchSec > 0.0) {\n vec3 travel = aVelocity * uStretchSec;\n float along = dot(normalize(offset + vec3(1e-6)), normalize(travel + vec3(1e-6)));\n offset += travel * along * 0.5;\n }\n\n /* bladeGain applied here, after the stretch rather than before it, so a dropped blade\n is degenerate whatever the stretch added: all four of its corners land on aPos. */\n vec3 world = aPos + offset * bladeGain;\n /* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */\n world += viewer * uCameraOffset * bladeGain;\n vUv = aCorner;\n vFrame = aSprite.x;\n vColor = aColor;\n vAlpha = aAlpha;\n vAge = aAge;\n vSeed = aSeed;\n vWorldPos = world;\n /*\n * The blade's own facing, flipped toward the viewer. A cross has no meaningful\n * outward normal, and a puff of smoke is not a surface \u2014 but it does have to be\n * *lit*, and a normal facing the camera is the standard, honest cheat for a\n * volume approximated by cards: it makes the puff read as a lump catching light\n * from wherever the light is, rather than as a flat panel that goes black at\n * some angles.\n */\n vec3 face = cross(across, up);\n vec3 toEye = normalize(uCameraPos - world);\n vNormal = dot(face, toEye) < 0.0 ? -face : face;\n\n gl_Position = uViewProj * vec4(world, 1.0);\n /* A perspective projection's w is the depth from the eye, which is what the fades measure. */\n vViewDepth = gl_Position.w;\n}\n";
41
41
  /**
42
42
  * Smoke, grit and dust: a soft body eroded by noise, alpha blended, and lit.
43
43
  *
@@ -80,3 +80,19 @@ export declare const PARTICLE_SPARK_FRAG = "#version 300 es\nprecision highp flo
80
80
  * existed; see `ParticleBatchOptions.fog`.
81
81
  */
82
82
  export declare const PARTICLE_MOTE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\nin vec3 vColor;\nin float vAlpha;\nin float vAge;\nin float vSeed;\nin vec3 vWorldPos;\nin vec3 vNormal;\n\nuniform vec3 uCameraPos;\n/**\n * Whether the fragment stage mixes toward the medium's own colour at all. 0 \u2014 what a batch\n * gets by leaving ParticleBatchOptions.fog unset \u2014 leaves vColor untouched by distance, which\n * is what a caller reaching for this material over 'spark'/'smoke' is usually asking for: an\n * unlit point drawn exactly as its own colour says. 1 mixes toward mediumColor() by\n * mediumFog(), the same medium every other pass in the frame recedes into.\n */\nuniform int uFogEnabled;\n\nuniform vec3 uFogColor;\n/** Extinction per metre **at the camera's own height**, not at the base. */\nuniform float uFogDensity;\n/** Reciprocal scale height of the haze, per metre. 0 is a uniform medium. */\nuniform float uFogHeightFalloff;\n/** The camera's height, so a fragment's own height is one subtraction away. */\nuniform float uFogEyeY;\nuniform vec3 uUnderwaterColor;\nuniform float uUnderwaterFogDensity;\nuniform float uUnderwaterFactor;\n/** 0 integrates a medium (Beer-Lambert); 1 ramps linearly between near and far. */\nuniform int uFogMode;\n/** Linear mode only: where haze begins, and where it is total. */\nuniform float uFogNear;\nuniform float uFogFar;\n\n/** What distance converges toward: the day's haze, or the water column. */\nvec3 mediumColor() {\n return mix(uFogColor, uUnderwaterColor, uUnderwaterFactor);\n}\n\n/**\n * How much of a surface standing at height pointY, dist metres away, is lost to\n * the medium in between. 0 is perfectly clear, 1 is the medium's own colour.\n */\nfloat mediumFog(float dist, float pointY) {\n /*\n * A depth ramp rather than a medium, for worlds authored against one.\n *\n * Beer-Lambert is the honest model and stays the default: it integrates something\n * physically there, so extinction begins at the eye and never stops. A linear near/far\n * fog is not that \u2014 it is a *look*, with a hard start distance before which the air is\n * perfectly clear, and it is what the fixed-function pipeline offered and what several\n * renderers still expose. Approximating one with the other is not a matter of tuning:\n * matched at half occlusion, the exponential curve still lays haze over everything\n * inside near, where the ramp lays none, and near-field colour desaturates toward the\n * fog colour with nothing to show for it.\n *\n * Height falloff has no meaning here and is ignored: a ramp is defined on distance\n * alone.\n */\n if (uFogMode == 1) {\n float span = max(uFogFar - uFogNear, 1e-4);\n float ramp = clamp((dist - uFogNear) / span, 0.0, 1.0);\n if (uUnderwaterFactor <= 0.0) return ramp;\n float wetLinear = uUnderwaterFogDensity * dist;\n return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), uUnderwaterFactor);\n }\n\n float t = (pointY - uFogEyeY) * uFogHeightFalloff;\n // (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than\n // branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.\n float denom = abs(t) < 1e-4 ? 1e-4 : t;\n float air = 1.0 - exp(-uFogDensity * dist * (1.0 - exp(-denom)) / denom);\n if (uUnderwaterFactor <= 0.0) return air;\n // Water is its own medium and keeps its own curve, so crossing the surface\n // changes nothing about how the water column reads.\n float wet = uUnderwaterFogDensity * dist;\n return mix(air, 1.0 - exp2(-wet * wet * 1.442695), uUnderwaterFactor);\n}\n\n\nout vec4 outColor;\n\n/** 0 none, 1 sRGB, 2 ACES then sRGB, 3 the highlight shoulder then sRGB. See renderQuality.ts. */\nuniform int uOutputTransform;\n/** Scales the scene into the tone curve. 1 is the reference grade. See renderQuality.ts. */\nuniform float uOutputExposure;\n\n/*\n * ACES: the reference fit, not the cheap one.\n *\n * This was Narkowicz's single rational function, on the argument that the difference from\n * the reference curve is invisible next to the gain. That argument holds for a look and\n * fails for a *match*, which is what this option is for \u2014 aces exists so a world ported\n * from three.js reads the same, and three.js applies the full fit: into AP1 through\n * ACESInputMat, the RRT and ODT together, then back out through ACESOutputMat.\n *\n * The two curves are not a shade apart. Narkowicz is markedly brighter through the\n * midtones, which is exactly where an interior scene lives, and it is a large part of a\n * measured ~1.7x that survived correcting the colour space and the missing Lambert pi.\n * An approximation of a standard is the wrong thing to hold when the standard is the\n * specification.\n */\nvec3 rrtAndOdtFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\n\nvec3 acesFilmic(vec3 x) {\n /*\n * Exposure first, because a tone curve is only defined relative to one.\n *\n * The reference RRT and ODT are graded for a dark cinema. Every renderer that ships ACES\n * scales its input to suit whatever it is actually shown on, and the factor each one\n * picks is a *look*, not a standard - so this engine takes no position and reads it from\n * the caller. Neutral by default, which is the reference grade unmodified. three.js's own\n * pick is 1 / 0.6, for a consumer matching one.\n */\n x *= uOutputExposure;\n // Columns, as GLSL reads them: sRGB -> XYZ -> D65_2_D60 -> AP1 -> RRT_SAT.\n const mat3 ACES_INPUT = mat3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777\n );\n // ODT_SAT -> XYZ -> D60_2_D65 -> sRGB.\n const mat3 ACES_OUTPUT = mat3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602\n );\n return clamp(ACES_OUTPUT * rrtAndOdtFit(ACES_INPUT * x), 0.0, 1.0);\n}\n\n/*\n * Linear to sRGB. The piecewise form rather than a plain pow: the linear toe near black\n * is where banding lives in an 8-bit buffer, and the cheap approximation puts it in the\n * wrong place.\n */\nvec3 linearToSrgb(vec3 c) {\n vec3 low = c * 12.92;\n vec3 high = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;\n return mix(high, low, step(c, vec3(0.0031308)));\n}\n\n\nvec3 highlightShoulder(vec3 c) {\n float m = max(c.r, max(c.g, c.b));\n if (m <= 0.8) return c;\n float e = m - 0.8;\n return c * ((0.8 + 0.2 * e / (e + 0.2)) / m);\n}\n\nvec3 applyOutputTransform(vec3 c) {\n if (uOutputTransform == 0) return c;\n if (uOutputTransform == 2) c = acesFilmic(c);\n if (uOutputTransform == 3) c = highlightShoulder(c * uOutputExposure);\n return linearToSrgb(c);\n}\n\n\nvoid main() {\n /*\n * One soft, round falloff \u2014 no erosion, no core past white, because this material draws\n * neither a body being eaten by noise nor a light source. Squared rather than linear so the\n * edge feathers instead of ramping, which is what keeps a field of these reading as dust\n * rather than as a scatter of hard-edged discs.\n */\n float r = length(vUv);\n float body = 1.0 - smoothstep(0.4, 1.0, r);\n if (body <= 0.0) discard;\n float alpha = body * body * vAlpha;\n if (alpha <= 0.002) discard;\n\n vec3 color = vColor;\n if (uFogEnabled != 0) {\n float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);\n color = mix(color, mediumColor(), fog);\n }\n /*\n * A real alpha blend (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) rather than spark's energy-in-colour\n * trick: this point genuinely occludes what little is behind it instead of adding light to it.\n */\n outColor = vec4(applyOutputTransform(color), alpha);\n}\n";
83
+ /**
84
+ * A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
85
+ * each particle carries — what another engine's sprite renderer draws, and the material the three
86
+ * procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
87
+ *
88
+ * **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
89
+ * a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
90
+ * so a slow flipbook does not step.
91
+ *
92
+ * **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
93
+ * of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
94
+ * a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
95
+ * through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
96
+ * is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
97
+ */
98
+ export declare const PARTICLE_SPRITE_FRAG: string;
@@ -1,5 +1,6 @@
1
1
  import { FOG_GLSL } from './fog.js';
2
2
  import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.js';
3
+ import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.js';
3
4
  import { MAX_POINT_LIGHTS } from '../lightBudget.js';
4
5
  /**
5
6
  * Particle materials: soft smoke, hot sparks, and unlit motes.
@@ -83,6 +84,11 @@ layout(location = 6) in float aAlpha;
83
84
  layout(location = 7) in float aAge;
84
85
  layout(location = 8) in float aSeed;
85
86
  layout(location = 9) in vec3 aVelocity;
87
+ /**
88
+ * A sprite's flipbook frame and its half-height: \`ParticleInstances.frames\` and \`heights\`. The
89
+ * procedural materials read neither, and a height of 0 is a square, its height its width.
90
+ */
91
+ layout(location = 10) in vec2 aSprite;
86
92
 
87
93
  uniform mat4 uViewProj;
88
94
  uniform vec3 uCameraPos;
@@ -122,6 +128,8 @@ uniform float uStretchSec;
122
128
  * from everywhere.
123
129
  */
124
130
  uniform float uCameraFacing;
131
+ /** How far each particle is moved toward the eye, in metres: \`ParticleBatchOptions.cameraOffset\`. */
132
+ uniform float uCameraOffset;
125
133
 
126
134
  out vec2 vUv;
127
135
  out vec3 vColor;
@@ -130,6 +138,9 @@ out float vAge;
130
138
  out float vSeed;
131
139
  out vec3 vWorldPos;
132
140
  out vec3 vNormal;
141
+ /** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */
142
+ out float vFrame;
143
+ out float vViewDepth;
133
144
 
134
145
  void main() {
135
146
  float s = sin(aSpin);
@@ -146,7 +157,18 @@ void main() {
146
157
  vec3 across;
147
158
  vec3 up;
148
159
  float bladeGain;
149
- if (uCameraFacing > 0.5) {
160
+ /*
161
+ * **Along its travel**, \`facing: 'velocity'\` (2): the quad's up is the velocity seen across
162
+ * the view, so a streak of light or a stretched spark lies along its motion and still faces the
163
+ * eye. A particle at rest has no such direction and faces the camera as the default does.
164
+ */
165
+ vec3 viewer = normalize(uCameraPos - aPos);
166
+ vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);
167
+ if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {
168
+ up = normalize(alongView);
169
+ across = cross(up, viewer);
170
+ bladeGain = aBlade < 0.5 ? 1.0 : 0.0;
171
+ } else if (uCameraFacing > 0.5) {
150
172
  vec3 toCamera = normalize(uCameraPos - aPos);
151
173
  /* Any reference not parallel to the view. World up serves everywhere except looking
152
174
  straight up or down it, where the two are the same line and the cross product
@@ -169,6 +191,16 @@ void main() {
169
191
  bladeGain = 1.0;
170
192
  }
171
193
  vec3 offset = (across * rolled.x + up * rolled.y) * aSize;
194
+ /*
195
+ * **A sprite of two sizes** — a half-width and a half-height — rolls as a rectangle: the corner
196
+ * is scaled first and turned after. Only where a height was given, so a square particle keeps
197
+ * the arithmetic it always had to the bit.
198
+ */
199
+ if (aSprite.y > 0.0) {
200
+ vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);
201
+ vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);
202
+ offset = across * turned.x + up * turned.y;
203
+ }
172
204
 
173
205
  /*
174
206
  * Stretched along travel. Applied as an extra displacement on the component of
@@ -184,7 +216,10 @@ void main() {
184
216
  /* bladeGain applied here, after the stretch rather than before it, so a dropped blade
185
217
  is degenerate whatever the stretch added: all four of its corners land on aPos. */
186
218
  vec3 world = aPos + offset * bladeGain;
219
+ /* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */
220
+ world += viewer * uCameraOffset * bladeGain;
187
221
  vUv = aCorner;
222
+ vFrame = aSprite.x;
188
223
  vColor = aColor;
189
224
  vAlpha = aAlpha;
190
225
  vAge = aAge;
@@ -203,6 +238,8 @@ void main() {
203
238
  vNormal = dot(face, toEye) < 0.0 ? -face : face;
204
239
 
205
240
  gl_Position = uViewProj * vec4(world, 1.0);
241
+ /* A perspective projection's w is the depth from the eye, which is what the fades measure. */
242
+ vViewDepth = gl_Position.w;
206
243
  }
207
244
  `;
208
245
  /**
@@ -429,3 +466,98 @@ void main() {
429
466
  outColor = vec4(applyOutputTransform(color), alpha);
430
467
  }
431
468
  `;
469
+ /**
470
+ * A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
471
+ * each particle carries — what another engine's sprite renderer draws, and the material the three
472
+ * procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
473
+ *
474
+ * **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
475
+ * a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
476
+ * so a slow flipbook does not step.
477
+ *
478
+ * **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
479
+ * of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
480
+ * a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
481
+ * through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
482
+ * is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
483
+ */
484
+ export const PARTICLE_SPRITE_FRAG = `#version 300 es
485
+ precision highp float;
486
+
487
+ /* Every output of the vertex stage, in its order, read or not: the generated stages number their
488
+ locations in declaration order, so a fragment declaring a subset reads the wrong ones. */
489
+ in vec2 vUv;
490
+ in vec3 vColor;
491
+ in float vAlpha;
492
+ in float vAge;
493
+ in float vSeed;
494
+ in vec3 vWorldPos;
495
+ in vec3 vNormal;
496
+ in float vFrame;
497
+ in float vViewDepth;
498
+
499
+ /** The image, a surface texture: its first layer. */
500
+ uniform mediump sampler2DArray uSprite;
501
+ /** The frame's opaque depth, copied, for the soft edge. highp for the reason depth always is. */
502
+ uniform highp sampler2D uDepth;
503
+ /** Columns, rows, and 1 to blend between cells. */
504
+ uniform vec4 uCells;
505
+ /** The soft edge's distance, the eye fade's, and whether the depth copy is there to read. */
506
+ uniform vec4 uFade;
507
+ /** A depth sample to view-space metres, as the skin blur takes it. */
508
+ uniform vec4 uDepthToViewZ;
509
+ /** The target's size in pixels, so a fragment finds its own texel of the depth copy. */
510
+ uniform vec2 uViewport;
511
+ uniform vec3 uCameraPos;
512
+ /** Whether the colour recedes into the medium: \`ParticleBatchOptions.fog\`. */
513
+ uniform int uFogEnabled;
514
+ ${FOG_GLSL}
515
+
516
+ out vec4 outColor;
517
+ ${OUTPUT_TRANSFORM_GLSL}
518
+
519
+ /** Cell \`cell\` of the flipbook, at \`uv\` inside it. Integer arithmetic: see AGENTS.md. */
520
+ vec2 cellUv(int cell, vec2 uv) {
521
+ int columns = max(int(uCells.x + 0.5), 1);
522
+ int rows = max(int(uCells.y + 0.5), 1);
523
+ int wrapped = cell % (columns * rows);
524
+ int column = wrapped % columns;
525
+ int row = wrapped / columns;
526
+ return (vec2(float(column), float(row)) + uv) / vec2(float(columns), float(rows));
527
+ }
528
+
529
+ float viewZ(float depth) {
530
+ float ndc = ${glslSceneDepthToNdc('depth')};
531
+ return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
532
+ }
533
+
534
+ void main() {
535
+ /* The corner's square to the image's: v runs down an image, and up a quad. */
536
+ vec2 uv = vec2(vUv.x * 0.5 + 0.5, 0.5 - vUv.y * 0.5);
537
+ float frame = max(vFrame, 0.0);
538
+ int first = int(floor(frame));
539
+ vec4 image = texture(uSprite, vec3(cellUv(first, uv), 0.0));
540
+ if (uCells.z > 0.5) {
541
+ vec4 next = texture(uSprite, vec3(cellUv(first + 1, uv), 0.0));
542
+ image = mix(image, next, fract(frame));
543
+ }
544
+
545
+ float fade = 1.0;
546
+ if (uFade.z > 0.5 && uFade.x > 0.0) {
547
+ float depth = textureLod(uDepth, gl_FragCoord.xy / uViewport, 0.0).r;
548
+ if (!(${glslIsFarDepth('depth')})) {
549
+ fade = clamp((abs(viewZ(depth)) - vViewDepth) / uFade.x, 0.0, 1.0);
550
+ }
551
+ }
552
+ if (uFade.y > 0.0) fade *= clamp(vViewDepth / uFade.y, 0.0, 1.0);
553
+
554
+ float alpha = vAlpha * image.a * fade;
555
+ if (alpha <= 0.001) discard;
556
+ vec3 color = vColor * image.rgb;
557
+ if (uFogEnabled != 0) {
558
+ float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
559
+ color = mix(color, mediumColor(), fog);
560
+ }
561
+ outColor = vec4(applyOutputTransform(color), alpha);
562
+ }
563
+ `;
@@ -41,4 +41,4 @@ export declare const MOTION_DRAW_STRIDE = 256;
41
41
  export declare const MOTION_DRAW_FLOATS = 32;
42
42
  /** The motion target's format. Half float holds a uv to a hundredth of a pixel and a view depth. */
43
43
  export declare const MOTION_TARGET_FORMAT: GPUTextureFormat;
44
- export declare const RECON_MOTION_WGSL = "\nstruct MotionFrame {\n /* The scene's own clip transform, with the generated shaders' y negation already folded in. */\n raster: mat4x4<f32>,\n /* This frame's and last frame's view-projections, unjittered, in the matrices' own convention. */\n viewProj: mat4x4<f32>,\n previousViewProj: mat4x4<f32>,\n}\n\nstruct MotionDraw {\n model: mat4x4<f32>,\n previousModel: mat4x4<f32>,\n}\n\n@group(0) @binding(0) var<uniform> frame: MotionFrame;\n@group(0) @binding(1) var<uniform> draw: MotionDraw;\n\nstruct Varyings {\n @builtin(position) clip: vec4<f32>,\n /* Clip positions rather than uv: an interpolator divides by w for us, so dividing these by their\n own interpolated w in the fragment stage is the perspective-correct answer at every pixel. */\n @location(0) now: vec4<f32>,\n @location(1) was: vec4<f32>,\n}\n\n@vertex\nfn motionVert(@location(0) position: vec3<f32>) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/* Normalised device coordinates to the uv the resolve measures in: v increases upward, as the\n references' images do, and the accessor that reads this target is what flips the texture's rows. */\nfn motionUv(clip: vec4<f32>) -> vec2<f32> {\n return clip.xy / clip.w * 0.5 + vec2<f32>(0.5);\n}\n\n/*\n * The skinned stage: the joints this frame and last frame, read from two palette textures of the\n * frame's ring. The same four weighted products in the same order as the scene's own skinning, so\n * the depth this stage produces is the depth the scene wrote.\n */\n@group(1) @binding(0) var palette: texture_2d<f32>;\n@group(1) @binding(1) var previousPalette: texture_2d<f32>;\n\nfn jointOf(source: texture_2d<f32>, index: i32) -> mat4x4<f32> {\n let x = index * 4;\n return mat4x4<f32>(\n textureLoad(source, vec2<i32>(x, 0), 0),\n textureLoad(source, vec2<i32>(x + 1, 0), 0),\n textureLoad(source, vec2<i32>(x + 2, 0), 0),\n textureLoad(source, vec2<i32>(x + 3, 0), 0));\n}\n\nfn skinOf(source: texture_2d<f32>, joints: vec4<f32>, weights: vec4<f32>) -> mat4x4<f32> {\n return jointOf(source, i32(joints.x)) * weights.x\n + jointOf(source, i32(joints.y)) * weights.y\n + jointOf(source, i32(joints.z)) * weights.z\n + jointOf(source, i32(joints.w)) * weights.w;\n}\n\n@vertex\nfn motionSkinnedVert(\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n) -> Varyings {\n let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));\n let wasWorld =\n draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The same for a rig with eight influences a vertex, the second four at the scene's own locations\n * 14 and 15 and summed after the first, as `skinMatrix()` sums them with `SKIN_EIGHT` on \u2014 or the\n * motion of a face or a shoulder would be the four heaviest joints' alone.\n */\n@vertex\nfn motionSkinnedEightVert(\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n @location(14) joints2: vec4<f32>,\n @location(15) weights2: vec4<f32>,\n) -> Varyings {\n let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);\n let wasSkin =\n skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);\n let world = draw.model * (skin * vec4<f32>(position, 1.0));\n let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The cloth-bound stage: a skinned vertex moved toward its simulation triangle by its painted\n * weight, as shaders/clothBinding.ts moves it in the scene, once with this frame's particles and\n * once with last frame's. **The same arithmetic in the same order**, or the depth this stage writes\n * is not the scene's and the equal test admits none of the garment. The binding's turn is not\n * needed: a motion is where a point went, not which way it faces.\n */\n@group(2) @binding(0) var clothBinding: texture_2d<f32>;\n@group(2) @binding(1) var clothParticles: texture_2d<f32>;\n@group(2) @binding(2) var previousClothParticles: texture_2d<f32>;\n\nfn clothTexel(i: i32, width: i32) -> vec2<i32> {\n return vec2<i32>(i % width, i / width);\n}\n\nfn clothPoint(particles: texture_2d<f32>, indices: vec4<f32>, at: vec4<f32>) -> vec3<f32> {\n let width = i32(textureDimensions(particles, 0).x);\n let a = i32(indices.x);\n let b = i32(indices.y);\n let c = i32(indices.z);\n let pa = textureLoad(particles, clothTexel(a, width), 0).xyz;\n if (a == b && b == c) {\n return pa;\n }\n let pb = textureLoad(particles, clothTexel(b, width), 0).xyz;\n let pc = textureLoad(particles, clothTexel(c, width), 0).xyz;\n let n = normalize(cross(pb - pa, pc - pa));\n return pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + n * at.z;\n}\n\n/* A skinned point now and last frame, carried to the cloth by the vertex's own weight. */\nfn clothVaryings(vertex: u32, world: vec4<f32>, wasWorld: vec4<f32>) -> Varyings {\n let width = i32(textureDimensions(clothBinding, 0).x);\n let indices = textureLoad(clothBinding, clothTexel(i32(vertex) * 2, width), 0);\n let at = textureLoad(clothBinding, clothTexel(i32(vertex) * 2 + 1, width), 0);\n let follow = indices.w;\n let placed = vec4<f32>(mix(world.xyz, clothPoint(clothParticles, indices, at), follow), world.w);\n let wasPlaced = vec4<f32>(\n mix(wasWorld.xyz, clothPoint(previousClothParticles, indices, at), follow),\n wasWorld.w);\n var out: Varyings;\n out.clip = frame.raster * placed;\n out.now = frame.viewProj * placed;\n out.was = frame.previousViewProj * wasPlaced;\n return out;\n}\n\n@vertex\nfn motionClothVert(\n @builtin(vertex_index) vertex: u32,\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n) -> Varyings {\n let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));\n let wasWorld =\n draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));\n return clothVaryings(vertex, world, wasWorld);\n}\n\n@vertex\nfn motionClothEightVert(\n @builtin(vertex_index) vertex: u32,\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n @location(14) joints2: vec4<f32>,\n @location(15) weights2: vec4<f32>,\n) -> Varyings {\n let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);\n let wasSkin =\n skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);\n let world = draw.model * (skin * vec4<f32>(position, 1.0));\n let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));\n return clothVaryings(vertex, world, wasWorld);\n}\n\n/*\n * The rewritten stage: a mesh whose vertices were rewritten this frame reads last frame's rows from\n * a second buffer of the same stride, so each vertex carries both of its positions.\n */\n@vertex\nfn motionDynamicVert(\n @location(0) position: vec3<f32>,\n @location(1) previousPosition: vec3<f32>,\n) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(previousPosition, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The instanced stage: each instance's matrix this frame and last frame, read from the batch's\n * placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with\n * itself; the per-draw matrices are not read.\n */\n@vertex\nfn motionInstancedVert(\n @location(0) position: vec3<f32>,\n @location(2) m0: vec4<f32>,\n @location(3) m1: vec4<f32>,\n @location(4) m2: vec4<f32>,\n @location(5) m3: vec4<f32>,\n @location(6) p0: vec4<f32>,\n @location(7) p1: vec4<f32>,\n @location(8) p2: vec4<f32>,\n @location(9) p3: vec4<f32>,\n) -> Varyings {\n let world = mat4x4<f32>(m0, m1, m2, m3) * vec4<f32>(position, 1.0);\n let wasWorld = mat4x4<f32>(p0, p1, p2, p3) * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n@fragment\nfn motionFrag(in: Varyings) -> @location(0) vec4<f32> {\n /*\n * A surface behind last frame's eye has a w that is not positive, and there is no uv for it. The\n * flag stays set: the disocclusion refuses a depth that is not positive, so the resolve drops the\n * history rather than reading it from wherever a divide by a negative w would have pointed.\n */\n let offset = select(vec2<f32>(0.0), motionUv(in.was) - motionUv(in.now), in.was.w > 0.0);\n return vec4<f32>(offset, in.was.w, 1.0);\n}\n";
44
+ export declare const RECON_MOTION_WGSL = "\nstruct MotionFrame {\n /* The scene's own clip transform, with the generated shaders' y negation already folded in. */\n raster: mat4x4<f32>,\n /* This frame's and last frame's view-projections, unjittered, in the matrices' own convention. */\n viewProj: mat4x4<f32>,\n previousViewProj: mat4x4<f32>,\n}\n\nstruct MotionDraw {\n model: mat4x4<f32>,\n previousModel: mat4x4<f32>,\n}\n\n@group(0) @binding(0) var<uniform> frame: MotionFrame;\n@group(0) @binding(1) var<uniform> draw: MotionDraw;\n\nstruct Varyings {\n @builtin(position) clip: vec4<f32>,\n /* Clip positions rather than uv: an interpolator divides by w for us, so dividing these by their\n own interpolated w in the fragment stage is the perspective-correct answer at every pixel. */\n @location(0) now: vec4<f32>,\n @location(1) was: vec4<f32>,\n}\n\n@vertex\nfn motionVert(@location(0) position: vec3<f32>) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/* Normalised device coordinates to the uv the resolve measures in: v increases upward, as the\n references' images do, and the accessor that reads this target is what flips the texture's rows. */\nfn motionUv(clip: vec4<f32>) -> vec2<f32> {\n return clip.xy / clip.w * 0.5 + vec2<f32>(0.5);\n}\n\n/*\n * The skinned stage: the joints this frame and last frame, read from two palette textures of the\n * frame's ring. The same four weighted products in the same order as the scene's own skinning, so\n * the depth this stage produces is the depth the scene wrote.\n */\n@group(1) @binding(0) var palette: texture_2d<f32>;\n@group(1) @binding(1) var previousPalette: texture_2d<f32>;\n\nfn jointOf(source: texture_2d<f32>, index: i32) -> mat4x4<f32> {\n let x = index * 4;\n return mat4x4<f32>(\n textureLoad(source, vec2<i32>(x, 0), 0),\n textureLoad(source, vec2<i32>(x + 1, 0), 0),\n textureLoad(source, vec2<i32>(x + 2, 0), 0),\n textureLoad(source, vec2<i32>(x + 3, 0), 0));\n}\n\nfn skinOf(source: texture_2d<f32>, joints: vec4<f32>, weights: vec4<f32>) -> mat4x4<f32> {\n return jointOf(source, i32(joints.x)) * weights.x\n + jointOf(source, i32(joints.y)) * weights.y\n + jointOf(source, i32(joints.z)) * weights.z\n + jointOf(source, i32(joints.w)) * weights.w;\n}\n\n@vertex\nfn motionSkinnedVert(\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n) -> Varyings {\n let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));\n let wasWorld =\n draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The same for a rig with eight influences a vertex, the second four at the scene's own locations\n * 14 and 15 and summed after the first, as `skinMatrix()` sums them with `SKIN_EIGHT` on \u2014 or the\n * motion of a face or a shoulder would be the four heaviest joints' alone.\n */\n@vertex\nfn motionSkinnedEightVert(\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n @location(14) joints2: vec4<f32>,\n @location(15) weights2: vec4<f32>,\n) -> Varyings {\n let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);\n let wasSkin =\n skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);\n let world = draw.model * (skin * vec4<f32>(position, 1.0));\n let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The cloth-bound stage: a skinned vertex moved toward its simulation triangle by its painted\n * weight, as shaders/clothBinding.ts moves it in the scene, once with this frame's particles and\n * once with last frame's. **The same arithmetic in the same order**, or the depth this stage writes\n * is not the scene's and the equal test admits none of the garment. The binding's turn is not\n * needed: a motion is where a point went, not which way it faces.\n */\n@group(2) @binding(0) var clothBinding: texture_2d<f32>;\n@group(2) @binding(1) var clothParticles: texture_2d<f32>;\n@group(2) @binding(2) var previousClothParticles: texture_2d<f32>;\n\nfn clothTexel(i: i32, width: i32) -> vec2<i32> {\n return vec2<i32>(i % width, i / width);\n}\n\nfn clothPoint(particles: texture_2d<f32>, indices: vec4<f32>, at: vec4<f32>) -> vec3<f32> {\n let width = i32(textureDimensions(particles, 0).x);\n let a = i32(indices.x);\n let b = i32(indices.y);\n let c = i32(indices.z);\n let pa = textureLoad(particles, clothTexel(a, width), 0).xyz;\n if (a == b && b == c) {\n return pa;\n }\n let pb = textureLoad(particles, clothTexel(b, width), 0).xyz;\n let pc = textureLoad(particles, clothTexel(c, width), 0).xyz;\n let n = normalize(cross(pb - pa, pc - pa));\n return pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + n * at.z;\n}\n\n/* A skinned point now and last frame, carried to the cloth by the vertex's own weight. */\nfn clothVaryings(vertex: u32, world: vec4<f32>, wasWorld: vec4<f32>) -> Varyings {\n let width = i32(textureDimensions(clothBinding, 0).x);\n let indices = textureLoad(clothBinding, clothTexel(i32(vertex) * 2, width), 0);\n let at = textureLoad(clothBinding, clothTexel(i32(vertex) * 2 + 1, width), 0);\n let follow = indices.w;\n let placed = vec4<f32>(mix(world.xyz, clothPoint(clothParticles, indices, at), follow), world.w);\n let wasPlaced = vec4<f32>(\n mix(wasWorld.xyz, clothPoint(previousClothParticles, indices, at), follow),\n wasWorld.w);\n var out: Varyings;\n out.clip = frame.raster * placed;\n out.now = frame.viewProj * placed;\n out.was = frame.previousViewProj * wasPlaced;\n return out;\n}\n\n@vertex\nfn motionClothVert(\n @builtin(vertex_index) vertex: u32,\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n) -> Varyings {\n let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));\n let wasWorld =\n draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));\n return clothVaryings(vertex, world, wasWorld);\n}\n\n@vertex\nfn motionClothEightVert(\n @builtin(vertex_index) vertex: u32,\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n @location(14) joints2: vec4<f32>,\n @location(15) weights2: vec4<f32>,\n) -> Varyings {\n let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);\n let wasSkin =\n skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);\n let world = draw.model * (skin * vec4<f32>(position, 1.0));\n let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));\n return clothVaryings(vertex, world, wasWorld);\n}\n\n/*\n * The rewritten stage: a mesh whose vertices were rewritten this frame reads last frame's rows from\n * a second buffer of the same stride, so each vertex carries both of its positions.\n */\n@vertex\nfn motionDynamicVert(\n @location(0) position: vec3<f32>,\n @location(1) previousPosition: vec3<f32>,\n) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(previousPosition, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/* An instance's placement with its bottom row, which carries its texture cell, rebuilt as the\n [0, 0, 0, 1] an affine placement has: `MeshInstances.uvRegions`. */\nfn placementOf(c0: vec4<f32>, c1: vec4<f32>, c2: vec4<f32>, c3: vec4<f32>) -> mat4x4<f32> {\n return mat4x4<f32>(\n vec4<f32>(c0.xyz, 0.0),\n vec4<f32>(c1.xyz, 0.0),\n vec4<f32>(c2.xyz, 0.0),\n vec4<f32>(c3.xyz, 1.0),\n );\n}\n\n/*\n * The instanced stage: each instance's matrix this frame and last frame, read from the batch's\n * placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with\n * itself; the per-draw matrices are not read.\n */\n@vertex\nfn motionInstancedVert(\n @location(0) position: vec3<f32>,\n @location(2) m0: vec4<f32>,\n @location(3) m1: vec4<f32>,\n @location(4) m2: vec4<f32>,\n @location(5) m3: vec4<f32>,\n @location(6) p0: vec4<f32>,\n @location(7) p1: vec4<f32>,\n @location(8) p2: vec4<f32>,\n @location(9) p3: vec4<f32>,\n) -> Varyings {\n let world = placementOf(m0, m1, m2, m3) * vec4<f32>(position, 1.0);\n let wasWorld = placementOf(p0, p1, p2, p3) * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n@fragment\nfn motionFrag(in: Varyings) -> @location(0) vec4<f32> {\n /*\n * A surface behind last frame's eye has a w that is not positive, and there is no uv for it. The\n * flag stays set: the disocclusion refuses a depth that is not positive, so the resolve drops the\n * history rather than reading it from wherever a divide by a negative w would have pointed.\n */\n let offset = select(vec2<f32>(0.0), motionUv(in.was) - motionUv(in.now), in.was.w > 0.0);\n return vec4<f32>(offset, in.was.w, 1.0);\n}\n";
@@ -243,6 +243,17 @@ fn motionDynamicVert(
243
243
  return out;
244
244
  }
245
245
 
246
+ /* An instance's placement with its bottom row, which carries its texture cell, rebuilt as the
247
+ [0, 0, 0, 1] an affine placement has: \`MeshInstances.uvRegions\`. */
248
+ fn placementOf(c0: vec4<f32>, c1: vec4<f32>, c2: vec4<f32>, c3: vec4<f32>) -> mat4x4<f32> {
249
+ return mat4x4<f32>(
250
+ vec4<f32>(c0.xyz, 0.0),
251
+ vec4<f32>(c1.xyz, 0.0),
252
+ vec4<f32>(c2.xyz, 0.0),
253
+ vec4<f32>(c3.xyz, 1.0),
254
+ );
255
+ }
256
+
246
257
  /*
247
258
  * The instanced stage: each instance's matrix this frame and last frame, read from the batch's
248
259
  * placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with
@@ -260,8 +271,8 @@ fn motionInstancedVert(
260
271
  @location(8) p2: vec4<f32>,
261
272
  @location(9) p3: vec4<f32>,
262
273
  ) -> Varyings {
263
- let world = mat4x4<f32>(m0, m1, m2, m3) * vec4<f32>(position, 1.0);
264
- let wasWorld = mat4x4<f32>(p0, p1, p2, p3) * vec4<f32>(position, 1.0);
274
+ let world = placementOf(m0, m1, m2, m3) * vec4<f32>(position, 1.0);
275
+ let wasWorld = placementOf(p0, p1, p2, p3) * vec4<f32>(position, 1.0);
265
276
  var out: Varyings;
266
277
  out.clip = frame.raster * world;
267
278
  out.now = frame.viewProj * world;