在三维地球可视化领域,Cesium是一个非常强大的开源JavaScript库,它能够帮助开发者轻松地创建丰富的3D地理应用。本文将介绍如何使用Cesium结合GLSL(OpenGL Shading Language)着色器来实现一个动态的光影效果,为您的地球模型增添更多视觉上的吸引力。
引入Cesium
首先,确保您的项目中已经引入了Cesium库。可以通过npm安装或直接从CDN引入Cesium到您的HTML文件中。
<script src="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
创建基本的Cesium Viewer
接下来,在页面上创建一个Cesium Viewer实例,这是所有后续操作的基础。
var viewer = new Cesium.Viewer('cesiumContainer', {
terrain: Cesium.Terrain.fromWorldTerrain(),
});
编写GLSL着色器代码
为了实现动态光影效果,我们需要编写一个GLSL片段着色器。下面的代码段定义了一个复杂的光照效果,包括随机形状、光环以及太阳光线等元素。
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
in vec2 v_textureCoordinates;
float rnd(vec2 p) {
float f = fract(sin(dot(p, vec2(12.1234, 72.8392))) * 45123.2);
return f;
}
float rnd(float w) {
float f = fract(sin(w) * 1000.);
return f;
}
float regShape(vec2 p, int N) {
float f;
float a = atan(p.x, p.y) + 0.2;
float b = 6.28319 / float(N);
f = smoothstep(0.5, 0.51, cos(floor(0.5 + a / b) * b - a) * length(p.xy));
return f;
}
vec3 circle(vec2 p, float size, float decay, vec3 color, vec3 color2, float dist, vec2 mouse) {
// 控制光晕 效果
float l = length(p + mouse * (dist * 4.)) + size / 2.;
float l2 = length(p + mouse * (dist * 4.)) + size / 3.;
float c = max(0.01 - pow(length(p + mouse * dist), size * 1.4), 0.0) * 50.;
float c1 = max(0.001 - pow(l - 0.3, 1. / 40.) + sin(l * 30.), 0.0) * 3.;
float c2 = max(0.04 / pow(length(p - mouse * dist / 2. + 0.09) * 1., 1.), 0.0) / 20.;
float s = max(0.01 - pow(regShape(p * 5. + mouse * dist * 5. + 0.9, 6), 1.), 0.0) * 5.;
color = 0.5 + 0.5 * sin(color);
color = cos(vec3(0.44, 0.24, 0.2) * 8. + dist * 4.) * 0.5 + 0.5;
vec3 f = c * color;
f += c1 * color;
f += c2 * color;
f += s * color;
return f - 0.01;
}
float sun(vec2 p, vec2 mouse) {
float f;
vec2 sunp = p + mouse;
float sun = 1.0 - length(sunp) * 8.;
return f;
}
void main() {
float iTime = czm_frameNumber / 10000.;
vec4 sceneColor = texture(colorTexture, v_textureCoordinates);
vec2 uv = gl_FragCoord.xy / czm_viewport.zw - 0.5;
uv.x *= czm_viewport.z / czm_viewport.w;
float depth = czm_unpackDepth(texture(czm_globeDepthTexture, v_textureCoordinates));
vec4 sunPos = czm_morphTime == 1.0 ? vec4(czm_sunPositionWC, 1.0) : vec4(czm_sunPositionColumbusView.zxy, 1.0);
vec4 sunPositionEC = czm_view * sunPos;
vec4 sunPositionWC = czm_eyeToWindowCoordinates(sunPositionEC);
sunPos = czm_viewportOrthographic * vec4(sunPositionWC.xy, -sunPositionWC.z, 1.0);
vec2 mm = sunPos.xy ;
mm.x *= czm_viewport.z / czm_viewport.w;
vec3 circColor = vec3(0.9, 0.2, 0.1);
vec3 circColor2 = vec3(0.3, 0.1, 0.9);
vec3 color = mix(vec3(0.3, 0.2, 0.02) / 0.9, vec3(0.2, 0.5, 0.8), uv.y) * 3. - 0.52 * sin(iTime); // iTime 太阳光环控制大小 时间速率
for (float i = 0.; i < 10.; i++) {
color += circle(uv, pow(rnd(i * 2000.) * 1.8, 2.) + 1.41, 0.0, circColor + i, circColor2 + i, rnd(i * 20.) * 3. + 0.2 - 0.5, mm);
}
float a = atan(uv.y - mm.y, uv.x - mm.x);
float l = max(1.0 - length(uv - mm) - 0.84, 0.0);
float bright = 0.1;
color += max(0.1 / pow(length(uv - mm) * 5., 5.), 0.0) * abs(sin(a * 5. + cos(a * 9.))) / 20.;
color += max(0.1 / pow(length(uv - mm) * 10., 1. / 20.), 0.0) + abs(sin(a * 3. + cos(a * 9.))) / 8. * (abs(sin(a * 9.))) / 1.;
color += (max(bright / pow(length(uv - mm) * 4., 1. / 2.), 0.0) * 4.) * vec3(0.2, 0.21, 0.3) * 4.;
color *= exp(1.0 - length(uv - mm)) / 5.;
vec4 sunDepthColor = texture(czm_globeDepthTexture, v_textureCoordinates);
float sunDepth = sunDepthColor.r;
if (depth > sunDepth) {
out_FragColor = sceneColor;
} else {
out_FragColor = vec4(color, 1.0) * vec4(vec4(color, 1.0).aaa, 1.0) + sceneColor * (2.001 - vec4(color, 1.0).a);
}
}
此着色器利用了随机函数rnd
生成随机值,并通过circle
函数模拟光圈效果。sun
函数则用于计算太阳光线的效果。整个着色器的核心在于main
函数,它根据不同的参数组合计算出最终的颜色输出。
添加后处理阶段
为了将上述着色器应用于Cesium场景,我们需要创建一个PostProcessStage
对象,并将其添加到viewer.scene.postProcessStages
集合中。