@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,625 @@
1
+ /**
2
+ * Shared terrain core. Imported by TerrainMaterial and available to anything else
3
+ * that needs to know where the ground is.
4
+ *
5
+ * Nothing here is per-type in the sense of separate code paths for separate
6
+ * renderers: the four landforms are one fBm with different shaping operators,
7
+ * selected by `u_terrainType`, exactly as the three volumetric cloud types are
8
+ * one marcher with different vertical profiles.
9
+ *
10
+ * These chunks assume three's `<common>` has already been included — PI and
11
+ * saturate come from there, and re-declaring them next to it is a compile error.
12
+ * `skyLib` from `cloudCore.glsl` is included alongside, so terrain, sky and
13
+ * clouds are lit by literally the same atmosphere function.
14
+ *
15
+ * ## The performance shape of this file
16
+ *
17
+ * Two decisions dominate the cost of everything below, and both were made after
18
+ * measuring rather than guessing:
19
+ *
20
+ * 1. **The height field carries its own derivative.** `terrainSample` returns the
21
+ * surface gradient alongside the height, so a normal costs ONE evaluation
22
+ * instead of the five a central difference needs. The chain rule is carried
23
+ * through the domain warp, the contrast curve and every landform operator.
24
+ * 2. **The hash is arithmetic, not trigonometric.** `sin`-based hashing is the
25
+ * default everywhere online and it is a trap at this call volume: four hashes
26
+ * per octave, nine octaves, several evaluations per pixel puts it into the
27
+ * billions of transcendentals per frame.
28
+ */
29
+ /** Every uniform the height field and the shading read. */
30
+ export const terrainUniforms = /* glsl */ `
31
+ uniform int u_terrainType;
32
+
33
+ uniform float u_elevation;
34
+ uniform float u_seaLevel;
35
+ uniform float u_baseScale; // 1 / featureSize
36
+ uniform int u_octaves;
37
+ uniform float u_lacunarity;
38
+ uniform float u_gain;
39
+ uniform float u_ridge;
40
+ uniform float u_warpStrength;
41
+ uniform float u_warpScale;
42
+ uniform float u_erosion;
43
+ uniform float u_terraces;
44
+ uniform float u_terraceSharp;
45
+ uniform vec2 u_bearingDir;
46
+ uniform float u_duneFreq; // 2pi / duneWavelength
47
+ uniform float u_duneAsym;
48
+ uniform float u_plateau;
49
+ uniform float u_valleyDepth;
50
+ uniform float u_valleyWidth;
51
+
52
+ uniform float u_cellSize;
53
+ uniform float u_curvature;
54
+
55
+ // surface
56
+ uniform vec3 u_lowColor, u_midColor, u_rockColor, u_peakColor;
57
+ uniform float u_slopeRockStart, u_slopeRockEnd;
58
+ uniform float u_peakLevel, u_peakBlend, u_peakSlopeFalloff;
59
+ uniform float u_macroVariation, u_macroScale;
60
+ uniform float u_detailStrength, u_detailScale, u_detailFade;
61
+ uniform float u_roughness, u_specular;
62
+
63
+ // lighting. u_skyAmbient and u_groundAmbient come from useCloudSun through
64
+ // applySunUniforms - the same objects that light the clouds. They are constant
65
+ // across the frame, so computing them on the CPU rather than calling skyRadiance
66
+ // per pixel removes an acos, a pow and three exps from every fragment.
67
+ uniform vec3 u_skyAmbient;
68
+ uniform vec3 u_groundAmbient;
69
+ uniform float u_ambient, u_bounce, u_sunShadow, u_shadowSoftness;
70
+ uniform float u_shadowDistance;
71
+ uniform float u_fogDensity, u_exposure;
72
+ uniform int u_shadowSteps;
73
+ `;
74
+ /**
75
+ * Gradient noise with an analytic derivative.
76
+ *
77
+ * The derivative does two jobs. It is what makes `u_erosion` possible — real
78
+ * erosion strips material where water runs fastest, so damping each octave by the
79
+ * slope the octaves below it have already accumulated flattens valley floors and
80
+ * leaves ridges sharp. And it is what lets the surface normal fall out of the
81
+ * height evaluation for free instead of costing four extra ones.
82
+ *
83
+ * The hash is Hoskins-style: a few multiplies and fracts. The usual
84
+ * `fract(sin(dot(p, k)) * 43758.5453)` costs two transcendentals per component,
85
+ * and there are four hashes per octave — at nine octaves and several evaluations
86
+ * per shaded point that alone was most of the frame.
87
+ */
88
+ export const terrainNoise = /* glsl */ `
89
+ vec2 tHash(vec2 p){
90
+ vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
91
+ p3 += dot(p3, p3.yzx + 33.33);
92
+ return -1.0 + 2.0 * fract((p3.xx + p3.yz) * p3.zy);
93
+ }
94
+
95
+ /** x = value in [-1,1], yz = d(value)/d(input). */
96
+ vec3 tNoised(vec2 x){
97
+ vec2 i = floor(x);
98
+ vec2 f = fract(x);
99
+
100
+ vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
101
+ vec2 du = 30.0 * f * f * (f * (f - 2.0) + 1.0);
102
+
103
+ vec2 ga = tHash(i + vec2(0.0, 0.0));
104
+ vec2 gb = tHash(i + vec2(1.0, 0.0));
105
+ vec2 gc = tHash(i + vec2(0.0, 1.0));
106
+ vec2 gd = tHash(i + vec2(1.0, 1.0));
107
+
108
+ float va = dot(ga, f - vec2(0.0, 0.0));
109
+ float vb = dot(gb, f - vec2(1.0, 0.0));
110
+ float vc = dot(gc, f - vec2(0.0, 1.0));
111
+ float vd = dot(gd, f - vec2(1.0, 1.0));
112
+
113
+ float k = va - vb - vc + vd;
114
+ float v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * k;
115
+ vec2 d = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)
116
+ + du * (u.yx * k + vec2(vb, vc) - va);
117
+ return vec3(v, d);
118
+ }
119
+
120
+ float tNoise(vec2 x){ return tNoised(x).x; }
121
+ `;
122
+ /**
123
+ * The height field, and its gradient.
124
+ *
125
+ * `lodCell` is the world size of the cell being shaded, and every octave whose
126
+ * wavelength has fallen below a couple of those cells is faded out and then
127
+ * skipped. That is doing two jobs at once: it is the antialiasing (a ring 12 km
128
+ * out cannot resolve 4 m detail, and sampling it anyway is what makes distant
129
+ * terrain crawl), and it is most of the cost saving, because the outer rings —
130
+ * which carry three quarters of the vertices — run four octaves instead of nine.
131
+ */
132
+ export const terrainHeight = /* glsl */ `
133
+ #define TERRAIN_MOUNTAINS 0
134
+ #define TERRAIN_DUNES 1
135
+ #define TERRAIN_HILLS 2
136
+ #define TERRAIN_CANYON 3
137
+
138
+ #define TERRAIN_MAX_OCTAVES 12
139
+
140
+ /** Normalised fBm in [0,1], derivative-damped, with d/d(worldXZ) in grad. */
141
+ float terrainFbmD(vec2 p, float lodCell, out vec2 grad){
142
+ vec2 q = p * u_baseScale;
143
+
144
+ // Domain warp. Without it fBm reads as fBm: blobs with no direction and no
145
+ // drainage. Warping the sample point by a slower copy of the same noise bends
146
+ // ridge lines into the branching curves erosion actually produces.
147
+ //
148
+ // Its Jacobian is carried rather than assumed to be the identity. That shortcut
149
+ // is the usual one and it shows: at warpStrength 0.3 the normal is wrong by up
150
+ // to 30% precisely where the warp bends a ridge, which is where the eye goes.
151
+ vec2 qw = q;
152
+ mat2 J = mat2(1.0, 0.0, 0.0, 1.0);
153
+ if (u_warpStrength > 0.0){
154
+ vec3 w0 = tNoised(q * u_warpScale + vec2(11.3, 5.7));
155
+ vec3 w1 = tNoised(q * u_warpScale + vec2(-7.1, 19.2));
156
+ qw = q + vec2(w0.x, w1.x) * u_warpStrength;
157
+ float k = u_warpStrength * u_warpScale;
158
+ // Columns, so that (v * J) evaluates transpose(J) * v - which is the chain
159
+ // rule direction we need, and GLSL ES 1.00 has no transpose().
160
+ J = mat2(1.0 + k * w0.y, k * w1.y,
161
+ k * w0.z, 1.0 + k * w1.z);
162
+ }
163
+
164
+ float amp = 1.0;
165
+ float freq = 1.0;
166
+ float sum = 0.0;
167
+ float norm = 0.0;
168
+ vec2 gradW = vec2(0.0); // d(sum) / d(qw)
169
+ vec2 slope = vec2(0.0); // bounded erosion accumulator, octave-local units
170
+
171
+ // Wavelength of the first octave, in world units.
172
+ float wl = 1.0 / max(u_baseScale, 1e-9);
173
+
174
+ for (int i = 0; i < TERRAIN_MAX_OCTAVES; i++){
175
+ if (i >= u_octaves) break;
176
+
177
+ // Fade an octave out as its wavelength approaches the shading cell, then
178
+ // stop. Popping it off with a hard test leaves a visible ring where the
179
+ // clipmap level changes; fading is what makes the transition invisible.
180
+ float limit = 2.0 * lodCell;
181
+ float fade = clamp(wl / max(limit, 1e-4) - 1.0, 0.0, 1.0);
182
+ if (fade <= 0.001) break;
183
+
184
+ // A constant per-octave offset rather than a rotation matrix: rotating the
185
+ // domain would need the transpose applied to the derivative as well, and the
186
+ // offset decorrelates the octaves just as effectively.
187
+ vec3 n = tNoised(qw * freq + float(i) * vec2(37.13, 61.91));
188
+ float v = n.x;
189
+ vec2 g = n.yz;
190
+
191
+ // Ridge shaping, with the chain rule carried through so both the erosion term
192
+ // and the surface normal still see a true slope. 1 - |v| folds the noise about
193
+ // zero; squaring sharpens the crest.
194
+ if (u_ridge > 0.0){
195
+ // Ramp the fold in by octave. 1 - |v| is C1-discontinuous along the whole
196
+ // v = 0 contour, and on octave 0 that contour is a kilometre-long smooth
197
+ // arc - so the crease runs right across the terrain and reads as a
198
+ // modelling seam, not as a ridge. Found by baking presets to heightmaps
199
+ // and hillshading them, where the arcs are unmistakable; on the lit
200
+ // terrain they hide as a suspiciously straight shadow edge.
201
+ //
202
+ // A crest only reads as a crest at a scale where a crest is plausible, so
203
+ // the base octave stays smooth, the next is half folded and everything
204
+ // finer is fully ridged. Sharpness at the scales that matter is unchanged.
205
+ float ridgeAmt = u_ridge * clamp(float(i) * 0.5, 0.0, 1.0);
206
+ float s = sign(v);
207
+ float r = 1.0 - abs(v);
208
+ float rv = r * r * 2.0 - 1.0;
209
+ vec2 rg = -4.0 * r * s * g;
210
+ v = mix(v, rv, ridgeAmt);
211
+ g = mix(g, rg, ridgeAmt);
212
+ }
213
+
214
+ // The erosion term. Slope accumulated so far suppresses everything finer.
215
+ // Deliberately fed the OCTAVE-LOCAL derivative, not the frequency-scaled one:
216
+ // scaled, it grows like lacunarity^i, the damping collapses by the third
217
+ // octave and every finer octave vanishes - a terrain that is flat however high
218
+ // its elevation is set. Its own derivative is ignored, which is the standard
219
+ // approximation and invisible at these amplitudes.
220
+ float damp = 1.0 / (1.0 + u_erosion * 1.5 * dot(slope, slope));
221
+ float wgt = amp * damp * fade;
222
+
223
+ sum += wgt * v;
224
+ norm += amp * fade;
225
+ gradW += wgt * g * freq;
226
+ slope += g * amp * damp;
227
+
228
+ amp *= u_gain;
229
+ freq *= u_lacunarity;
230
+ wl /= u_lacunarity;
231
+ }
232
+
233
+ float inv = 1.0 / max(norm, 1e-4);
234
+
235
+ // Contrast, and it is not a taste knob.
236
+ //
237
+ // Dividing by the summed amplitude is the textbook normalisation and it is
238
+ // wrong for terrain: octaves of gradient noise almost never align, so the sum
239
+ // lands in about a quarter of the range the divisor assumes and the field comes
240
+ // out spanning 0.45-0.69 instead of 0-1. A 1400 m elevation then renders as a
241
+ // 320 m swell, and no amount of extra octaves fixes it because they are being
242
+ // divided away too. Measured against the height field directly: 3.2 recovers
243
+ // roughly 740 m of the 1400, which is a real range at a real grade.
244
+ //
245
+ // The soft clamp rather than a hard one, so a peak that would overshoot rolls
246
+ // off instead of arriving with a flat top.
247
+ float t = sum * inv * 3.2;
248
+ float rs = inversesqrt(1.0 + t * t);
249
+
250
+ // d(height)/d(qw) -> d/d(q) via the warp Jacobian -> d/d(world).
251
+ vec2 dqw = gradW * (0.5 * 3.2 * rs * rs * rs * inv);
252
+ grad = (dqw * J) * u_baseScale;
253
+
254
+ return 0.5 + 0.5 * t * rs;
255
+ }
256
+
257
+ /**
258
+ * Height above sea level in world units at a world XZ, and the surface gradient
259
+ * d(height)/d(worldXZ). The normal is normalize(vec3(-grad.x, 1, -grad.y)).
260
+ *
261
+ * Curvature is deliberately NOT applied here. It is a view-relative bend applied
262
+ * at the vertex, and folding it in would make the height field depend on where
263
+ * the camera is — which the shadow march and the normals both sample, and would
264
+ * mean a shadow that moves when you turn your head.
265
+ */
266
+ float terrainSample(vec2 p, float lodCell, out vec2 grad){
267
+ vec2 gh;
268
+ float h = terrainFbmD(p, lodCell, gh);
269
+
270
+ if (u_terrainType == TERRAIN_DUNES){
271
+ // Dunes are a wave train transverse to the wind, not a noise field. The fBm
272
+ // bends the crest lines and sets how tall each dune is; the sine is the dune.
273
+ vec2 d = vec2(u_bearingDir.y, -u_bearingDir.x);
274
+ float phase = dot(p, d) * u_duneFreq + 7.0 * h;
275
+ vec2 dphase = d * u_duneFreq + 7.0 * gh;
276
+
277
+ float u = 0.5 + 0.5 * sin(phase);
278
+ vec2 du = 0.5 * cos(phase) * dphase;
279
+
280
+ // Asymmetry: a real dune is a long windward ramp and a short slip face at the
281
+ // angle of repose. Raising the crest to a power does that in one instruction.
282
+ float k = mix(1.0, 3.2, u_duneAsym);
283
+ float us = max(u, 1e-4);
284
+ float s = pow(us, k);
285
+ vec2 ds = k * pow(us, k - 1.0) * du;
286
+
287
+ // The envelope keeps dune height varying across the field rather than
288
+ // stamping one ridge repeated to the horizon.
289
+ float env = 0.45 + 0.55 * h;
290
+ float a = 0.15 + 0.5 * h;
291
+ float b = s * env;
292
+ vec2 da = 0.5 * gh;
293
+ vec2 db = ds * env + s * 0.55 * gh;
294
+ h = mix(a, b, 0.8);
295
+ gh = mix(da, db, 0.8);
296
+ }
297
+ else if (u_terrainType == TERRAIN_CANYON){
298
+ // Canyon country is a plateau with things taken out of it, not a mountain
299
+ // range. Flatten the top first, then cut.
300
+ float om = 1.0 - h;
301
+ float mesa = mix(h, 1.0 - om * om * om, u_plateau);
302
+ float dmf = mix(1.0, 3.0 * om * om, u_plateau);
303
+
304
+ // Channel network: ridged noise is near 1 along its zero set, which is a
305
+ // connected branching line — a drainage network, for free.
306
+ vec3 cn = tNoised(p * (u_baseScale * 1.7) + vec2(4.2, -9.1));
307
+ float ch = 1.0 - abs(cn.x);
308
+ vec2 dch = -sign(cn.x) * cn.yz * (u_baseScale * 1.7);
309
+
310
+ float e0 = 1.0 - u_valleyWidth;
311
+ float ct = clamp((ch - e0) / max(1.0 - e0, 1e-4), 0.0, 1.0);
312
+ float carve = ct * ct * (3.0 - 2.0 * ct);
313
+ float dcarve = 6.0 * ct * (1.0 - ct) / max(1.0 - e0, 1e-4);
314
+
315
+ h = mesa - u_valleyDepth * carve;
316
+ gh = dmf * gh - (u_valleyDepth * dcarve) * dch;
317
+ }
318
+
319
+ // Sedimentary banding. Applied after shaping, so it bands the finished landform
320
+ // rather than the raw noise.
321
+ if (u_terraces >= 0.5){
322
+ float n = u_terraces;
323
+
324
+ // The aliasing bound is HORIZONTAL, not vertical. What aliases is how far
325
+ // across the ground a riser takes to climb, and that scales with the
326
+ // landform's own size divided by the number of bands stacked on it. Widening
327
+ // the riser is not enough on its own either: smoothstep(0, 1, fr) still has a
328
+ // flat spot at each end, so a "fully softened" terrace is still a staircase.
329
+ // Fade all the way to the identity ramp, which is the un-terraced surface.
330
+ float featureSize = 1.0 / max(u_baseScale, 1e-9);
331
+ float soft = clamp(lodCell * n * 8.0 / featureSize, 0.0, 1.0);
332
+ float w = max(1.0 - 0.95 * u_terraceSharp, 0.02);
333
+
334
+ float x = h * n;
335
+ float fr = fract(x);
336
+ float ct = clamp(fr / w, 0.0, 1.0);
337
+ float k = mix(ct * ct * (3.0 - 2.0 * ct), fr, soft);
338
+ float dk = mix(6.0 * ct * (1.0 - ct) / w, 1.0, soft);
339
+
340
+ h = (floor(x) + k) / n;
341
+ gh = dk * gh;
342
+ }
343
+
344
+ grad = gh * u_elevation;
345
+ return u_seaLevel + u_elevation * h;
346
+ }
347
+
348
+ /**
349
+ * Height only, and a genuinely separate path rather than terrainSample with the
350
+ * gradient thrown away.
351
+ *
352
+ * This is the shadow march's inner loop - fourteen calls per shaded point - and
353
+ * it is the one place where paying for a derivative nobody reads actually shows
354
+ * up. No warp Jacobian, no gradient accumulation, no chain rule through the
355
+ * shaping: about half the work of the full sample.
356
+ */
357
+ float terrainHeightAt(vec2 p, float lodCell){
358
+ vec2 q = p * u_baseScale;
359
+ if (u_warpStrength > 0.0){
360
+ q += vec2(
361
+ tNoise(q * u_warpScale + vec2(11.3, 5.7)),
362
+ tNoise(q * u_warpScale + vec2(-7.1, 19.2))
363
+ ) * u_warpStrength;
364
+ }
365
+
366
+ float amp = 1.0, freq = 1.0, sum = 0.0, norm = 0.0;
367
+ vec2 slope = vec2(0.0);
368
+ float wl = 1.0 / max(u_baseScale, 1e-9);
369
+
370
+ for (int i = 0; i < TERRAIN_MAX_OCTAVES; i++){
371
+ if (i >= u_octaves) break;
372
+ float limit = 2.0 * lodCell;
373
+ float fade = clamp(wl / max(limit, 1e-4) - 1.0, 0.0, 1.0);
374
+ if (fade <= 0.001) break;
375
+
376
+ vec3 n = tNoised(q * freq + float(i) * vec2(37.13, 61.91));
377
+ float v = n.x;
378
+ vec2 g = n.yz;
379
+ if (u_ridge > 0.0){
380
+ // Same octave ramp as the derivative path above - the two must agree or a
381
+ // height query and the rendered surface disagree by the fold amount.
382
+ float ridgeAmt = u_ridge * clamp(float(i) * 0.5, 0.0, 1.0);
383
+ float sg = sign(v);
384
+ float r = 1.0 - abs(v);
385
+ v = mix(v, r * r * 2.0 - 1.0, ridgeAmt);
386
+ g = mix(g, -4.0 * r * sg * g, ridgeAmt);
387
+ }
388
+ float damp = 1.0 / (1.0 + u_erosion * 1.5 * dot(slope, slope));
389
+ sum += amp * damp * fade * v;
390
+ norm += amp * fade;
391
+ slope += g * amp * damp;
392
+
393
+ amp *= u_gain;
394
+ freq *= u_lacunarity;
395
+ wl /= u_lacunarity;
396
+ }
397
+
398
+ float t = (sum / max(norm, 1e-4)) * 3.2;
399
+ float h = 0.5 + 0.5 * t * inversesqrt(1.0 + t * t);
400
+
401
+ if (u_terrainType == TERRAIN_DUNES){
402
+ vec2 d = vec2(u_bearingDir.y, -u_bearingDir.x);
403
+ float u = 0.5 + 0.5 * sin(dot(p, d) * u_duneFreq + 7.0 * h);
404
+ float s = pow(max(u, 1e-4), mix(1.0, 3.2, u_duneAsym));
405
+ h = mix(0.15 + 0.5 * h, s * (0.45 + 0.55 * h), 0.8);
406
+ }
407
+ else if (u_terrainType == TERRAIN_CANYON){
408
+ float om = 1.0 - h;
409
+ float mesa = mix(h, 1.0 - om * om * om, u_plateau);
410
+ float ch = 1.0 - abs(tNoise(p * (u_baseScale * 1.7) + vec2(4.2, -9.1)));
411
+ h = mesa - u_valleyDepth * smoothstep(1.0 - u_valleyWidth, 1.0, ch);
412
+ }
413
+
414
+ if (u_terraces >= 0.5){
415
+ float n = u_terraces;
416
+ float featureSize = 1.0 / max(u_baseScale, 1e-9);
417
+ float soft = clamp(lodCell * n * 8.0 / featureSize, 0.0, 1.0);
418
+ float w = max(1.0 - 0.95 * u_terraceSharp, 0.02);
419
+ float x = h * n;
420
+ float fr = fract(x);
421
+ h = (floor(x) + mix(smoothstep(0.0, w, fr), fr, soft)) / n;
422
+ }
423
+
424
+ return u_seaLevel + u_elevation * h;
425
+ }
426
+
427
+ vec3 terrainNormal(vec2 grad){
428
+ return normalize(vec3(-grad.x, 1.0, -grad.y));
429
+ }
430
+ `;
431
+ /**
432
+ * Shading: the analytic self-shadow, the splat, and the light.
433
+ *
434
+ * The self-shadow is a ray march against the height field rather than a shadow
435
+ * map, and that is the decision that makes terrain shadows work at this scale. A
436
+ * 2048 map stretched over 25 km is 12 m per texel; a mountain's shadow edge would
437
+ * be a staircase you could stand in. Marching the field costs a fixed number of
438
+ * height evaluations and has no resolution to run out of.
439
+ *
440
+ * It is called from the VERTEX shader. Per pixel it was the single most expensive
441
+ * thing in the renderer — twenty height evaluations on every shaded fragment — and
442
+ * per vertex it is two orders of magnitude cheaper while looking almost identical,
443
+ * because what it produces is a soft occlusion factor that interpolates cleanly.
444
+ * The terminator itself stays per-pixel: `terrainLight` takes N·L from the
445
+ * fragment's own normal, so only the CAST shadow is interpolated.
446
+ */
447
+ export const terrainShading = /* glsl */ `
448
+ /** 1 lit, 0 shadowed, soft in between. */
449
+ float terrainSunShadow(vec3 p, vec3 n, float lodCell, int steps){
450
+ if (steps < 1 || u_sunShadow <= 0.0) return 1.0;
451
+ // Below the horizon there is nothing to shadow; the march would also walk
452
+ // downhill forever.
453
+ if (u_sunDir.y <= 0.015) return 1.0;
454
+ // A surface facing away from the sun is shadowed by definition, and marching
455
+ // from it only finds the ground it is already part of.
456
+ if (dot(n, u_sunDir) <= 0.0) return 0.0;
457
+
458
+ // Lift off the surface before starting, or every sample on a steep face - a
459
+ // terrace riser above all - reads its own ground as an occluder and the whole
460
+ // wall goes black in a grid of triangles. Start ON the offset surface and
461
+ // compare raw heights; subtracting the bias from the height difference instead
462
+ // makes the first sample fail on any slope tilted a few degrees from the sun.
463
+ float bias = max(lodCell, u_elevation * 0.003);
464
+ vec3 origin = p + n * bias;
465
+
466
+ float shadowLod = (1.0 / max(u_baseScale, 1e-9)) * 0.008;
467
+
468
+ float res = 1.0;
469
+ float t = bias * 3.0;
470
+ float maxT = (u_elevation * 2.5) / max(u_sunDir.y, 0.05);
471
+
472
+ for (int i = 0; i < 48; i++){
473
+ if (i >= steps || t > maxT) break;
474
+ vec3 sp = origin + u_sunDir * t;
475
+ // The occluder is sampled at the SHADING lod, coarsening with distance -
476
+ // never at a fixed coarse lod. Testing a fine surface point against a coarse
477
+ // height field puts the whole valley floor in shadow, because the coarse
478
+ // field sits above the fine one wherever the fine one has been cut down.
479
+ // Sampled MUCH coarser than the surface being shaded. A shadow does not need
480
+ // nine octaves: what casts a visible shadow is the landform, and marching the
481
+ // fine field costs the same nine octaves per step for detail that is below a
482
+ // pixel by the time it lands. Floored against the landform's own size, so the
483
+ // march runs about five octaves instead of nine wherever it starts.
484
+ float h = terrainHeightAt(sp.xz, max(max(lodCell * 2.0, t * 0.06), shadowLod));
485
+ float diff = sp.y - h;
486
+ res = min(res, u_shadowSoftness * diff / t);
487
+ if (res <= 0.0) return 0.0;
488
+ // Geometric growth: near the surface the occluder is metres away, at the end
489
+ // of the march it is kilometres, and a linear step wastes every sample at one
490
+ // end or the other. 1.7 rather than 1.42 so twelve steps actually reach maxT -
491
+ // at 1.42 the march ran out around 700 m and long shadows at low sun simply
492
+ // did not exist, which is a quality bug that happened to look like a cheap one.
493
+ t = t * 1.7 + bias;
494
+ }
495
+ return saturate(res);
496
+ }
497
+
498
+ /**
499
+ * The shadow, faded out with distance and given a step budget that falls with it.
500
+ *
501
+ * The march is the most expensive thing left in the renderer, and past a few
502
+ * kilometres it is buying nothing: aerial perspective has already washed most of
503
+ * the contrast out of the ground, and a ridge shadow out there is a couple of
504
+ * pixels wide. Skipping it entirely beyond u_shadowDistance drops the outer
505
+ * clipmap rings - which are two thirds of the vertices - out of the march
506
+ * completely, and the rings in between run four steps instead of fourteen.
507
+ *
508
+ * Faded rather than cut, or the cut-off would draw a visible circle on the ground.
509
+ */
510
+ float terrainShadowAt(vec3 p, vec3 n, float lodCell, float dist){
511
+ float fade = 1.0 - saturate(dist / max(u_shadowDistance, 1.0));
512
+ if (fade <= 0.002) return 1.0;
513
+ int steps = int(mix(4.0, float(u_shadowSteps), fade));
514
+ return mix(1.0, terrainSunShadow(p, n, lodCell, steps), fade);
515
+ }
516
+
517
+ /**
518
+ * Albedo.
519
+ *
520
+ * Rock is splatted by SLOPE, everything else by height. That ordering is the
521
+ * whole trick: it means a cliff is bare rock whether it is in a valley or on a
522
+ * summit, which is how real ground works and why height-only splatting always
523
+ * reads as a painted contour map.
524
+ */
525
+ vec3 terrainAlbedo(vec3 wp, float h01, float slope){
526
+ // Large-scale variation. Without it a splat layer is one flat colour across
527
+ // kilometres, and the eye reads flat colour as untextured rather than as ground.
528
+ float macro = tNoise(wp.xz * u_macroScale) * 0.5 + 0.5;
529
+ float tint = 1.0 + u_macroVariation * (macro - 0.5) * 2.0;
530
+
531
+ vec3 col = mix(u_lowColor, u_midColor, smoothstep(0.05, 0.55, h01));
532
+
533
+ // Peak layer, held back on steep ground: snow does not sit on a vertical face,
534
+ // and without this the summit looks dipped in paint.
535
+ float peak = smoothstep(u_peakLevel, u_peakLevel + u_peakBlend, h01 - slope * u_peakSlopeFalloff * 0.6);
536
+ col = mix(col, u_peakColor, saturate(peak));
537
+
538
+ float rock = smoothstep(u_slopeRockStart, u_slopeRockEnd, slope);
539
+ col = mix(col, u_rockColor, rock);
540
+
541
+ return col * tint;
542
+ }
543
+
544
+ /** High-frequency bump, well below the mesh resolution, faded out with distance
545
+ * so it never becomes a field of crawling pixels on the horizon. One evaluation:
546
+ * the noise carries its own derivative, so the three taps a finite difference
547
+ * would need are not. */
548
+ vec3 terrainDetailNormal(vec3 n, vec3 wp, float dist){
549
+ float fade = 1.0 - saturate(dist * u_detailFade);
550
+ float amt = u_detailStrength * fade * fade;
551
+ if (amt <= 0.001) return n;
552
+ vec3 d = tNoised(wp.xz * u_detailScale);
553
+ return normalize(n + vec3(-d.y, 0.0, -d.z) * amt * 0.9);
554
+ }
555
+
556
+ /**
557
+ * Direct sun, sky fill and ground bounce.
558
+ *
559
+ * Sun irradiance is in the units skyRadiance returns, calibrated against it rather
560
+ * than guessed: a 0.3-albedo surface in full sun lands a little below the sky at
561
+ * the horizon, which is the relationship the eye reads as daylight. Set it to the
562
+ * clouds' SUN_RADIANCE and every ground surface clips to white.
563
+ */
564
+ const float TERRAIN_SUN = 2.2;
565
+
566
+ vec3 terrainLight(vec3 albedo, vec3 n, vec3 viewDir, float shadow, float sceneShadow){
567
+ float ndl = max(dot(n, u_sunDir), 0.0);
568
+ // Wrap the terminator slightly: bare ground is not a Lambertian sphere, and a
569
+ // hard terminator across a whole hillside reads as CG.
570
+ float wrap = max((dot(n, u_sunDir) + 0.2) / 1.2, 0.0);
571
+ float diff = mix(ndl, wrap, 0.4);
572
+
573
+ vec3 sun = u_sunColor * u_sunIntensity * TERRAIN_SUN * diff * shadow * sceneShadow;
574
+
575
+ // Sky fill from the same atmosphere the sky is drawn with, so it darkens and
576
+ // warms with the sun instead of freezing at a constant blue - but supplied as a
577
+ // uniform rather than evaluated here, since it is the same for every pixel.
578
+ // Whitened a little: a single-scattering zenith colour is very saturated, and
579
+ // at full saturation every shadowed slope - snow above all - comes out cyan.
580
+ vec3 fill = mix(u_skyAmbient, vec3(dot(u_skyAmbient, vec3(0.3333))), 0.4);
581
+ vec3 ambient = fill * (0.5 + 0.5 * n.y) * u_ambient * 1.05;
582
+
583
+ // Ground bounce, tinted by whatever the ground is. Keeps overhangs and shaded
584
+ // faces off pure black without flattening them the way raising ambient does.
585
+ vec3 bounce = u_lowColor * u_groundAmbient * (0.5 - 0.5 * n.y) * u_bounce * 1.3;
586
+
587
+ vec3 col = albedo * (sun + ambient + bounce);
588
+
589
+ // A single specular lobe. Rock barely needs it; wet sand and snow at low sun
590
+ // are almost entirely this.
591
+ if (u_specular > 0.0){
592
+ vec3 hv = normalize(u_sunDir - viewDir);
593
+ float a = max(u_roughness * u_roughness, 0.002);
594
+ float nh = max(dot(n, hv), 0.0);
595
+ float dd = nh * nh * (a - 1.0) + 1.0;
596
+ float d = a / max(3.14159265 * dd * dd, 1e-4);
597
+ col += u_sunColor * u_sunIntensity * TERRAIN_SUN * d * u_specular * shadow * sceneShadow * ndl;
598
+ }
599
+
600
+ return col;
601
+ }
602
+
603
+ /**
604
+ * Aerial perspective.
605
+ *
606
+ * The single most important line in this shader for the horizon. Ground fades
607
+ * into the exact sky colour it is seen through, at a rate that thins with
608
+ * altitude because the atmosphere does — so a distant ridge sits behind the haze
609
+ * and a peak above it stands clear of it, which is what tells you the scene is
610
+ * kilometres across rather than metres.
611
+ *
612
+ * The one remaining per-pixel skyRadiance call, and it has to be: the colour is
613
+ * the sky in the view direction, which is different for every fragment.
614
+ */
615
+ vec3 terrainAerial(vec3 col, vec3 rd, float dist, float height){
616
+ float scaleHeight = exp(-max(height - u_seaLevel, 0.0) / 1400.0);
617
+ float f = 1.0 - exp(-dist * u_fogDensity * scaleHeight);
618
+ if (f < 0.002) return col;
619
+ return mix(col, skyRadiance(rd), saturate(f));
620
+ }
621
+ `;
622
+ /** Everything a surface material needs, in include order. Requires three's
623
+ * `<common>` and `skyLib` from cloudCore before it. */
624
+ export const terrainField = [terrainUniforms, terrainNoise, terrainHeight].join('\n');
625
+ export const terrainFull = [terrainField, terrainShading].join('\n');