@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.
- package/dist/components/Clouds.d.ts.map +1 -1
- package/dist/components/Clouds.js +0 -1
- package/dist/components/CloudsGenerator.d.ts +32 -0
- package/dist/components/CloudsGenerator.d.ts.map +1 -0
- package/dist/components/CloudsGenerator.js +58 -0
- package/dist/components/EnvironmentManager.d.ts +47 -5
- package/dist/components/EnvironmentManager.d.ts.map +1 -1
- package/dist/components/EnvironmentManager.js +35 -66
- package/dist/components/HdrEnvironment.d.ts +20 -0
- package/dist/components/HdrEnvironment.d.ts.map +1 -0
- package/dist/components/HdrEnvironment.js +44 -0
- package/dist/components/LandscapeBuilder.d.ts +23 -0
- package/dist/components/LandscapeBuilder.d.ts.map +1 -0
- package/dist/components/LandscapeBuilder.js +25 -0
- package/dist/components/LandscapeObject.d.ts +24 -0
- package/dist/components/LandscapeObject.d.ts.map +1 -0
- package/dist/components/LandscapeObject.js +27 -0
- package/dist/components/ObjectNode.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +10 -0
- package/dist/components/OceanBuilder.d.ts +35 -0
- package/dist/components/OceanBuilder.d.ts.map +1 -0
- package/dist/components/OceanBuilder.js +53 -0
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SkySystem.d.ts +27 -6
- package/dist/components/SkySystem.d.ts.map +1 -1
- package/dist/components/SkySystem.js +6 -5
- package/dist/components/SunLight.d.ts.map +1 -1
- package/dist/components/SunLight.js +4 -1
- package/dist/components/TerrainBuilder.d.ts +30 -0
- package/dist/components/TerrainBuilder.d.ts.map +1 -0
- package/dist/components/TerrainBuilder.js +44 -0
- package/dist/components/index.d.ts +17 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +10 -0
- package/dist/components/landscape/LandscapeMesh.d.ts +18 -0
- package/dist/components/landscape/LandscapeMesh.d.ts.map +1 -0
- package/dist/components/landscape/LandscapeMesh.js +278 -0
- package/dist/components/landscape/index.d.ts +3 -0
- package/dist/components/landscape/index.d.ts.map +1 -0
- package/dist/components/landscape/index.js +1 -0
- package/dist/components/landscape/types.d.ts +13 -0
- package/dist/components/landscape/types.d.ts.map +1 -0
- package/dist/components/landscape/types.js +1 -0
- package/dist/components/terrain/Canyon.d.ts +23 -0
- package/dist/components/terrain/Canyon.d.ts.map +1 -0
- package/dist/components/terrain/Canyon.js +29 -0
- package/dist/components/terrain/Dunes.d.ts +22 -0
- package/dist/components/terrain/Dunes.d.ts.map +1 -0
- package/dist/components/terrain/Dunes.js +25 -0
- package/dist/components/terrain/Hills.d.ts +17 -0
- package/dist/components/terrain/Hills.d.ts.map +1 -0
- package/dist/components/terrain/Hills.js +20 -0
- package/dist/components/terrain/Mountains.d.ts +19 -0
- package/dist/components/terrain/Mountains.d.ts.map +1 -0
- package/dist/components/terrain/Mountains.js +22 -0
- package/dist/components/terrain/TerrainSurface.d.ts +22 -0
- package/dist/components/terrain/TerrainSurface.d.ts.map +1 -0
- package/dist/components/terrain/TerrainSurface.js +94 -0
- package/dist/components/terrain/index.d.ts +7 -0
- package/dist/components/terrain/index.d.ts.map +1 -0
- package/dist/components/terrain/index.js +7 -0
- package/dist/components/terrain/types.d.ts +17 -0
- package/dist/components/terrain/types.d.ts.map +1 -0
- package/dist/components/terrain/types.js +1 -0
- package/dist/components/volumetricClouds/Cirrus.d.ts +14 -0
- package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cirrus.js +13 -0
- package/dist/components/volumetricClouds/Cumulonimbus.d.ts +17 -0
- package/dist/components/volumetricClouds/Cumulonimbus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cumulonimbus.js +20 -0
- package/dist/components/volumetricClouds/Cumulus.d.ts +14 -0
- package/dist/components/volumetricClouds/Cumulus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Cumulus.js +17 -0
- package/dist/components/volumetricClouds/Stratocumulus.d.ts +12 -0
- package/dist/components/volumetricClouds/Stratocumulus.d.ts.map +1 -0
- package/dist/components/volumetricClouds/Stratocumulus.js +16 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +42 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -0
- package/dist/components/volumetricClouds/VolumetricCloudsPass.js +298 -0
- package/dist/components/volumetricClouds/index.d.ts +8 -0
- package/dist/components/volumetricClouds/index.d.ts.map +1 -0
- package/dist/components/volumetricClouds/index.js +7 -0
- package/dist/components/volumetricClouds/types.d.ts +17 -0
- package/dist/components/volumetricClouds/types.d.ts.map +1 -0
- package/dist/components/volumetricClouds/types.js +1 -0
- package/dist/components/water/Host.d.ts +12 -0
- package/dist/components/water/Host.d.ts.map +1 -0
- package/dist/components/water/Host.js +12 -0
- package/dist/components/water/Lake.d.ts +18 -0
- package/dist/components/water/Lake.d.ts.map +1 -0
- package/dist/components/water/Lake.js +21 -0
- package/dist/components/water/OceanSurface.d.ts +27 -0
- package/dist/components/water/OceanSurface.d.ts.map +1 -0
- package/dist/components/water/OceanSurface.js +60 -0
- package/dist/components/water/Sea.d.ts +18 -0
- package/dist/components/water/Sea.d.ts.map +1 -0
- package/dist/components/water/Sea.js +17 -0
- package/dist/components/water/Shore.d.ts +16 -0
- package/dist/components/water/Shore.d.ts.map +1 -0
- package/dist/components/water/Shore.js +15 -0
- package/dist/components/water/Storm.d.ts +14 -0
- package/dist/components/water/Storm.d.ts.map +1 -0
- package/dist/components/water/Storm.js +13 -0
- package/dist/components/water/WaterBody.d.ts +31 -0
- package/dist/components/water/WaterBody.d.ts.map +1 -0
- package/dist/components/water/WaterBody.js +76 -0
- package/dist/components/water/index.d.ts +9 -0
- package/dist/components/water/index.d.ts.map +1 -0
- package/dist/components/water/index.js +11 -0
- package/dist/components/water/types.d.ts +20 -0
- package/dist/components/water/types.d.ts.map +1 -0
- package/dist/components/water/types.js +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +9 -0
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +12 -0
- package/dist/hooks/useClipmap.d.ts +71 -0
- package/dist/hooks/useClipmap.d.ts.map +1 -0
- package/dist/hooks/useClipmap.js +81 -0
- package/dist/hooks/useCloudLive.d.ts +29 -0
- package/dist/hooks/useCloudLive.d.ts.map +1 -0
- package/dist/hooks/useCloudLive.js +43 -0
- package/dist/hooks/useCloudNoise.d.ts +28 -0
- package/dist/hooks/useCloudNoise.d.ts.map +1 -0
- package/dist/hooks/useCloudNoise.js +128 -0
- package/dist/hooks/useCloudSun.d.ts +49 -0
- package/dist/hooks/useCloudSun.d.ts.map +1 -0
- package/dist/hooks/useCloudSun.js +84 -0
- package/dist/hooks/useCubeCamera.d.ts +27 -9
- package/dist/hooks/useCubeCamera.d.ts.map +1 -1
- package/dist/hooks/useCubeCamera.js +28 -70
- package/dist/hooks/useEnvironmentProbe.d.ts +32 -0
- package/dist/hooks/useEnvironmentProbe.d.ts.map +1 -0
- package/dist/hooks/useEnvironmentProbe.js +35 -0
- package/dist/hooks/useFrameRenderer.d.ts +15 -0
- package/dist/hooks/useFrameRenderer.d.ts.map +1 -0
- package/dist/hooks/useFrameRenderer.js +23 -0
- package/dist/hooks/useLandscapeLive.d.ts +29 -0
- package/dist/hooks/useLandscapeLive.d.ts.map +1 -0
- package/dist/hooks/useLandscapeLive.js +41 -0
- package/dist/hooks/useLandscapeMaps.d.ts +32 -0
- package/dist/hooks/useLandscapeMaps.d.ts.map +1 -0
- package/dist/hooks/useLandscapeMaps.js +138 -0
- package/dist/hooks/useOceanLive.d.ts +29 -0
- package/dist/hooks/useOceanLive.d.ts.map +1 -0
- package/dist/hooks/useOceanLive.js +38 -0
- package/dist/hooks/useOceanMaterial.d.ts +39 -0
- package/dist/hooks/useOceanMaterial.d.ts.map +1 -0
- package/dist/hooks/useOceanMaterial.js +65 -0
- package/dist/hooks/useTerrainClipmap.d.ts +12 -0
- package/dist/hooks/useTerrainClipmap.d.ts.map +1 -0
- package/dist/hooks/useTerrainClipmap.js +10 -0
- package/dist/hooks/useTerrainLive.d.ts +34 -0
- package/dist/hooks/useTerrainLive.d.ts.map +1 -0
- package/dist/hooks/useTerrainLive.js +45 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/presets/landscapePresets.d.ts +60 -0
- package/dist/presets/landscapePresets.d.ts.map +1 -0
- package/dist/presets/landscapePresets.js +194 -0
- package/dist/presets/landscapeTypes.d.ts +195 -0
- package/dist/presets/landscapeTypes.d.ts.map +1 -0
- package/dist/presets/landscapeTypes.js +315 -0
- package/dist/presets/oceanSurfacePresets.d.ts +33 -0
- package/dist/presets/oceanSurfacePresets.d.ts.map +1 -0
- package/dist/presets/oceanSurfacePresets.js +245 -0
- package/dist/presets/terrainPresets.d.ts +28 -0
- package/dist/presets/terrainPresets.d.ts.map +1 -0
- package/dist/presets/terrainPresets.js +293 -0
- package/dist/presets/volumetricCloudPresets.d.ts +38 -0
- package/dist/presets/volumetricCloudPresets.d.ts.map +1 -0
- package/dist/presets/volumetricCloudPresets.js +480 -0
- package/dist/shaders/clouds/CirrusMaterial.d.ts +84 -0
- package/dist/shaders/clouds/CirrusMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CirrusMaterial.js +167 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.d.ts +20 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudCompositeMaterial.js +82 -0
- package/dist/shaders/clouds/CloudResolveMaterial.d.ts +22 -0
- package/dist/shaders/clouds/CloudResolveMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudResolveMaterial.js +77 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +143 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/CloudVolumeMaterial.js +249 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts +21 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.d.ts.map +1 -0
- package/dist/shaders/clouds/SkyBackgroundMaterial.js +42 -0
- package/dist/shaders/glsl/cloudCore.glsl.d.ts +24 -0
- package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/cloudCore.glsl.js +348 -0
- package/dist/shaders/glsl/landscapeCore.glsl.d.ts +49 -0
- package/dist/shaders/glsl/landscapeCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/landscapeCore.glsl.js +518 -0
- package/dist/shaders/glsl/noiseBake.glsl.d.ts +30 -0
- package/dist/shaders/glsl/noiseBake.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/noiseBake.glsl.js +164 -0
- package/dist/shaders/glsl/oceanCore.glsl.d.ts +52 -0
- package/dist/shaders/glsl/oceanCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/oceanCore.glsl.js +421 -0
- package/dist/shaders/glsl/terrainCore.glsl.d.ts +78 -0
- package/dist/shaders/glsl/terrainCore.glsl.d.ts.map +1 -0
- package/dist/shaders/glsl/terrainCore.glsl.js +625 -0
- package/dist/shaders/landscape/LandscapeMaterial.d.ts +73 -0
- package/dist/shaders/landscape/LandscapeMaterial.d.ts.map +1 -0
- package/dist/shaders/landscape/LandscapeMaterial.js +136 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts +57 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.d.ts.map +1 -0
- package/dist/shaders/ocean/OceanSurfaceMaterial.js +268 -0
- package/dist/shaders/terrain/TerrainMaterial.d.ts +80 -0
- package/dist/shaders/terrain/TerrainMaterial.d.ts.map +1 -0
- package/dist/shaders/terrain/TerrainMaterial.js +291 -0
- package/dist/types/cloudConfigs.d.ts +244 -0
- package/dist/types/cloudConfigs.d.ts.map +1 -0
- package/dist/types/cloudConfigs.js +282 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/landscapeConfigs.d.ts +218 -0
- package/dist/types/landscapeConfigs.d.ts.map +1 -0
- package/dist/types/landscapeConfigs.js +256 -0
- package/dist/types/oceanConfigs.d.ts +290 -0
- package/dist/types/oceanConfigs.d.ts.map +1 -0
- package/dist/types/oceanConfigs.js +380 -0
- package/dist/types/terrainConfigs.d.ts +251 -0
- package/dist/types/terrainConfigs.d.ts.map +1 -0
- package/dist/types/terrainConfigs.js +292 -0
- package/dist/utils/cloudUniforms.d.ts +14 -0
- package/dist/utils/cloudUniforms.d.ts.map +1 -0
- package/dist/utils/cloudUniforms.js +122 -0
- package/dist/utils/frameRenderer.d.ts +33 -0
- package/dist/utils/frameRenderer.d.ts.map +1 -0
- package/dist/utils/frameRenderer.js +24 -0
- package/dist/utils/handleCloudsAnimations.d.ts +16 -0
- package/dist/utils/handleCloudsAnimations.d.ts.map +1 -0
- package/dist/utils/handleCloudsAnimations.js +64 -0
- package/dist/utils/landscapeGeometry.d.ts +61 -0
- package/dist/utils/landscapeGeometry.d.ts.map +1 -0
- package/dist/utils/landscapeGeometry.js +126 -0
- package/dist/utils/landscapeHeight.d.ts +44 -0
- package/dist/utils/landscapeHeight.d.ts.map +1 -0
- package/dist/utils/landscapeHeight.js +65 -0
- package/dist/utils/landscapeTextures.d.ts +94 -0
- package/dist/utils/landscapeTextures.d.ts.map +1 -0
- package/dist/utils/landscapeTextures.js +290 -0
- package/dist/utils/landscapeUniforms.d.ts +20 -0
- package/dist/utils/landscapeUniforms.d.ts.map +1 -0
- package/dist/utils/landscapeUniforms.js +61 -0
- package/dist/utils/oceanUniforms.d.ts +47 -0
- package/dist/utils/oceanUniforms.d.ts.map +1 -0
- package/dist/utils/oceanUniforms.js +216 -0
- package/dist/utils/rtin.d.ts +63 -0
- package/dist/utils/rtin.d.ts.map +1 -0
- package/dist/utils/rtin.js +202 -0
- package/dist/utils/terrainUniforms.d.ts +14 -0
- package/dist/utils/terrainUniforms.d.ts.map +1 -0
- package/dist/utils/terrainUniforms.js +196 -0
- 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');
|