原文链接及内容

示例介绍:加载了Cesium Ion提供的gltf模型数据,支持基于环境贴图的图像基照明(IBL)、程序化天空光照、仅方向光三种光照模式,通过工具栏切换模型和光照模式来查看渲染后的效果,场景禁用动画和时间轴,启用帧率显示并检查是否支持环境贴图。

注:

  1. IBL(Image-Based Lighting,即基于图像的照明)是一种利用环境贴图(通常是高动态范围图像)来模拟场景光照的技术。它通过将环境光照信息存储在图像中,并在渲染时根据场景几何体的表面法线来采样该图像,从而为场景提供更加真实的光照效果。
  2. PBR(Physically based rendering,即基于物理的渲染):它是计算机图形学中的着色方法,目的在于模拟光线的物理行为及其与材质的交互作用,从而实现逼真的视觉效果。PBR采用基于物理精确公式的算法来重现真实世界中的材质,从而生成一致性的逼真环境。
  3. 在 CesiumJS 中,环境光照通常通过以下两种预计算资源实现:用法见下面代码
    • 球谐系数(spherical harmonic coefficients):用于漫反射光照的低频分量,描述光照的分布。
    • 环境贴图(specular environment maps):用于镜面反射的高频分量。
  4. 各向异性(英语:Anisotropy),或称异向性、非均向性,与各向同性相反,指物体的全部或部分物理、化学等性质随方向的不同而有所变化的特性,例如石墨单晶的电导率在不同方向的差异可达数千倍,又如天文学上,宇宙微波背景辐射亦拥有些微的非均向性。许多的物理量都具有非均向性,如弹性模量、电导率、在酸中的溶解速度等。
  5. “Clear Coat” 翻译为“清漆”,常用于描述透明涂层效果。
  6. “Specular” 翻译为“镜面反射”,指高光反射效果。

运行效果如下图所示:

示例代码如下:

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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,

useBrowserRecommendedResolution: false,//使用自定义分辨率,优化渲染。
});

const { scene, camera, clock } = viewer;

// 检查浏览器是否支持镜面环境贴图,该项特性用于 IBL(Image-Based Lighting,即基于图像的照明)。
if (!scene.specularEnvironmentMapsSupported) {
window.alert("此浏览器不支持镜面环境贴图(specular environment maps).");
}

const height = 20.0;
const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0);
const origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, hpr);

scene.light = new Cesium.DirectionalLight({
direction: new Cesium.Cartesian3(
0.2454278300540191,
0.8842635425193919,
0.39729481195458805
),
intensity: 0.0,//设置方向光,初始强度为 0(默认禁用,依赖 IBL)。
});

/**
* 此环境贴图使用 Khronos 的 glTF IBL 取样器进行处理。要处理您自己的环境贴图:
* 1 - 下载并构建 Khronos glTF IBL Sampler(https://github.com/KhronosGroup/glTF-IBL-Sampler)。
* 2 - 运行 `cli -inputPath /path/to/image.hdr -outCubeMap /path/to/output.ktx2`。
* 运行 `cli -h` 查看所有选项。
*/
const environmentMapURL =
"https://cesium.com/public/SandcastleSampleData/kiara_6_afternoon_2k_ibl.ktx2";

/**
* 要生成以下球谐系数,请使用谷歌的 Filament 项目:
* 1 - 下载 Filament 发布版本(https://github.com/google/filament/releases)。
* 2 - 运行`cmgen --no-mirror --type=ktx --deploy=/path/to/output /path/to/image.hdr`命令。
* 该工具也支持其他格式,运行`cmgen --help`可查看所有选项。
* 3 - 获取生成的系数并按如下方式加载到 CesiumJS 中。
*/
const L00 = new Cesium.Cartesian3(
1.234897375106812,
1.221635103225708,
1.273374080657959
);
const L1_1 = new Cesium.Cartesian3(
1.136140108108521,
1.171419978141785,
1.287894368171692
);
const L10 = new Cesium.Cartesian3(
1.245410919189453,
1.245791077613831,
1.283067107200623
);
const L11 = new Cesium.Cartesian3(
1.107124328613281,
1.112697005271912,
1.153419137001038
);
const L2_2 = new Cesium.Cartesian3(
1.08641505241394,
1.079904079437256,
1.10212504863739
);
const L2_1 = new Cesium.Cartesian3(
1.190043210983276,
1.186099290847778,
1.214627981185913
);
const L20 = new Cesium.Cartesian3(
0.017783647403121,
0.020140396431088,
0.025317270308733
);
const L21 = new Cesium.Cartesian3(
1.087014317512512,
1.084779262542725,
1.111417651176453
);
const L22 = new Cesium.Cartesian3(
-0.052426788955927,
-0.048315055668354,
-0.041973855346441
);
// coefficients:9 个球谐系数(L00 到 L22),由 Google Filament 的 cmgen 工具生成,用于漫反射光照。
const coefficients = [L00, L1_1, L10, L11, L2_2, L2_1, L20, L21, L22];

const imageBasedLighting = new Cesium.ImageBasedLighting({
sphericalHarmonicCoefficients: coefficients,//定义漫反射光照的低频分量
specularEnvironmentMaps: environmentMapURL,//定义镜面反射的高频分量
});

const lightingOptions = [
{
text: "环境贴图光照",
onselect: () => {
// 启用 IBL(球谐系数和环境贴图),禁用方向光
imageBasedLighting.sphericalHarmonicCoefficients = coefficients;
imageBasedLighting.specularEnvironmentMaps = environmentMapURL;
imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ONE;
scene.light.intensity = 0.0;
},
},
{
text: "程序化天空光照",
onselect: () => {
// 禁用 IBL,使用方向光(强度 2.0)模拟天空
imageBasedLighting.sphericalHarmonicCoefficients = undefined;
imageBasedLighting.specularEnvironmentMaps = undefined;
imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ONE;
scene.light.intensity = 2.0;
},
},
{
text: "仅方向光",
onselect: () => {
// 禁用 IBL,仅使用方向光(强度 1.0)
imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ZERO;
scene.light.intensity = 1.0;
},
},
];
Sandcastle.addToolbarMenu(lightingOptions, "lightingToolbar");

// 模型切换,默认加载第一个模型
let model;
const modelOptions = [
{
text: "涂有清漆的柳条编织制品",
onselect: () => loadModel(2584329),
},
{
text: "损坏的头盔",
onselect: () => loadModel(2681021),
},
{
text: "水瓶",
onselect: () => loadModel(2654597),
},
{
text: "古董相机",
onselect: () => loadModel(2681022),
},
{
text: "玩具车",
onselect: () => loadModel(2584331),
},
{
text: "煤火盆",
onselect: () => loadModel(2584330),
},
{
text: "谷仓灯",
onselect: () => loadModel(2583726),
},
{
text: "鸭子",
onselect: () => loadModel(2681027),
},
{
text: "环境测试",
onselect: () => loadModel(2681028),
},
{
text: "镜面球",
onselect: () => loadModel(2674524),
},
{
text: "金属-粗糙度球体",
onselect: () => loadModel(2635364),
},
{
text: "镜面反射测试",
onselect: () => loadModel(2572779),
},
{
text: "各向异性(Anisotropy)强度测试",
onselect: () => loadModel(2583526),
},
{
text: "各向异性(Anisotropy)圆盘测试",
onselect: () => loadModel(2583858),
},
{
text: "各向异性(Anisotropy)旋转测试",
onselect: () => loadModel(2583859),
},
{
text: "清漆测试",
onselect: () => loadModel(2584326),
},
{
text: "清漆汽车涂料",
onselect: () => loadModel(2584328),
},
];
Sandcastle.addToolbarMenu(modelOptions, "modelsToolbar");
loadModel(2584329);

/**
* 模型加载方法
*/
async function loadModel(ionAssetId) {
try {
const resource = await Cesium.IonResource.fromAssetId(ionAssetId);
model = await Cesium.Model.fromGltfAsync({
url: resource,
modelMatrix: modelMatrix,
imageBasedLighting,
});

scene.primitives.removeAll();
scene.primitives.add(model);

model.readyEvent.addEventListener(() => {
zoomToModel(model);
});
} catch (error) {
window.alert(`模型加载出错: ${error}`);
}
}

// 调整相机视角,聚焦模型边界球,优化显示
function zoomToModel(model) {
const controller = scene.screenSpaceCameraController;
controller.minimumZoomDistance = camera.frustum.near;
controller._minimumRotateRate = 1.0;//设置最小旋转速率

let { radius } = model.boundingSphere;
/**
* 如果边界球半径小于 10 米,缩放模型至 10 米(调整 modelMatrix)。
* 原因:ScreenSpaceCameraController 不能很好地处理小型模型
*/
if (radius < 10.0) {
const scale = 10.0 / radius;
Cesium.Matrix4.multiplyByUniformScale(
model.modelMatrix,
scale,
model.modelMatrix
);
radius *= scale;
}

const heading = Cesium.Math.toRadians(270.0);
const pitch = 0.0;
camera.lookAt(
model.boundingSphere.center,
new Cesium.HeadingPitchRange(heading, pitch, radius * 2.0)
);
}