- 作者:老汪软件技巧
- 发表时间:2024-11-28 17:07
- 浏览量:
大家好,我是日拱一卒的攻城师不浪,致力于技术与艺术的融合。这是2024年输出的第45/100篇文章。
前言
很多小伙伴经常会问,Cesium中如何绘制天空盒?
其实Cesium中的天空盒分2种:全景天空盒和近地天空盒。全景天空盒比较好做,因为Cesium直接提供了相关的绘制类:SkyBox,这个如何做我就不罗嗦了,不会的可以参照我的【Cesium开源项目】:/tingyuxuan2…
而近地天空盒的实现Cesium官方并没有给出,那么,今天我们就来看下如何实现一个近地天空盒。
其实不管是全景还是近地,它们的原理基本上都差不多,所以我们在实现近地天空盒时,可以直接借鉴全景天空盒的源代码,然后稍微加以改进即可。
天空盒的原理
立方体贴图(Cube Mapping):天空盒利用立方体贴图技术,将六个2D图像(纹理)映射到一个立方体的六个面上。那么当我们看天空盒的时候,其实就是在这个矩形盒子中的视角。这些图像通常是全景图,可以是天空、星空或其他任何类型的环境背景。
相机视角:在渲染过程中,相机的视角决定了观察天空盒的方向。相机的朝向和位置被用来确定从立方体的哪个面渲染纹理。
纹理坐标:天空盒的每个顶点都被赋予了纹理坐标,这些坐标通常是立方体的角落坐标(-1到1)。当顶点通过顶点着色器处理时,这些坐标会被用来从立方图集中采样正确的纹理。
变换矩阵:为了使天空盒看起来随着相机移动而移动,需要将相机的位置和方向考虑进去。一般需要一个变换矩阵来实现,该矩阵将相机的视角旋转应用到天空盒的顶点上。
深度缓冲:由于天空盒应该始终在场景的最远端,它的深度值通常被设置为最大值,以确保它总是在其他3D对象的后面。
无缝连接:为了使天空盒的六个面之间没有可见的接缝,纹理图像需要正确对齐,并且在立方体的边缘处进行平滑过渡。
近地天空盒的不同
近地的原理其实还好,原理基本与全景相同,唯一不同的是需要根据你的视角高度来决定是否显示天空盒。
当你的视角高度低于某个阈值时,比如200公里,我们就显示一个近地的天空盒;当你飞得更高时,我们就切换回默认的天空盒。这样,近地天空盒就可以提供更加真实的视觉效果,尤其是在低空飞行或者地面观察时。
代码实现
首先找到Skybox的类的实现
Cesium的文档,搜到Skybox后可以直接点进去查看其源码,源码并不多,200来行,把源码搬出来,再来来看看我们要修改哪些地方。
根据视高调整天空盒显示逻辑
这里我定义了小于20万米的时候显示近地天空盒,大于20万的时候显示默认的天空盒;
init(options) {
var height = !options.height ? "200000" : options.height;
var defaultSkyBox = this.viewer.scene.skyBox;
this.viewer.scene.postRender.addEventListener(() => {
var e = this.viewer.camera.position;
//判断当前视高
if (Cesium.Cartographic.fromCartesian(e).height < height) {
// 显示近地天空盒
this.viewer.scene.skyBox = this;
this.viewer.scene.skyAtmosphere.show = false;
} else {
// 显示默认天空盒
this.viewer.scene.skyBox = defaultSkyBox;
this.viewer.scene.skyAtmosphere.show = true;
}
});
}
顶点着色器修改
由于我用的是比较新版本的Cesium,它的shader语法已经切换成Webgl2语法,因此需要对着色器的部分语法进行修改。
const SkyBoxVS = `
#version 300 es
precision highp float;
in vec3 position;
out vec3 v_texCoord;
uniform mat3 u_rotateMatrix;

void main() {
vec3 p = czm_viewRotation * u_rotateMatrix * (czm_temeToPseudoFixed * (czm_entireFrustum.y * position));
gl_Position = czm_projection * vec4(p, 1.0);
v_texCoord = position;
}
`;
其中主要是将v_texCoord的前置定义varying改成输出语法out,因为v_texCoord是需要传递给片元着色器的。
片元着色器的修改
const SkyBoxFS = `
precision highp float;
uniform samplerCube u_cubeMap;
in vec3 v_texCoord;
out vec4 fragColor;
void main() {
vec4 color = texture(u_cubeMap, normalize(v_texCoord));
fragColor = vec4(czm_gammaCorrect(color).rgb, czm_morphTime);
}`;
这里主要是将varying vec3 v_texCoord;改成in vec3 v_texCoord;,因为v_texCoord是从顶点传过来的,因此前置语法要用in。
还有就是新版glsl去除了gl_FragColor,而是变成了输出变量:
out vec4 fragColor;
fragColor = vec4(czm_gammaCorrect(color).rgb, czm_morphTime);
使用
修改的地方也不多,那当我们封装好后,使用起来就非常方便了,跟全景天空盒基本没啥差别,只要你准备好全景图片即可。
import SkyboxGround from '@/classes/SkyboxGround.js'
const skyBoxGround = () => {
new SkyboxGround(__viewer, {
sources: {
positiveX: '/images/skyboxGround/right.jpg',
negativeX: '/images/skyboxGround/left.jpg',
positiveY: '/images/skyboxGround/front.jpg',
negativeY: '/images/skyboxGround/back.jpg',
positiveZ: '/images/skyboxGround/up.jpg',
negativeZ: '/images/skyboxGround/down.jpg'
}
})
}
最后
如果实在找不到近地天空盒的照片,可以私信我领取。
如果想系统学习Cesium,可以了解下我的Cesium系列教程《Cesium从入门到实战》,将Cesium的知识点进行串联,让不了解Cesium的小伙伴拥有一个完整的学习路线,并最终完成一个智慧城市的完整项目,+作者:brown_7778(备注来意)了解教程细节。
有需要进可视化&Webgis交流群可以加我:brown_7778(备注来意),另外也可接项目合作。