原文链接及内容

效果如下图所示:

  1. 噪声地形:基于分形布朗运动(FBM,Fractal Brownian Motion)和值噪声,生成复杂、自然的地形(高度 0 到 9000 米)。
  2. 正弦地形:基于正弦函数,生成规则的波浪形地形(高度 0 到 4000 米)。

注:

  1. 噪声地形看起来像自然山脉或丘陵,具有不规则但平滑的起伏,模拟真实地形的复杂性。
  2. 在计算机图形学和程序化内容生成中,“噪声”(noise)是一个技术术语,指一种看似随机但具有一定数学规律的数值分布,常用于模拟自然现象(如山脉、云朵、波浪)。常见的噪声算法包括 Perlin 噪声、Simplex 噪声和值噪声(value noise,代码中使用的是这一种)。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
skyBox: false,
shouldAnimate: true,
baseLayerPicker: false,
shadows: true,
terrainProvider: false,
});
viewer.cesiumWidget.creditContainer.style.display = "none";

/**
* 定义地形高度图的宽度和高度。
* 表示高度图为 32x32 的网格,每个格子存储一个高度值,用于生成地形。
*/
const width = 32;
const height = 32;

/**
* 创建一个自定义地形提供器,使用噪声算法生成复杂的地形
*/
const noiseTerrainProvider = new Cesium.CustomHeightmapTerrainProvider({
width: width,
height: height,
// callback函数:根据瓦片坐标 (x, y) 和缩放级别 level 生成高度数据
callback: function (x, y, level) {
// fract函数用于返回 x 的小数部分
function fract(x) {
return x - Math.floor(x);
}
//smoothstep(x):平滑插值函数,返回平滑过渡值(3x² - 2x³),用于平滑噪声边缘
function smoothstep(x) {
return x * x * (3.0 - 2.0 * x);
}
//hash(x, y):伪随机函数,根据 (x, y) 坐标生成 -1 到 +1 的随机值
function hash(x, y) {
const a = 50.0 * fract(x * 0.3183099 + 0.71);
const b = 50.0 * fract(y * 0.3183099 + 0.113);
const v = fract(a * b * (a + b));
return -1.0 + 2.0 * v; // -1 to +1
}
// lerp(x, y, t):线性插值函数,根据权重 t(0 到 1)在 x 和 y 之间插值
function lerp(x, y, t) {
return x * (1.0 - t) + y * t;
}
/**
* 噪声(Value Noise)函数:生成平滑的随机高度场,具体步骤如下:
* 1. 首先,根据输入坐标 (x, y),计算整数网格点 (ix, iy) 和小数偏移 (fx, fy);
* 2. 然后,使用 smoothstep 平滑小数偏移,生成平滑过渡权重 (tx, ty);
* 3. 之后,在四个网格点 (ix, iy)、(ix+1, iy)、(ix, iy+1)、(ix+1, iy+1) 上调用 hash 生成随机值;
* 4. 最后,过双线性插值(lerp)计算最终噪声值,范围为 -1 到 +1
*/
function noise(x, y) {
const ix = Math.floor(x);
const iy = Math.floor(y);
const fx = fract(x);
const fy = fract(y);
const tx = smoothstep(fx);
const ty = smoothstep(fy);
const v00 = hash(ix, iy);
const v10 = hash(ix + 1, iy);
const v01 = hash(ix, iy + 1);
const v11 = hash(ix + 1, iy + 1);
const v = lerp(lerp(v00, v10, tx), lerp(v01, v11, tx), ty);
return v; // -1 to +1
}

/**
* 分形布朗运动(Fractal Brownian Motion, FBM):累积多重自相似噪声。
* 通过多层噪声叠加(分形布朗运动)生成更自然的地形。
* - 叠加六层噪声,每层使用不同的频率(通过坐标缩放,如 x * 1.0、x * 0.4)和振幅(0.5、0.25、...)
* - 每层振幅递减(每次减半),高频层添加细微细节,低频层定义整体形状
* - 输出范围仍为 -1 到 +1,模拟自然地形的复杂性
*/
function fbm(x, y) {
let v = 0.5 * noise(x * 1.0, y * 1.0);
v += 0.25 * noise(x * 0.4, y * 2.8);
v += 0.125 * noise(x * -2.72, y * 4.96);
v += 0.0625 * noise(x * -10.3, y * 4.67);
v += 0.03125 * noise(x * -22.09, y * -4.89);
v += 0.015625 * noise(x * -29.48, y * -34.33);
// v += 0.0078125 * noise(x * -5.97, y * -90.31);
// v += 0.00390625 * noise(x * 98.83, y * -151.66);
return v; // -1 to +1
}
/**
* 地形噪声调整
* - v * 0.5 + 0.5:将 -1 到 +1 的噪声值映射到 0 到 1
* - Math.pow(..., 2.0):平方操作使高度分布更“尖锐”,突出山峰和低谷
* - 输出范围仍为 0 到 1
*/
function terrainNoise(x, y) {
let v = fbm(x, y);
v = Math.pow(v * 0.5 + 0.5, 2.0);
return v;
}

/**
* 生成 32x32 高度图数据,返回给地形提供器
* - buffer:Float32Array 数组,存储 32x32 个高度值
* - u 和 v:归一化坐标
* - terrainNoise(u * 1750 - 10, v * 1500):调用噪声函数,缩放坐标(u * 1750、v * 1500)以控制地形特征大小,偏移 -10 调整地形图案
* - heightValue = 9000 * ...:将噪声值(0 到 1)缩放为 0 到 9000 米的高度
* - buffer[index]:将高度值存储到对应索引
*/
const buffer = new Float32Array(width * height);

for (let yy = 0; yy < height; yy++) {
for (let xx = 0; xx < width; xx++) {
const u = (x + xx / (width - 1)) / Math.pow(2, level);
const v = (y + yy / (height - 1)) / Math.pow(2, level);

const heightValue = 9000 * terrainNoise(u * 1750 - 10, v * 1500);

const index = yy * width + xx;
buffer[index] = heightValue;
}
}

return buffer;
},
});

/**
* 创建一个自定义地形提供器,使用正弦函数生成波浪形地形
* - 结构:与 noiseTerrainProvider 类似,生成 32x32 高度图
* - 高度计算:与 noiseTerrainProvider 类似,生成 32x32 高度图
* 1. Math.sin(8000 * v):使用正弦函数生成周期性高度,8000 * v 控制波浪频率
* 2. * 0.5 + 0.5:将正弦值(-1 到 +1)映射到 0 到 1
* 3. 4000 * ...:缩放为 0 到 4000 米的高度
* - 最终效果:生成沿 v 方向(南北)的规则波浪地形
*
*/
const sineTerrainProvider = new Cesium.CustomHeightmapTerrainProvider({
width: width,
height: height,
callback: function (x, y, level) {
const buffer = new Float32Array(width * height);

for (let yy = 0; yy < height; yy++) {
for (let xx = 0; xx < width; xx++) {
const u = (x + xx / (width - 1)) / Math.pow(2, level);
const v = (y + yy / (height - 1)) / Math.pow(2, level);

const heightValue = 4000 * (Math.sin(8000 * v) * 0.5 + 0.5);

const index = yy * width + xx;
buffer[index] = heightValue;
}
}

return buffer;
},
});

Sandcastle.addDefaultToolbarMenu([
{
text: "噪声地形",
onselect: function () {
viewer.terrainProvider = noiseTerrainProvider;
},
},
{
text: "正弦波地形",
onselect: function () {
viewer.terrainProvider = sineTerrainProvider;
},
},
]);

// 设置相机视角
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
339907.1874329616,
5654554.279066735,
2936259.008266917
),
orientation: new Cesium.HeadingPitchRoll(
5.473742192009368,//约 313.7°,北偏西
-0.2225518333236931,//约 -12.7°,略向下
6.28274245960864//约 360°,几乎水平
),
});