在三維地球視覺化領域,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
集合中。