@3driseai/3drise-viewer 0.1.7 → 0.1.8

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 (259) hide show
  1. package/dist/components/Clouds.d.ts.map +1 -1
  2. package/dist/components/Clouds.js +0 -1
  3. package/dist/components/CloudsGenerator.d.ts +32 -0
  4. package/dist/components/CloudsGenerator.d.ts.map +1 -0
  5. package/dist/components/CloudsGenerator.js +58 -0
  6. package/dist/components/EnvironmentManager.d.ts +47 -5
  7. package/dist/components/EnvironmentManager.d.ts.map +1 -1
  8. package/dist/components/EnvironmentManager.js +35 -66
  9. package/dist/components/HdrEnvironment.d.ts +20 -0
  10. package/dist/components/HdrEnvironment.d.ts.map +1 -0
  11. package/dist/components/HdrEnvironment.js +44 -0
  12. package/dist/components/LandscapeBuilder.d.ts +23 -0
  13. package/dist/components/LandscapeBuilder.d.ts.map +1 -0
  14. package/dist/components/LandscapeBuilder.js +25 -0
  15. package/dist/components/LandscapeObject.d.ts +24 -0
  16. package/dist/components/LandscapeObject.d.ts.map +1 -0
  17. package/dist/components/LandscapeObject.js +27 -0
  18. package/dist/components/ObjectNode.d.ts.map +1 -1
  19. package/dist/components/ObjectNode.js +10 -0
  20. package/dist/components/OceanBuilder.d.ts +35 -0
  21. package/dist/components/OceanBuilder.d.ts.map +1 -0
  22. package/dist/components/OceanBuilder.js +53 -0
  23. package/dist/components/SceneBuilder.d.ts.map +1 -1
  24. package/dist/components/SkySystem.d.ts +27 -6
  25. package/dist/components/SkySystem.d.ts.map +1 -1
  26. package/dist/components/SkySystem.js +6 -5
  27. package/dist/components/SunLight.d.ts.map +1 -1
  28. package/dist/components/SunLight.js +4 -1
  29. package/dist/components/TerrainBuilder.d.ts +30 -0
  30. package/dist/components/TerrainBuilder.d.ts.map +1 -0
  31. package/dist/components/TerrainBuilder.js +44 -0
  32. package/dist/components/index.d.ts +17 -1
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +10 -0
  35. package/dist/components/landscape/LandscapeMesh.d.ts +18 -0
  36. package/dist/components/landscape/LandscapeMesh.d.ts.map +1 -0
  37. package/dist/components/landscape/LandscapeMesh.js +278 -0
  38. package/dist/components/landscape/index.d.ts +3 -0
  39. package/dist/components/landscape/index.d.ts.map +1 -0
  40. package/dist/components/landscape/index.js +1 -0
  41. package/dist/components/landscape/types.d.ts +13 -0
  42. package/dist/components/landscape/types.d.ts.map +1 -0
  43. package/dist/components/landscape/types.js +1 -0
  44. package/dist/components/terrain/Canyon.d.ts +23 -0
  45. package/dist/components/terrain/Canyon.d.ts.map +1 -0
  46. package/dist/components/terrain/Canyon.js +29 -0
  47. package/dist/components/terrain/Dunes.d.ts +22 -0
  48. package/dist/components/terrain/Dunes.d.ts.map +1 -0
  49. package/dist/components/terrain/Dunes.js +25 -0
  50. package/dist/components/terrain/Hills.d.ts +17 -0
  51. package/dist/components/terrain/Hills.d.ts.map +1 -0
  52. package/dist/components/terrain/Hills.js +20 -0
  53. package/dist/components/terrain/Mountains.d.ts +19 -0
  54. package/dist/components/terrain/Mountains.d.ts.map +1 -0
  55. package/dist/components/terrain/Mountains.js +22 -0
  56. package/dist/components/terrain/TerrainSurface.d.ts +22 -0
  57. package/dist/components/terrain/TerrainSurface.d.ts.map +1 -0
  58. package/dist/components/terrain/TerrainSurface.js +94 -0
  59. package/dist/components/terrain/index.d.ts +7 -0
  60. package/dist/components/terrain/index.d.ts.map +1 -0
  61. package/dist/components/terrain/index.js +7 -0
  62. package/dist/components/terrain/types.d.ts +17 -0
  63. package/dist/components/terrain/types.d.ts.map +1 -0
  64. package/dist/components/terrain/types.js +1 -0
  65. package/dist/components/volumetricClouds/Cirrus.d.ts +14 -0
  66. package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -0
  67. package/dist/components/volumetricClouds/Cirrus.js +13 -0
  68. package/dist/components/volumetricClouds/Cumulonimbus.d.ts +17 -0
  69. package/dist/components/volumetricClouds/Cumulonimbus.d.ts.map +1 -0
  70. package/dist/components/volumetricClouds/Cumulonimbus.js +20 -0
  71. package/dist/components/volumetricClouds/Cumulus.d.ts +14 -0
  72. package/dist/components/volumetricClouds/Cumulus.d.ts.map +1 -0
  73. package/dist/components/volumetricClouds/Cumulus.js +17 -0
  74. package/dist/components/volumetricClouds/Stratocumulus.d.ts +12 -0
  75. package/dist/components/volumetricClouds/Stratocumulus.d.ts.map +1 -0
  76. package/dist/components/volumetricClouds/Stratocumulus.js +16 -0
  77. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +42 -0
  78. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -0
  79. package/dist/components/volumetricClouds/VolumetricCloudsPass.js +298 -0
  80. package/dist/components/volumetricClouds/index.d.ts +8 -0
  81. package/dist/components/volumetricClouds/index.d.ts.map +1 -0
  82. package/dist/components/volumetricClouds/index.js +7 -0
  83. package/dist/components/volumetricClouds/types.d.ts +17 -0
  84. package/dist/components/volumetricClouds/types.d.ts.map +1 -0
  85. package/dist/components/volumetricClouds/types.js +1 -0
  86. package/dist/components/water/Host.d.ts +12 -0
  87. package/dist/components/water/Host.d.ts.map +1 -0
  88. package/dist/components/water/Host.js +12 -0
  89. package/dist/components/water/Lake.d.ts +18 -0
  90. package/dist/components/water/Lake.d.ts.map +1 -0
  91. package/dist/components/water/Lake.js +21 -0
  92. package/dist/components/water/OceanSurface.d.ts +27 -0
  93. package/dist/components/water/OceanSurface.d.ts.map +1 -0
  94. package/dist/components/water/OceanSurface.js +60 -0
  95. package/dist/components/water/Sea.d.ts +18 -0
  96. package/dist/components/water/Sea.d.ts.map +1 -0
  97. package/dist/components/water/Sea.js +17 -0
  98. package/dist/components/water/Shore.d.ts +16 -0
  99. package/dist/components/water/Shore.d.ts.map +1 -0
  100. package/dist/components/water/Shore.js +15 -0
  101. package/dist/components/water/Storm.d.ts +14 -0
  102. package/dist/components/water/Storm.d.ts.map +1 -0
  103. package/dist/components/water/Storm.js +13 -0
  104. package/dist/components/water/WaterBody.d.ts +31 -0
  105. package/dist/components/water/WaterBody.d.ts.map +1 -0
  106. package/dist/components/water/WaterBody.js +76 -0
  107. package/dist/components/water/index.d.ts +9 -0
  108. package/dist/components/water/index.d.ts.map +1 -0
  109. package/dist/components/water/index.js +11 -0
  110. package/dist/components/water/types.d.ts +20 -0
  111. package/dist/components/water/types.d.ts.map +1 -0
  112. package/dist/components/water/types.js +1 -0
  113. package/dist/constants.d.ts.map +1 -1
  114. package/dist/constants.js +9 -0
  115. package/dist/hooks/index.d.ts +24 -1
  116. package/dist/hooks/index.d.ts.map +1 -1
  117. package/dist/hooks/index.js +12 -0
  118. package/dist/hooks/useClipmap.d.ts +71 -0
  119. package/dist/hooks/useClipmap.d.ts.map +1 -0
  120. package/dist/hooks/useClipmap.js +81 -0
  121. package/dist/hooks/useCloudLive.d.ts +29 -0
  122. package/dist/hooks/useCloudLive.d.ts.map +1 -0
  123. package/dist/hooks/useCloudLive.js +43 -0
  124. package/dist/hooks/useCloudNoise.d.ts +28 -0
  125. package/dist/hooks/useCloudNoise.d.ts.map +1 -0
  126. package/dist/hooks/useCloudNoise.js +128 -0
  127. package/dist/hooks/useCloudSun.d.ts +49 -0
  128. package/dist/hooks/useCloudSun.d.ts.map +1 -0
  129. package/dist/hooks/useCloudSun.js +84 -0
  130. package/dist/hooks/useCubeCamera.d.ts +27 -9
  131. package/dist/hooks/useCubeCamera.d.ts.map +1 -1
  132. package/dist/hooks/useCubeCamera.js +28 -70
  133. package/dist/hooks/useEnvironmentProbe.d.ts +32 -0
  134. package/dist/hooks/useEnvironmentProbe.d.ts.map +1 -0
  135. package/dist/hooks/useEnvironmentProbe.js +35 -0
  136. package/dist/hooks/useFrameRenderer.d.ts +15 -0
  137. package/dist/hooks/useFrameRenderer.d.ts.map +1 -0
  138. package/dist/hooks/useFrameRenderer.js +23 -0
  139. package/dist/hooks/useLandscapeLive.d.ts +29 -0
  140. package/dist/hooks/useLandscapeLive.d.ts.map +1 -0
  141. package/dist/hooks/useLandscapeLive.js +41 -0
  142. package/dist/hooks/useLandscapeMaps.d.ts +32 -0
  143. package/dist/hooks/useLandscapeMaps.d.ts.map +1 -0
  144. package/dist/hooks/useLandscapeMaps.js +138 -0
  145. package/dist/hooks/useOceanLive.d.ts +29 -0
  146. package/dist/hooks/useOceanLive.d.ts.map +1 -0
  147. package/dist/hooks/useOceanLive.js +38 -0
  148. package/dist/hooks/useOceanMaterial.d.ts +39 -0
  149. package/dist/hooks/useOceanMaterial.d.ts.map +1 -0
  150. package/dist/hooks/useOceanMaterial.js +65 -0
  151. package/dist/hooks/useTerrainClipmap.d.ts +12 -0
  152. package/dist/hooks/useTerrainClipmap.d.ts.map +1 -0
  153. package/dist/hooks/useTerrainClipmap.js +10 -0
  154. package/dist/hooks/useTerrainLive.d.ts +34 -0
  155. package/dist/hooks/useTerrainLive.d.ts.map +1 -0
  156. package/dist/hooks/useTerrainLive.js +45 -0
  157. package/dist/index.d.ts +26 -0
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +16 -0
  160. package/dist/presets/landscapePresets.d.ts +60 -0
  161. package/dist/presets/landscapePresets.d.ts.map +1 -0
  162. package/dist/presets/landscapePresets.js +194 -0
  163. package/dist/presets/landscapeTypes.d.ts +195 -0
  164. package/dist/presets/landscapeTypes.d.ts.map +1 -0
  165. package/dist/presets/landscapeTypes.js +315 -0
  166. package/dist/presets/oceanSurfacePresets.d.ts +33 -0
  167. package/dist/presets/oceanSurfacePresets.d.ts.map +1 -0
  168. package/dist/presets/oceanSurfacePresets.js +245 -0
  169. package/dist/presets/terrainPresets.d.ts +28 -0
  170. package/dist/presets/terrainPresets.d.ts.map +1 -0
  171. package/dist/presets/terrainPresets.js +293 -0
  172. package/dist/presets/volumetricCloudPresets.d.ts +38 -0
  173. package/dist/presets/volumetricCloudPresets.d.ts.map +1 -0
  174. package/dist/presets/volumetricCloudPresets.js +480 -0
  175. package/dist/shaders/clouds/CirrusMaterial.d.ts +84 -0
  176. package/dist/shaders/clouds/CirrusMaterial.d.ts.map +1 -0
  177. package/dist/shaders/clouds/CirrusMaterial.js +167 -0
  178. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts +20 -0
  179. package/dist/shaders/clouds/CloudCompositeMaterial.d.ts.map +1 -0
  180. package/dist/shaders/clouds/CloudCompositeMaterial.js +82 -0
  181. package/dist/shaders/clouds/CloudResolveMaterial.d.ts +22 -0
  182. package/dist/shaders/clouds/CloudResolveMaterial.d.ts.map +1 -0
  183. package/dist/shaders/clouds/CloudResolveMaterial.js +77 -0
  184. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +143 -0
  185. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -0
  186. package/dist/shaders/clouds/CloudVolumeMaterial.js +249 -0
  187. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts +21 -0
  188. package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts.map +1 -0
  189. package/dist/shaders/clouds/SkyBackgroundMaterial.js +42 -0
  190. package/dist/shaders/glsl/cloudCore.glsl.d.ts +24 -0
  191. package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -0
  192. package/dist/shaders/glsl/cloudCore.glsl.js +348 -0
  193. package/dist/shaders/glsl/landscapeCore.glsl.d.ts +49 -0
  194. package/dist/shaders/glsl/landscapeCore.glsl.d.ts.map +1 -0
  195. package/dist/shaders/glsl/landscapeCore.glsl.js +518 -0
  196. package/dist/shaders/glsl/noiseBake.glsl.d.ts +30 -0
  197. package/dist/shaders/glsl/noiseBake.glsl.d.ts.map +1 -0
  198. package/dist/shaders/glsl/noiseBake.glsl.js +164 -0
  199. package/dist/shaders/glsl/oceanCore.glsl.d.ts +52 -0
  200. package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -0
  201. package/dist/shaders/glsl/oceanCore.glsl.js +421 -0
  202. package/dist/shaders/glsl/terrainCore.glsl.d.ts +78 -0
  203. package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -0
  204. package/dist/shaders/glsl/terrainCore.glsl.js +625 -0
  205. package/dist/shaders/landscape/LandscapeMaterial.d.ts +73 -0
  206. package/dist/shaders/landscape/LandscapeMaterial.d.ts.map +1 -0
  207. package/dist/shaders/landscape/LandscapeMaterial.js +136 -0
  208. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +57 -0
  209. package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -0
  210. package/dist/shaders/ocean/OceanSurfaceMaterial.js +268 -0
  211. package/dist/shaders/terrain/TerrainMaterial.d.ts +80 -0
  212. package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -0
  213. package/dist/shaders/terrain/TerrainMaterial.js +291 -0
  214. package/dist/types/cloudConfigs.d.ts +244 -0
  215. package/dist/types/cloudConfigs.d.ts.map +1 -0
  216. package/dist/types/cloudConfigs.js +282 -0
  217. package/dist/types/index.d.ts +8 -0
  218. package/dist/types/index.d.ts.map +1 -1
  219. package/dist/types/index.js +4 -1
  220. package/dist/types/landscapeConfigs.d.ts +218 -0
  221. package/dist/types/landscapeConfigs.d.ts.map +1 -0
  222. package/dist/types/landscapeConfigs.js +256 -0
  223. package/dist/types/oceanConfigs.d.ts +290 -0
  224. package/dist/types/oceanConfigs.d.ts.map +1 -0
  225. package/dist/types/oceanConfigs.js +380 -0
  226. package/dist/types/terrainConfigs.d.ts +251 -0
  227. package/dist/types/terrainConfigs.d.ts.map +1 -0
  228. package/dist/types/terrainConfigs.js +292 -0
  229. package/dist/utils/cloudUniforms.d.ts +14 -0
  230. package/dist/utils/cloudUniforms.d.ts.map +1 -0
  231. package/dist/utils/cloudUniforms.js +122 -0
  232. package/dist/utils/frameRenderer.d.ts +33 -0
  233. package/dist/utils/frameRenderer.d.ts.map +1 -0
  234. package/dist/utils/frameRenderer.js +24 -0
  235. package/dist/utils/handleCloudsAnimations.d.ts +16 -0
  236. package/dist/utils/handleCloudsAnimations.d.ts.map +1 -0
  237. package/dist/utils/handleCloudsAnimations.js +64 -0
  238. package/dist/utils/landscapeGeometry.d.ts +61 -0
  239. package/dist/utils/landscapeGeometry.d.ts.map +1 -0
  240. package/dist/utils/landscapeGeometry.js +126 -0
  241. package/dist/utils/landscapeHeight.d.ts +44 -0
  242. package/dist/utils/landscapeHeight.d.ts.map +1 -0
  243. package/dist/utils/landscapeHeight.js +65 -0
  244. package/dist/utils/landscapeTextures.d.ts +94 -0
  245. package/dist/utils/landscapeTextures.d.ts.map +1 -0
  246. package/dist/utils/landscapeTextures.js +290 -0
  247. package/dist/utils/landscapeUniforms.d.ts +20 -0
  248. package/dist/utils/landscapeUniforms.d.ts.map +1 -0
  249. package/dist/utils/landscapeUniforms.js +61 -0
  250. package/dist/utils/oceanUniforms.d.ts +47 -0
  251. package/dist/utils/oceanUniforms.d.ts.map +1 -0
  252. package/dist/utils/oceanUniforms.js +216 -0
  253. package/dist/utils/rtin.d.ts +63 -0
  254. package/dist/utils/rtin.d.ts.map +1 -0
  255. package/dist/utils/rtin.js +202 -0
  256. package/dist/utils/terrainUniforms.d.ts +14 -0
  257. package/dist/utils/terrainUniforms.d.ts.map +1 -0
  258. package/dist/utils/terrainUniforms.js +196 -0
  259. package/package.json +2 -2
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Bake-time noise. Runs once at startup, never per frame.
3
+ *
4
+ * Everything here is *tileable*: the hash wraps its lattice with mod(p, freq) so the
5
+ * volumes repeat seamlessly under RepeatWrapping. Non-tileable noise in a repeating
6
+ * 3D texture produces a visible grid of seams across the sky, which is the single most
7
+ * common giveaway in a volumetric cloud implementation.
8
+ */
9
+ export const bakeCommon = /* glsl */ `
10
+ #define PI 3.14159265359
11
+ #define saturate(x) clamp(x, 0.0, 1.0)
12
+ float remap(float v, float a, float b, float c, float d){
13
+ return c + (v - a) * (d - c) / max(b - a, 1e-6);
14
+ }
15
+
16
+ vec3 hash33t(vec3 p, float f){
17
+ p = mod(p, vec3(f));
18
+ p = fract(p * vec3(0.1031, 0.1030, 0.0973));
19
+ p += dot(p, p.yxz + 19.19);
20
+ return fract((p.xxy + p.yxx) * p.zyx);
21
+ }
22
+ vec2 hash22t(vec2 p, float f){
23
+ p = mod(p, vec2(f));
24
+ vec3 q = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
25
+ q += dot(q, q.yzx + 19.19);
26
+ return fract((q.xx + q.yz) * q.zy);
27
+ }
28
+
29
+ float perlin3(vec3 x, float f){
30
+ vec3 p = x * f;
31
+ vec3 i = floor(p); vec3 t = p - i;
32
+ vec3 u = t*t*t*(t*(t*6.0-15.0)+10.0);
33
+ float n000 = dot(hash33t(i+vec3(0,0,0),f)*2.0-1.0, t-vec3(0,0,0));
34
+ float n100 = dot(hash33t(i+vec3(1,0,0),f)*2.0-1.0, t-vec3(1,0,0));
35
+ float n010 = dot(hash33t(i+vec3(0,1,0),f)*2.0-1.0, t-vec3(0,1,0));
36
+ float n110 = dot(hash33t(i+vec3(1,1,0),f)*2.0-1.0, t-vec3(1,1,0));
37
+ float n001 = dot(hash33t(i+vec3(0,0,1),f)*2.0-1.0, t-vec3(0,0,1));
38
+ float n101 = dot(hash33t(i+vec3(1,0,1),f)*2.0-1.0, t-vec3(1,0,1));
39
+ float n011 = dot(hash33t(i+vec3(0,1,1),f)*2.0-1.0, t-vec3(0,1,1));
40
+ float n111 = dot(hash33t(i+vec3(1,1,1),f)*2.0-1.0, t-vec3(1,1,1));
41
+ float x00 = mix(n000,n100,u.x), x10 = mix(n010,n110,u.x);
42
+ float x01 = mix(n001,n101,u.x), x11 = mix(n011,n111,u.x);
43
+ return mix(mix(x00,x10,u.y), mix(x01,x11,u.y), u.z);
44
+ }
45
+
46
+ float perlin2(vec2 x, float f){
47
+ vec2 p = x * f;
48
+ vec2 i = floor(p); vec2 t = p - i;
49
+ vec2 u = t*t*t*(t*(t*6.0-15.0)+10.0);
50
+ float n00 = dot(hash22t(i+vec2(0,0),f)*2.0-1.0, t-vec2(0,0));
51
+ float n10 = dot(hash22t(i+vec2(1,0),f)*2.0-1.0, t-vec2(1,0));
52
+ float n01 = dot(hash22t(i+vec2(0,1),f)*2.0-1.0, t-vec2(0,1));
53
+ float n11 = dot(hash22t(i+vec2(1,1),f)*2.0-1.0, t-vec2(1,1));
54
+ return mix(mix(n00,n10,u.x), mix(n01,n11,u.x), u.y);
55
+ }
56
+
57
+ float worley3(vec3 x, float f){
58
+ vec3 p = x * f;
59
+ vec3 i = floor(p); vec3 t = p - i;
60
+ float md = 1e9;
61
+ for(int z=-1; z<=1; z++)
62
+ for(int y=-1; y<=1; y++)
63
+ for(int w=-1; w<=1; w++){
64
+ vec3 o = vec3(float(w), float(y), float(z));
65
+ vec3 h = hash33t(i + o, f);
66
+ vec3 d = o + h - t;
67
+ md = min(md, dot(d, d));
68
+ }
69
+ return saturate(sqrt(md));
70
+ }
71
+ `;
72
+ export const bakeVertex = /* glsl */ `
73
+ out vec2 vUv;
74
+ void main(){ vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }
75
+ `;
76
+ /** Declarations every bake fragment shader shares. Must precede atlasUnpack, which reads vUv. */
77
+ const bakeHeader = /* glsl */ `
78
+ in vec2 vUv;
79
+ out vec4 outColor;
80
+ `;
81
+ /** Slice atlas -> volume coordinate. Tiles run left-to-right, top-to-bottom. */
82
+ const atlasUnpack = /* glsl */ `
83
+ uniform vec2 u_atlas;
84
+ uniform float u_tiles;
85
+ uniform float u_res;
86
+ vec3 volCoord(){
87
+ vec2 px = floor(vUv * u_atlas);
88
+ vec2 tile = floor(px / u_res);
89
+ float z = tile.y * u_tiles + tile.x;
90
+ vec2 xy = px - tile * u_res;
91
+ return (vec3(xy, z) + 0.5) / u_res;
92
+ }
93
+ `;
94
+ /**
95
+ * Base shape, 128³ RGBA.
96
+ * R = Perlin-Worley (Schneider): perlin fbm remapped by an inverted-worley fbm, which
97
+ * gives billowy tops without losing perlin's connectedness.
98
+ * G/B/A = inverted worley at 4 / 8 / 16, recombined in the march as the low-frequency
99
+ * fbm that erodes R.
100
+ */
101
+ export const bakeBaseFrag = /* glsl */ `
102
+ ${bakeCommon}
103
+ ${bakeHeader}
104
+ ${atlasUnpack}
105
+ void main(){
106
+ vec3 p = volCoord();
107
+ float pf = perlin3(p, 4.0) * 0.5 + perlin3(p, 8.0) * 0.25
108
+ + perlin3(p, 16.0) * 0.125 + perlin3(p, 32.0) * 0.0625;
109
+ pf = saturate(pf * 1.35 + 0.5);
110
+ float w4 = 1.0 - worley3(p, 4.0);
111
+ float w8 = 1.0 - worley3(p, 8.0);
112
+ float w16 = 1.0 - worley3(p, 16.0);
113
+ float wfbm = saturate(w4 * 0.625 + w8 * 0.25 + w16 * 0.125);
114
+ outColor = vec4(saturate(remap(pf, wfbm - 1.0, 1.0, 0.0, 1.0)), w4, w8, w16);
115
+ }
116
+ `;
117
+ /** Detail erosion, 64³ RGB inverted worley at 4 / 8 / 16. */
118
+ export const bakeDetailFrag = /* glsl */ `
119
+ ${bakeCommon}
120
+ ${bakeHeader}
121
+ ${atlasUnpack}
122
+ void main(){
123
+ vec3 p = volCoord();
124
+ outColor = vec4(1.0 - worley3(p, 4.0), 1.0 - worley3(p, 8.0), 1.0 - worley3(p, 16.0), 1.0);
125
+ }
126
+ `;
127
+ /**
128
+ * Weather map, 512² RGBA. Domain-warped so cell edges are not perlin-round.
129
+ * The warp offsets are themselves tileable, so the map still wraps exactly.
130
+ * R = coverage, low frequency B = cloud type bias
131
+ * G = coverage, high frequency A = precipitation
132
+ */
133
+ export const bakeWeatherFrag = /* glsl */ `
134
+ ${bakeCommon}
135
+ ${bakeHeader}
136
+ void main(){
137
+ vec2 q = vUv;
138
+ vec2 w = vec2(perlin2(q, 3.0), perlin2(q + vec2(0.37, 0.11), 3.0));
139
+ vec2 qw = q + w * 0.11;
140
+ float c1 = perlin2(qw, 3.0) * 0.60 + perlin2(qw, 6.0) * 0.28 + perlin2(qw, 12.0) * 0.12;
141
+ float c2 = perlin2(qw, 7.0) * 0.52 + perlin2(qw, 14.0) * 0.30 + perlin2(qw, 28.0) * 0.18;
142
+ float ty = perlin2(q + vec2(5.1, 2.3), 2.0);
143
+ float pr = perlin2(q + vec2(1.7, 8.2), 5.0) * 0.7 + perlin2(q + vec2(1.7, 8.2), 10.0) * 0.3;
144
+ outColor = vec4(
145
+ saturate(c1 * 1.55 + 0.5),
146
+ saturate(c2 * 1.60 + 0.5),
147
+ saturate(ty * 1.60 + 0.5),
148
+ saturate(pr * 1.60 + 0.5)
149
+ );
150
+ }
151
+ `;
152
+ /** Curl field, 128² RG. Curl of a scalar fbm — divergence free, so it swirls rather than pushes. */
153
+ export const bakeCurlFrag = /* glsl */ `
154
+ ${bakeCommon}
155
+ ${bakeHeader}
156
+ float fld(vec2 q){ return perlin2(q, 6.0) * 0.6 + perlin2(q, 12.0) * 0.3 + perlin2(q, 24.0) * 0.15; }
157
+ void main(){
158
+ float e = 1.0 / 128.0;
159
+ float dx = fld(vUv + vec2(e, 0.0)) - fld(vUv - vec2(e, 0.0));
160
+ float dy = fld(vUv + vec2(0.0, e)) - fld(vUv - vec2(0.0, e));
161
+ vec2 c = clamp(vec2(dy, -dx) / (2.0 * e) / 26.0, -1.0, 1.0);
162
+ outColor = vec4(c * 0.5 + 0.5, 0.0, 1.0);
163
+ }
164
+ `;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The ocean surface model, in GLSL.
3
+ *
4
+ * Split into chunks the way `cloudCore` and `terrainCore` are, so the vertex and
5
+ * fragment stages take only what they use and the FFT phase can replace exactly
6
+ * one of them.
7
+ *
8
+ * oceanUniforms the wave table and every config value
9
+ * oceanWaves the height field: displacement, analytic frame, Jacobian
10
+ * oceanShade foam, detail normal, lighting, aerial perspective
11
+ * oceanFull all three
12
+ *
13
+ * ## Why the wave table is a uniform array and not computed here
14
+ *
15
+ * The obvious shape is to derive each component's wavelength and amplitude from
16
+ * the Pierson-Moskowitz spectrum inside the loop. That is an `exp` and two `pow`
17
+ * per component per vertex, and the spectrum does not change between vertices —
18
+ * it changes when somebody moves the wind slider. So the fit runs once on the
19
+ * CPU in `oceanUniforms.ts` and uploads a table; the shader does nothing but sum
20
+ * sines. It is also the shape the FFT phase wants: a spectrum baked once,
21
+ * evaluated per vertex.
22
+ *
23
+ * ## Why the normal comes from the vertex stage
24
+ *
25
+ * Terrain re-evaluates its height field per fragment, because a clipmap triangle
26
+ * is a poor approximation of a mountain and shading off the mesh normal draws
27
+ * every splat threshold along a triangle edge. Water is the opposite case: every
28
+ * component shorter than the cell has already been faded out by the LOD rule
29
+ * below, so the mesh IS the wave field to within a cell, and an interpolated
30
+ * normal is the right answer. Twenty sines per pixel would buy nothing. What the
31
+ * fragment stage adds is detail *below* the mesh — which is exactly the energy
32
+ * the LOD fade removed.
33
+ *
34
+ * ## The uniform-name trap
35
+ *
36
+ * When terrain interaction is on, `terrainField` is included beside this, and it
37
+ * brings `u_seaLevel` and `u_elevation` with it. Those are TERRAIN'S. The water's
38
+ * own level is `u_waterLevel`. Read `u_seaLevel` in this file and you will get
39
+ * the height the ground was built around, which is usually 0 and therefore looks
40
+ * correct until somebody moves it.
41
+ */
42
+ /** Hard cap on the wave table. The loop is bounded by it because GLSL ES 1.00
43
+ * requires a constant bound; `u_waveCount` breaks out early. */
44
+ export declare const MAX_WAVES = 32;
45
+ export declare const oceanUniforms = "\n#define MAX_WAVES 32\n\n// Per component: (dirX, dirZ, amplitude, wavenumber k).\nuniform vec4 u_waveA[MAX_WAVES];\n// Per component: (angular frequency w, phase, steepness Q, wavelength).\nuniform vec4 u_waveB[MAX_WAVES];\nuniform float u_waveCount;\n\nuniform float u_time;\nuniform float u_waterLevel;\nuniform float u_choppiness;\nuniform float u_amplitudeScale;\n\n// Half-extent of a placed body, and the width of its rim taper. Both zero on the\n// world host, which is what switches the taper off - a sea has no rim.\nuniform vec2 u_bodyHalf;\nuniform float u_edgeFade;\n\nuniform vec3 u_deepColor;\nuniform vec3 u_shallowColor;\nuniform vec3 u_foamColor;\nuniform vec3 u_sssColor;\nuniform float u_absorption;\nuniform float u_clarity;\nuniform float u_roughness;\nuniform float u_specular;\nuniform float u_sssStrength;\nuniform float u_detailStrength;\nuniform float u_detailScale; // 1 / detailSize\nuniform float u_detailFade; // 1 / detailFadeDistance\n// World size of one pixel at one unit of distance: 2*tan(fov/2) / viewportHeight.\n// What turns a distance into a screen-space footprint, which is the only thing\n// that decides whether a ripple is a shape or a shimmer.\nuniform float u_pixelScale;\nuniform float u_foamAmount;\nuniform float u_foamDecay;\nuniform float u_foamScale; // 1 / foamSize\nuniform float u_ambientScale;\nuniform float u_fogDensity;\nuniform float u_exposure;\nuniform float u_curvature;\nuniform float u_receiveShadows;\n\n// Filled by applySunUniforms and applyTerrainAmbient, exactly as on the terrain\n// material - one atmosphere for the sky, the clouds, the ground and the water.\nuniform vec3 u_skyAmbient;\nuniform vec3 u_groundAmbient;\n";
46
+ export declare const oceanWaves = "\n/**\n * One trochoidal component, accumulated.\n *\n * Gerstner displaces along the direction of travel as well as vertically:\n *\n * x' = x + d * Q * A * sin(theta)\n * y = A * cos(theta)\n * theta = k * dot(d, x) + w * t + phase\n *\n * Q is what peaks the crests and flattens the troughs. It also compresses the\n * horizontal map near a crest, and where that compression turns the map\n * degenerate the wave is breaking - which is where the foam is. That falls out\n * of the same derivatives, for free, so foam here is measured rather than faked.\n *\n * Everything is differentiated in closed form. A central difference would need\n * four more evaluations of the whole sum for the normal alone; this is the same\n * decision, and the same 13x, that the terrain height field made.\n */\nvoid oceanWaveSum(\n vec2 p, float cellLod, float amp,\n out vec3 disp, // displacement from the flat plane, world units\n out vec3 nrm, // analytic surface normal\n out float jacobian, // det of the horizontal map. < 1 compresses, < 0 folds\n out float relHeight // vertical displacement in standard deviations of the surface\n){\n disp = vec3(0.0);\n jacobian = 0.0;\n relHeight = 0.0;\n\n // Horizontal derivatives of the horizontal displacement, and of the height.\n float dXdx = 0.0, dXdz = 0.0, dZdx = 0.0, dZdz = 0.0;\n float dYdx = 0.0, dYdz = 0.0;\n float totalSq = 0.0;\n\n for (int i = 0; i < MAX_WAVES; i++) {\n if (float(i) >= u_waveCount) break;\n\n vec4 a = u_waveA[i];\n vec4 b = u_waveB[i];\n vec2 d = a.xy;\n float A = a.z * amp;\n float k = a.w;\n float w = b.x;\n float phase = b.y;\n float Q = b.z;\n float wl = b.w;\n\n // Per-ring wave LOD. A component of wavelength L cannot be represented on a\n // cell coarser than about L/4 - sampled there it aliases into crawling\n // noise, which is what a naive clipmap ocean looks like at the horizon. So\n // it fades out as the cell approaches its wavelength.\n //\n // cellLod is aCell * (1 + aMorph), the same morphing LOD the terrain uses,\n // which is what makes the two sides of a ring seam agree: the outer edge of\n // ring l evaluates at exactly ring l+1's cell size, so both sides fade the\n // same components by the same amount and the surface is continuous across\n // the boundary.\n float lod = 1.0 - smoothstep(wl * 0.25, wl * 0.75, cellLod);\n A *= lod;\n\n float theta = k * dot(d, p) + w * u_time + phase;\n float S = sin(theta);\n float C = cos(theta);\n\n float QA = Q * A;\n\n disp.x += d.x * QA * S;\n disp.z += d.y * QA * S;\n disp.y += A * C;\n totalSq += A * A;\n\n float kQA = k * QA;\n dXdx += d.x * d.x * kQA * C;\n dXdz += d.x * d.y * kQA * C;\n dZdx += d.y * d.x * kQA * C;\n dZdz += d.y * d.y * kQA * C;\n\n float kA = k * A;\n dYdx -= d.x * kA * S;\n dYdz -= d.y * kA * S;\n }\n\n // Tangent frame of the displaced surface. Taking the cross product rather than\n // the usual closed-form shortcut costs three multiplies and stays correct when\n // choppiness is high, where the shortcut - which assumes the horizontal map is\n // near-identity - visibly tilts the crests the wrong way.\n vec3 tx = vec3(1.0 + dXdx, dYdx, dZdx);\n vec3 tz = vec3(dXdz, dYdz, 1.0 + dZdz);\n nrm = normalize(cross(tz, tx));\n\n jacobian = (1.0 + dXdx) * (1.0 + dZdz) - dXdz * dZdx;\n\n // Height in standard deviations of the surface, NOT as a fraction of the\n // summed amplitudes. Dividing by the sum is the obvious thing and it is wrong\n // by a factor of sqrt(2N): random-phase components almost never align, so the\n // surface only ever reaches a fraction of that sum, and everything keyed on\n // relative height - the foam's crest gate, the subsurface term - was being\n // multiplied by ~0.15 and silently doing nothing. A storm with whitecaps\n // turned up had no foam at all, which is how this was found.\n //\n // sigma is the RMS surface elevation, so this is roughly -3..3 with the usual\n // excursion at +-1, and a threshold expressed in it means what it looks like.\n relHeight = disp.y / max(sqrt(totalSq * 0.5), 1e-4);\n}\n\n/** Rim taper for a placed body: displacement to zero at the edge, so the\n * boundary is still water rather than a sawn-off cliff of it. Returns 1 on the\n * world host, where u_bodyHalf is zero. */\nfloat oceanEdgeMask(vec2 local){\n if (u_bodyHalf.x <= 0.0) return 1.0;\n vec2 t = abs(local) / max(u_bodyHalf, vec2(1e-3));\n float e = max(t.x, t.y);\n float band = max(u_edgeFade, 1e-3);\n return 1.0 - smoothstep(1.0 - band, 1.0, e);\n}\n";
47
+ export declare const oceanShade = "\n/** Arithmetic hash. Never the fract(sin(dot())) one: four hashes per octave of\n * two sines each was most of the terrain frame budget before it was replaced,\n * and every noise tutorial online still teaches the expensive version. */\nvec2 oHash(vec2 p){\n vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy) * 2.0 - 1.0;\n}\n\n/** Gradient noise with its derivative, so a detail normal costs one evaluation\n * instead of three. */\nvec3 oNoised(vec2 x){\n vec2 i = floor(x);\n vec2 f = fract(x);\n vec2 u = f * f * (3.0 - 2.0 * f);\n vec2 du = 6.0 * f * (1.0 - f);\n\n vec2 ga = oHash(i + vec2(0.0, 0.0));\n vec2 gb = oHash(i + vec2(1.0, 0.0));\n vec2 gc = oHash(i + vec2(0.0, 1.0));\n vec2 gd = oHash(i + vec2(1.0, 1.0));\n\n float va = dot(ga, f - vec2(0.0, 0.0));\n float vb = dot(gb, f - vec2(1.0, 0.0));\n float vc = dot(gc, f - vec2(0.0, 1.0));\n float vd = dot(gd, f - vec2(1.0, 1.0));\n\n float v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * (va - vb - vc + vd);\n vec2 g = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)\n + du * (u.yx * (va - vb - vc + vd) + vec2(vb, vc) - va);\n return vec3(v, g);\n}\n\n/**\n * Ripple below the mesh.\n *\n * This is not decoration: the per-ring LOD in oceanWaveSum deletes every\n * component shorter than the cell, and near the camera that is most of the\n * high-frequency energy in the sea. Putting it back as a normal perturbation is\n * cheaper than carrying it in geometry and, past a few metres, indistinguishable\n * - a ripple two pixels across is a normal, not a shape.\n *\n * Two octaves drifting in opposite directions, because one drifting field reads\n * as a texture being dragged across the water.\n */\nfloat oceanDetailFade(float dist){\n // Two fades, and the second is the one that matters. Distance alone is not\n // what makes a ripple alias - a screen-space footprint is. A two-metre ripple\n // is a shape at fifty metres and sub-pixel noise at two thousand, and which of\n // those it is depends on the field of view and the resolution, not on the\n // distance. Fading on the footprint means the same water looks right in a\n // wide shot and a telephoto one.\n float footprint = dist * u_pixelScale;\n float size = 1.0 / max(u_detailScale, 1e-4);\n float screen = 1.0 - smoothstep(size * 0.35, size * 1.4, footprint);\n return exp(-dist * u_detailFade) * screen;\n}\n\nvec3 oceanDetailNormal(vec3 n, vec3 wp, float fade){\n float amt = u_detailStrength * fade;\n if (amt <= 0.001) return n;\n\n vec2 p = wp.xz * u_detailScale;\n vec2 drift = vec2(u_time * 0.06, u_time * 0.043);\n\n vec3 a = oNoised(p + drift);\n vec3 b = oNoised(p * 2.17 - drift * 1.6);\n vec2 g = a.yz + b.yz * 0.5;\n\n // Perturb in the surface plane, so a steep wave face keeps its own orientation\n // and only gains texture.\n vec3 t = normalize(cross(vec3(0.0, 1.0, 0.0), n) + vec3(1e-5, 0.0, 0.0));\n vec3 bt = cross(n, t);\n // 0.12, not 0.4. The gradient of the noise is order 1, so the constant IS the\n // maximum slope the ripple adds: at 0.4 the default strength tilts the normal\n // by twelve degrees, and at grazing angles - which is most of an ocean - a\n // twelve degree tilt swings the Fresnel term between reflecting the sky and\n // not. The whole surface then boils with white speckle, which is exactly what\n // it did. Sub-metre ripple on open water is a few degrees of slope.\n return normalize(n - (t * g.x + bt * g.y) * amt * 0.12);\n}\n\n/**\n * Whitecaps, from the Jacobian of the horizontal map.\n *\n * Where the determinant falls toward zero the surface is folding into itself,\n * which is physically what a breaking crest is. It is the same quantity an FFT\n * ocean reads off its displacement cascade, and a Gerstner sum hands it over for\n * nothing, so foam is measured here rather than paint over the crests.\n *\n * u_foamDecay is a stand-in for a real accumulation buffer: it widens the\n * threshold on the trailing side, so foam persists behind the crest instead of\n * flickering on and off with it. The honest version writes into a persistent\n * target and advects it - deferred with the FFT phase.\n */\nfloat oceanFoam(float jacobian, float relHeight, vec3 wp){\n float compress = saturate(1.0 - jacobian);\n\n // Thresholds are calibrated against the compression the wave table actually\n // produces, which is a much narrower range than it looks: at the defaults the\n // Jacobian sits just above zero at the sharpest crest and near 1 everywhere\n // else. A threshold picked by eye either foams the whole sea or none of it.\n float thr = mix(0.85, 0.15, saturate(u_foamAmount * 0.6));\n float sharp = smoothstep(thr, thr + 0.15, compress);\n float trail = smoothstep(thr * 0.55, thr + 0.4, compress);\n float f = mix(sharp, max(sharp, trail), saturate(u_foamDecay));\n\n // Foam belongs on and just behind the crest, never in the trough.\n f *= smoothstep(-0.15, 0.45, relHeight);\n\n // Break it up, or it reads as a decal following the wave.\n vec2 q = wp.xz * u_foamScale;\n float n = oNoised(q + vec2(u_time * 0.05, -u_time * 0.03)).x * 0.5 + 0.5;\n n = mix(n, 1.0, 0.35);\n return saturate(f * n * u_foamAmount);\n}\n\n/**\n * The water shading.\n *\n * Reflection is the whole story above a few degrees of grazing - most of what\n * you see looking at the sea is the sky - so the Fresnel split, the sky radiance\n * and the aerial perspective all have to come from the same atmosphere the sky\n * itself is drawn with, or the water is obviously pasted onto the scene. That is\n * skyRadiance from cloudCore, driven by applySunUniforms off the same\n * useCloudSun state as the clouds and the terrain.\n */\nvec3 oceanLight(\n vec3 wp, vec3 n, vec3 viewDir, // viewDir points FROM the camera TO the surface\n float foam, float relHeight, float shadow, float depthFactor,\n float roughnessBoost, // specular antialiasing, see the caller\n vec3 reflectionColor\n){\n vec3 V = -viewDir;\n vec3 L = normalize(u_sunDir);\n float NdotL = max(dot(n, L), 0.0);\n vec3 sun = u_sunColor * u_sunIntensity * shadow;\n\n // Reflection. Bending the reflected ray back above the horizon is a cheat, and\n // the alternative is worse: at grazing angles a wave face routinely reflects\n // just below the horizon, and sampling the sky's ground term there puts a band\n // of dirt across the brightest part of the water.\n vec3 R = reflect(viewDir, n);\n R.y = max(R.y, 0.015);\n vec3 sky = length(reflectionColor) > 0.0 ? reflectionColor : skyRadiance(R);\n\n // Prefilter the reflection by the same roughness the specular lobe uses.\n // Without this the specular is filtered and the reflection is not, and on\n // water that is the wrong half: reflection is most of what you see, and a\n // mirror sampled through a noisy normal aliases exactly as hard as a highlight\n // does. u_skyAmbient is already the hemispheric average of this sky, so it is\n // the infinitely-rough end of the same lobe and costs nothing to mix toward.\n float rough = clamp(u_roughness + roughnessBoost, 0.01, 1.0);\n sky = mix(sky, u_skyAmbient * 0.6, saturate(rough * 2.2));\n\n float fres = 0.02 + 0.98 * pow(1.0 - saturate(dot(V, n)), 5.0);\n\n // Body colour. depthFactor is 1 in open water and ramps to 0 as the bottom\n // comes up; until the refraction grab exists it is 1 everywhere.\n vec3 body = mix(u_shallowColor, u_deepColor, saturate(depthFactor));\n body = mix(body, u_shallowColor, u_clarity * 0.35);\n vec3 bodyLit = body * (u_skyAmbient * u_ambientScale * 0.22 + sun * (0.25 + 0.55 * NdotL));\n\n // Subsurface. A wave lit from behind glows through its own crest, and the\n // effect is strongest where the surface is steep and thin - so it keys off the\n // wave's own height rather than off thickness we do not have.\n float back = pow(saturate(dot(V, -L) * 0.5 + 0.5), 3.0);\n float thin = saturate(relHeight * 1.4);\n vec3 sss = u_sssColor * u_sssStrength * back * thin * u_sunColor * u_sunIntensity * (0.35 + 0.65 * shadow);\n\n // GGX. The glitter path from the horizon to the camera is most of what says\n // \"water\" at distance, and it is a specular lobe on a normal that is moving -\n // which is why the detail normal matters as much as the mesh does.\n // Water is nearly smooth, which is exactly what makes it alias: a specular\n // lobe this tight flips between nothing and a blown-out highlight across one\n // pixel, and a sea of them boils. The fix is not a softer sea - it is to widen\n // the lobe by however much the normal varies inside the pixel, so the energy\n // is kept and the flicker is not. roughnessBoost carries that in, and the\n // reflection above is filtered by the same number.\n float a = max(rough * rough, 2e-4);\n vec3 h = normalize(L + V);\n float ndh = max(dot(n, h), 0.0);\n float denom = ndh * ndh * (a * a - 1.0) + 1.0;\n float ggx = (a * a) / (PI * denom * denom);\n vec3 spec = sun * ggx * u_specular * fres;\n\n vec3 col = mix(bodyLit + sss, sky, fres) + spec;\n\n // Foam is a rough dielectric, so it takes the sky and the sun diffusely and\n // kills the reflection under it.\n vec3 foamLit = u_foamColor * (u_skyAmbient * u_ambientScale * 0.5 + sun * (0.3 + 0.7 * NdotL));\n col = mix(col, foamLit, saturate(foam));\n\n return col;\n}\n\n/** Fade into the sky the water is seen through. Same term as the terrain's,\n * and it needs to be stronger here: an ocean runs to the horizon in every\n * direction, so it is the only thing stopping the far field reading as a\n * painted plane. */\nvec3 oceanAerial(vec3 col, vec3 rd, float dist){\n float f = 1.0 - exp(-u_fogDensity * dist);\n if (f < 0.002) return col;\n return mix(col, skyRadiance(rd), f);\n}\n";
48
+ /** Height field only — what a vertex stage needs. */
49
+ export declare const oceanField: string;
50
+ /** The whole model. */
51
+ export declare const oceanFull: string;
52
+ //# sourceMappingURL=oceanCore.glsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oceanCore.glsl.d.ts","sourceRoot":"","sources":["../../../src/shaders/glsl/oceanCore.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;iEACiE;AACjE,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B,eAAO,MAAM,aAAa,+rDAgDzB,CAAC;AAEF,eAAO,MAAM,UAAU,gjJAoHtB,CAAC;AAEF,eAAO,MAAM,UAAU,woTA8MtB,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,UAAU,QAAyC,CAAC;AAEjE,uBAAuB;AACvB,eAAO,MAAM,SAAS,QAAqD,CAAC"}
@@ -0,0 +1,421 @@
1
+ /**
2
+ * The ocean surface model, in GLSL.
3
+ *
4
+ * Split into chunks the way `cloudCore` and `terrainCore` are, so the vertex and
5
+ * fragment stages take only what they use and the FFT phase can replace exactly
6
+ * one of them.
7
+ *
8
+ * oceanUniforms the wave table and every config value
9
+ * oceanWaves the height field: displacement, analytic frame, Jacobian
10
+ * oceanShade foam, detail normal, lighting, aerial perspective
11
+ * oceanFull all three
12
+ *
13
+ * ## Why the wave table is a uniform array and not computed here
14
+ *
15
+ * The obvious shape is to derive each component's wavelength and amplitude from
16
+ * the Pierson-Moskowitz spectrum inside the loop. That is an `exp` and two `pow`
17
+ * per component per vertex, and the spectrum does not change between vertices —
18
+ * it changes when somebody moves the wind slider. So the fit runs once on the
19
+ * CPU in `oceanUniforms.ts` and uploads a table; the shader does nothing but sum
20
+ * sines. It is also the shape the FFT phase wants: a spectrum baked once,
21
+ * evaluated per vertex.
22
+ *
23
+ * ## Why the normal comes from the vertex stage
24
+ *
25
+ * Terrain re-evaluates its height field per fragment, because a clipmap triangle
26
+ * is a poor approximation of a mountain and shading off the mesh normal draws
27
+ * every splat threshold along a triangle edge. Water is the opposite case: every
28
+ * component shorter than the cell has already been faded out by the LOD rule
29
+ * below, so the mesh IS the wave field to within a cell, and an interpolated
30
+ * normal is the right answer. Twenty sines per pixel would buy nothing. What the
31
+ * fragment stage adds is detail *below* the mesh — which is exactly the energy
32
+ * the LOD fade removed.
33
+ *
34
+ * ## The uniform-name trap
35
+ *
36
+ * When terrain interaction is on, `terrainField` is included beside this, and it
37
+ * brings `u_seaLevel` and `u_elevation` with it. Those are TERRAIN'S. The water's
38
+ * own level is `u_waterLevel`. Read `u_seaLevel` in this file and you will get
39
+ * the height the ground was built around, which is usually 0 and therefore looks
40
+ * correct until somebody moves it.
41
+ */
42
+ /** Hard cap on the wave table. The loop is bounded by it because GLSL ES 1.00
43
+ * requires a constant bound; `u_waveCount` breaks out early. */
44
+ export const MAX_WAVES = 32;
45
+ export const oceanUniforms = /* glsl */ `
46
+ #define MAX_WAVES 32
47
+
48
+ // Per component: (dirX, dirZ, amplitude, wavenumber k).
49
+ uniform vec4 u_waveA[MAX_WAVES];
50
+ // Per component: (angular frequency w, phase, steepness Q, wavelength).
51
+ uniform vec4 u_waveB[MAX_WAVES];
52
+ uniform float u_waveCount;
53
+
54
+ uniform float u_time;
55
+ uniform float u_waterLevel;
56
+ uniform float u_choppiness;
57
+ uniform float u_amplitudeScale;
58
+
59
+ // Half-extent of a placed body, and the width of its rim taper. Both zero on the
60
+ // world host, which is what switches the taper off - a sea has no rim.
61
+ uniform vec2 u_bodyHalf;
62
+ uniform float u_edgeFade;
63
+
64
+ uniform vec3 u_deepColor;
65
+ uniform vec3 u_shallowColor;
66
+ uniform vec3 u_foamColor;
67
+ uniform vec3 u_sssColor;
68
+ uniform float u_absorption;
69
+ uniform float u_clarity;
70
+ uniform float u_roughness;
71
+ uniform float u_specular;
72
+ uniform float u_sssStrength;
73
+ uniform float u_detailStrength;
74
+ uniform float u_detailScale; // 1 / detailSize
75
+ uniform float u_detailFade; // 1 / detailFadeDistance
76
+ // World size of one pixel at one unit of distance: 2*tan(fov/2) / viewportHeight.
77
+ // What turns a distance into a screen-space footprint, which is the only thing
78
+ // that decides whether a ripple is a shape or a shimmer.
79
+ uniform float u_pixelScale;
80
+ uniform float u_foamAmount;
81
+ uniform float u_foamDecay;
82
+ uniform float u_foamScale; // 1 / foamSize
83
+ uniform float u_ambientScale;
84
+ uniform float u_fogDensity;
85
+ uniform float u_exposure;
86
+ uniform float u_curvature;
87
+ uniform float u_receiveShadows;
88
+
89
+ // Filled by applySunUniforms and applyTerrainAmbient, exactly as on the terrain
90
+ // material - one atmosphere for the sky, the clouds, the ground and the water.
91
+ uniform vec3 u_skyAmbient;
92
+ uniform vec3 u_groundAmbient;
93
+ `;
94
+ export const oceanWaves = /* glsl */ `
95
+ /**
96
+ * One trochoidal component, accumulated.
97
+ *
98
+ * Gerstner displaces along the direction of travel as well as vertically:
99
+ *
100
+ * x' = x + d * Q * A * sin(theta)
101
+ * y = A * cos(theta)
102
+ * theta = k * dot(d, x) + w * t + phase
103
+ *
104
+ * Q is what peaks the crests and flattens the troughs. It also compresses the
105
+ * horizontal map near a crest, and where that compression turns the map
106
+ * degenerate the wave is breaking - which is where the foam is. That falls out
107
+ * of the same derivatives, for free, so foam here is measured rather than faked.
108
+ *
109
+ * Everything is differentiated in closed form. A central difference would need
110
+ * four more evaluations of the whole sum for the normal alone; this is the same
111
+ * decision, and the same 13x, that the terrain height field made.
112
+ */
113
+ void oceanWaveSum(
114
+ vec2 p, float cellLod, float amp,
115
+ out vec3 disp, // displacement from the flat plane, world units
116
+ out vec3 nrm, // analytic surface normal
117
+ out float jacobian, // det of the horizontal map. < 1 compresses, < 0 folds
118
+ out float relHeight // vertical displacement in standard deviations of the surface
119
+ ){
120
+ disp = vec3(0.0);
121
+ jacobian = 0.0;
122
+ relHeight = 0.0;
123
+
124
+ // Horizontal derivatives of the horizontal displacement, and of the height.
125
+ float dXdx = 0.0, dXdz = 0.0, dZdx = 0.0, dZdz = 0.0;
126
+ float dYdx = 0.0, dYdz = 0.0;
127
+ float totalSq = 0.0;
128
+
129
+ for (int i = 0; i < MAX_WAVES; i++) {
130
+ if (float(i) >= u_waveCount) break;
131
+
132
+ vec4 a = u_waveA[i];
133
+ vec4 b = u_waveB[i];
134
+ vec2 d = a.xy;
135
+ float A = a.z * amp;
136
+ float k = a.w;
137
+ float w = b.x;
138
+ float phase = b.y;
139
+ float Q = b.z;
140
+ float wl = b.w;
141
+
142
+ // Per-ring wave LOD. A component of wavelength L cannot be represented on a
143
+ // cell coarser than about L/4 - sampled there it aliases into crawling
144
+ // noise, which is what a naive clipmap ocean looks like at the horizon. So
145
+ // it fades out as the cell approaches its wavelength.
146
+ //
147
+ // cellLod is aCell * (1 + aMorph), the same morphing LOD the terrain uses,
148
+ // which is what makes the two sides of a ring seam agree: the outer edge of
149
+ // ring l evaluates at exactly ring l+1's cell size, so both sides fade the
150
+ // same components by the same amount and the surface is continuous across
151
+ // the boundary.
152
+ float lod = 1.0 - smoothstep(wl * 0.25, wl * 0.75, cellLod);
153
+ A *= lod;
154
+
155
+ float theta = k * dot(d, p) + w * u_time + phase;
156
+ float S = sin(theta);
157
+ float C = cos(theta);
158
+
159
+ float QA = Q * A;
160
+
161
+ disp.x += d.x * QA * S;
162
+ disp.z += d.y * QA * S;
163
+ disp.y += A * C;
164
+ totalSq += A * A;
165
+
166
+ float kQA = k * QA;
167
+ dXdx += d.x * d.x * kQA * C;
168
+ dXdz += d.x * d.y * kQA * C;
169
+ dZdx += d.y * d.x * kQA * C;
170
+ dZdz += d.y * d.y * kQA * C;
171
+
172
+ float kA = k * A;
173
+ dYdx -= d.x * kA * S;
174
+ dYdz -= d.y * kA * S;
175
+ }
176
+
177
+ // Tangent frame of the displaced surface. Taking the cross product rather than
178
+ // the usual closed-form shortcut costs three multiplies and stays correct when
179
+ // choppiness is high, where the shortcut - which assumes the horizontal map is
180
+ // near-identity - visibly tilts the crests the wrong way.
181
+ vec3 tx = vec3(1.0 + dXdx, dYdx, dZdx);
182
+ vec3 tz = vec3(dXdz, dYdz, 1.0 + dZdz);
183
+ nrm = normalize(cross(tz, tx));
184
+
185
+ jacobian = (1.0 + dXdx) * (1.0 + dZdz) - dXdz * dZdx;
186
+
187
+ // Height in standard deviations of the surface, NOT as a fraction of the
188
+ // summed amplitudes. Dividing by the sum is the obvious thing and it is wrong
189
+ // by a factor of sqrt(2N): random-phase components almost never align, so the
190
+ // surface only ever reaches a fraction of that sum, and everything keyed on
191
+ // relative height - the foam's crest gate, the subsurface term - was being
192
+ // multiplied by ~0.15 and silently doing nothing. A storm with whitecaps
193
+ // turned up had no foam at all, which is how this was found.
194
+ //
195
+ // sigma is the RMS surface elevation, so this is roughly -3..3 with the usual
196
+ // excursion at +-1, and a threshold expressed in it means what it looks like.
197
+ relHeight = disp.y / max(sqrt(totalSq * 0.5), 1e-4);
198
+ }
199
+
200
+ /** Rim taper for a placed body: displacement to zero at the edge, so the
201
+ * boundary is still water rather than a sawn-off cliff of it. Returns 1 on the
202
+ * world host, where u_bodyHalf is zero. */
203
+ float oceanEdgeMask(vec2 local){
204
+ if (u_bodyHalf.x <= 0.0) return 1.0;
205
+ vec2 t = abs(local) / max(u_bodyHalf, vec2(1e-3));
206
+ float e = max(t.x, t.y);
207
+ float band = max(u_edgeFade, 1e-3);
208
+ return 1.0 - smoothstep(1.0 - band, 1.0, e);
209
+ }
210
+ `;
211
+ export const oceanShade = /* glsl */ `
212
+ /** Arithmetic hash. Never the fract(sin(dot())) one: four hashes per octave of
213
+ * two sines each was most of the terrain frame budget before it was replaced,
214
+ * and every noise tutorial online still teaches the expensive version. */
215
+ vec2 oHash(vec2 p){
216
+ vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
217
+ p3 += dot(p3, p3.yzx + 33.33);
218
+ return fract((p3.xx + p3.yz) * p3.zy) * 2.0 - 1.0;
219
+ }
220
+
221
+ /** Gradient noise with its derivative, so a detail normal costs one evaluation
222
+ * instead of three. */
223
+ vec3 oNoised(vec2 x){
224
+ vec2 i = floor(x);
225
+ vec2 f = fract(x);
226
+ vec2 u = f * f * (3.0 - 2.0 * f);
227
+ vec2 du = 6.0 * f * (1.0 - f);
228
+
229
+ vec2 ga = oHash(i + vec2(0.0, 0.0));
230
+ vec2 gb = oHash(i + vec2(1.0, 0.0));
231
+ vec2 gc = oHash(i + vec2(0.0, 1.0));
232
+ vec2 gd = oHash(i + vec2(1.0, 1.0));
233
+
234
+ float va = dot(ga, f - vec2(0.0, 0.0));
235
+ float vb = dot(gb, f - vec2(1.0, 0.0));
236
+ float vc = dot(gc, f - vec2(0.0, 1.0));
237
+ float vd = dot(gd, f - vec2(1.0, 1.0));
238
+
239
+ float v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * (va - vb - vc + vd);
240
+ vec2 g = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)
241
+ + du * (u.yx * (va - vb - vc + vd) + vec2(vb, vc) - va);
242
+ return vec3(v, g);
243
+ }
244
+
245
+ /**
246
+ * Ripple below the mesh.
247
+ *
248
+ * This is not decoration: the per-ring LOD in oceanWaveSum deletes every
249
+ * component shorter than the cell, and near the camera that is most of the
250
+ * high-frequency energy in the sea. Putting it back as a normal perturbation is
251
+ * cheaper than carrying it in geometry and, past a few metres, indistinguishable
252
+ * - a ripple two pixels across is a normal, not a shape.
253
+ *
254
+ * Two octaves drifting in opposite directions, because one drifting field reads
255
+ * as a texture being dragged across the water.
256
+ */
257
+ float oceanDetailFade(float dist){
258
+ // Two fades, and the second is the one that matters. Distance alone is not
259
+ // what makes a ripple alias - a screen-space footprint is. A two-metre ripple
260
+ // is a shape at fifty metres and sub-pixel noise at two thousand, and which of
261
+ // those it is depends on the field of view and the resolution, not on the
262
+ // distance. Fading on the footprint means the same water looks right in a
263
+ // wide shot and a telephoto one.
264
+ float footprint = dist * u_pixelScale;
265
+ float size = 1.0 / max(u_detailScale, 1e-4);
266
+ float screen = 1.0 - smoothstep(size * 0.35, size * 1.4, footprint);
267
+ return exp(-dist * u_detailFade) * screen;
268
+ }
269
+
270
+ vec3 oceanDetailNormal(vec3 n, vec3 wp, float fade){
271
+ float amt = u_detailStrength * fade;
272
+ if (amt <= 0.001) return n;
273
+
274
+ vec2 p = wp.xz * u_detailScale;
275
+ vec2 drift = vec2(u_time * 0.06, u_time * 0.043);
276
+
277
+ vec3 a = oNoised(p + drift);
278
+ vec3 b = oNoised(p * 2.17 - drift * 1.6);
279
+ vec2 g = a.yz + b.yz * 0.5;
280
+
281
+ // Perturb in the surface plane, so a steep wave face keeps its own orientation
282
+ // and only gains texture.
283
+ vec3 t = normalize(cross(vec3(0.0, 1.0, 0.0), n) + vec3(1e-5, 0.0, 0.0));
284
+ vec3 bt = cross(n, t);
285
+ // 0.12, not 0.4. The gradient of the noise is order 1, so the constant IS the
286
+ // maximum slope the ripple adds: at 0.4 the default strength tilts the normal
287
+ // by twelve degrees, and at grazing angles - which is most of an ocean - a
288
+ // twelve degree tilt swings the Fresnel term between reflecting the sky and
289
+ // not. The whole surface then boils with white speckle, which is exactly what
290
+ // it did. Sub-metre ripple on open water is a few degrees of slope.
291
+ return normalize(n - (t * g.x + bt * g.y) * amt * 0.12);
292
+ }
293
+
294
+ /**
295
+ * Whitecaps, from the Jacobian of the horizontal map.
296
+ *
297
+ * Where the determinant falls toward zero the surface is folding into itself,
298
+ * which is physically what a breaking crest is. It is the same quantity an FFT
299
+ * ocean reads off its displacement cascade, and a Gerstner sum hands it over for
300
+ * nothing, so foam is measured here rather than paint over the crests.
301
+ *
302
+ * u_foamDecay is a stand-in for a real accumulation buffer: it widens the
303
+ * threshold on the trailing side, so foam persists behind the crest instead of
304
+ * flickering on and off with it. The honest version writes into a persistent
305
+ * target and advects it - deferred with the FFT phase.
306
+ */
307
+ float oceanFoam(float jacobian, float relHeight, vec3 wp){
308
+ float compress = saturate(1.0 - jacobian);
309
+
310
+ // Thresholds are calibrated against the compression the wave table actually
311
+ // produces, which is a much narrower range than it looks: at the defaults the
312
+ // Jacobian sits just above zero at the sharpest crest and near 1 everywhere
313
+ // else. A threshold picked by eye either foams the whole sea or none of it.
314
+ float thr = mix(0.85, 0.15, saturate(u_foamAmount * 0.6));
315
+ float sharp = smoothstep(thr, thr + 0.15, compress);
316
+ float trail = smoothstep(thr * 0.55, thr + 0.4, compress);
317
+ float f = mix(sharp, max(sharp, trail), saturate(u_foamDecay));
318
+
319
+ // Foam belongs on and just behind the crest, never in the trough.
320
+ f *= smoothstep(-0.15, 0.45, relHeight);
321
+
322
+ // Break it up, or it reads as a decal following the wave.
323
+ vec2 q = wp.xz * u_foamScale;
324
+ float n = oNoised(q + vec2(u_time * 0.05, -u_time * 0.03)).x * 0.5 + 0.5;
325
+ n = mix(n, 1.0, 0.35);
326
+ return saturate(f * n * u_foamAmount);
327
+ }
328
+
329
+ /**
330
+ * The water shading.
331
+ *
332
+ * Reflection is the whole story above a few degrees of grazing - most of what
333
+ * you see looking at the sea is the sky - so the Fresnel split, the sky radiance
334
+ * and the aerial perspective all have to come from the same atmosphere the sky
335
+ * itself is drawn with, or the water is obviously pasted onto the scene. That is
336
+ * skyRadiance from cloudCore, driven by applySunUniforms off the same
337
+ * useCloudSun state as the clouds and the terrain.
338
+ */
339
+ vec3 oceanLight(
340
+ vec3 wp, vec3 n, vec3 viewDir, // viewDir points FROM the camera TO the surface
341
+ float foam, float relHeight, float shadow, float depthFactor,
342
+ float roughnessBoost, // specular antialiasing, see the caller
343
+ vec3 reflectionColor
344
+ ){
345
+ vec3 V = -viewDir;
346
+ vec3 L = normalize(u_sunDir);
347
+ float NdotL = max(dot(n, L), 0.0);
348
+ vec3 sun = u_sunColor * u_sunIntensity * shadow;
349
+
350
+ // Reflection. Bending the reflected ray back above the horizon is a cheat, and
351
+ // the alternative is worse: at grazing angles a wave face routinely reflects
352
+ // just below the horizon, and sampling the sky's ground term there puts a band
353
+ // of dirt across the brightest part of the water.
354
+ vec3 R = reflect(viewDir, n);
355
+ R.y = max(R.y, 0.015);
356
+ vec3 sky = length(reflectionColor) > 0.0 ? reflectionColor : skyRadiance(R);
357
+
358
+ // Prefilter the reflection by the same roughness the specular lobe uses.
359
+ // Without this the specular is filtered and the reflection is not, and on
360
+ // water that is the wrong half: reflection is most of what you see, and a
361
+ // mirror sampled through a noisy normal aliases exactly as hard as a highlight
362
+ // does. u_skyAmbient is already the hemispheric average of this sky, so it is
363
+ // the infinitely-rough end of the same lobe and costs nothing to mix toward.
364
+ float rough = clamp(u_roughness + roughnessBoost, 0.01, 1.0);
365
+ sky = mix(sky, u_skyAmbient * 0.6, saturate(rough * 2.2));
366
+
367
+ float fres = 0.02 + 0.98 * pow(1.0 - saturate(dot(V, n)), 5.0);
368
+
369
+ // Body colour. depthFactor is 1 in open water and ramps to 0 as the bottom
370
+ // comes up; until the refraction grab exists it is 1 everywhere.
371
+ vec3 body = mix(u_shallowColor, u_deepColor, saturate(depthFactor));
372
+ body = mix(body, u_shallowColor, u_clarity * 0.35);
373
+ vec3 bodyLit = body * (u_skyAmbient * u_ambientScale * 0.22 + sun * (0.25 + 0.55 * NdotL));
374
+
375
+ // Subsurface. A wave lit from behind glows through its own crest, and the
376
+ // effect is strongest where the surface is steep and thin - so it keys off the
377
+ // wave's own height rather than off thickness we do not have.
378
+ float back = pow(saturate(dot(V, -L) * 0.5 + 0.5), 3.0);
379
+ float thin = saturate(relHeight * 1.4);
380
+ vec3 sss = u_sssColor * u_sssStrength * back * thin * u_sunColor * u_sunIntensity * (0.35 + 0.65 * shadow);
381
+
382
+ // GGX. The glitter path from the horizon to the camera is most of what says
383
+ // "water" at distance, and it is a specular lobe on a normal that is moving -
384
+ // which is why the detail normal matters as much as the mesh does.
385
+ // Water is nearly smooth, which is exactly what makes it alias: a specular
386
+ // lobe this tight flips between nothing and a blown-out highlight across one
387
+ // pixel, and a sea of them boils. The fix is not a softer sea - it is to widen
388
+ // the lobe by however much the normal varies inside the pixel, so the energy
389
+ // is kept and the flicker is not. roughnessBoost carries that in, and the
390
+ // reflection above is filtered by the same number.
391
+ float a = max(rough * rough, 2e-4);
392
+ vec3 h = normalize(L + V);
393
+ float ndh = max(dot(n, h), 0.0);
394
+ float denom = ndh * ndh * (a * a - 1.0) + 1.0;
395
+ float ggx = (a * a) / (PI * denom * denom);
396
+ vec3 spec = sun * ggx * u_specular * fres;
397
+
398
+ vec3 col = mix(bodyLit + sss, sky, fres) + spec;
399
+
400
+ // Foam is a rough dielectric, so it takes the sky and the sun diffusely and
401
+ // kills the reflection under it.
402
+ vec3 foamLit = u_foamColor * (u_skyAmbient * u_ambientScale * 0.5 + sun * (0.3 + 0.7 * NdotL));
403
+ col = mix(col, foamLit, saturate(foam));
404
+
405
+ return col;
406
+ }
407
+
408
+ /** Fade into the sky the water is seen through. Same term as the terrain's,
409
+ * and it needs to be stronger here: an ocean runs to the horizon in every
410
+ * direction, so it is the only thing stopping the far field reading as a
411
+ * painted plane. */
412
+ vec3 oceanAerial(vec3 col, vec3 rd, float dist){
413
+ float f = 1.0 - exp(-u_fogDensity * dist);
414
+ if (f < 0.002) return col;
415
+ return mix(col, skyRadiance(rd), f);
416
+ }
417
+ `;
418
+ /** Height field only — what a vertex stage needs. */
419
+ export const oceanField = [oceanUniforms, oceanWaves].join('\n');
420
+ /** The whole model. */
421
+ export const oceanFull = [oceanUniforms, oceanWaves, oceanShade].join('\n');