# 前言
作为一个三维地球,在场景中来点雨雪效果,貌似可以增加一点真实感。Cesium 官网 Demo 中有天气系统的实例,用的是 Cesium 中的粒子系统做的。效果如下图所示,粒子系统的本质是向场景中添加了很多物体,用 BillBoard 技术展现。这种实现方式有一个麻烦的地方就是当视角变化 (拉近、拉远、平移、旋转) 时,粒子就会变化,甚至会消失,很影响体验。考虑用 shader 的方式直接模拟雨雪效果,恰好发现了 Catzpaw 大神写的模拟雨雪的 shader,果断增添到 Cesium 中。
# 实验效果
# 代码
# 自定义 GLSL 代码
/** | |
* 后期处理控制类 | |
* postProcessController.js | |
*/ | |
const Snow = ` | |
uniform sampler2D colorTexture; //输入的场景渲染照片 | |
varying vec2 v_textureCoordinates; | |
float snow(vec2 uv, float scale) { | |
float time = czm_frameNumber / 60.0; | |
float w = smoothstep(1., 0., -uv.y * (scale / 10.)); | |
if (w < .1) | |
return 0.; | |
uv += time / scale; | |
uv.y += time * 2. / scale; | |
uv.x += sin(uv.y + time * .5) / scale; | |
uv *= scale; | |
vec2 s = floor(uv), f = fract(uv), p; | |
float k = 3., d; | |
p = .5 + .35 * sin(11. * fract(sin((s + p + scale) * mat2(7, 3, 6, 5)) * 5.)) - f; | |
d = length(p); | |
k = min(d, k); | |
k = smoothstep(0., k, sin(f.x + f.y) * 0.01); | |
return k * w; | |
} | |
void main(void) { | |
vec2 resolution = czm_viewport.zw; | |
vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / min(resolution.x, resolution.y); | |
vec3 finalColor = vec3(0); | |
// float c=smoothstep(1.,0.3,clamp(uv.y*.3+.8,0.,.75)); | |
float c = 0.0; | |
c += snow(uv, 30.) * .0; | |
c += snow(uv, 20.) * .0; | |
c += snow(uv, 15.) * .0; | |
c += snow(uv, 10.); | |
c += snow(uv, 8.); | |
c += snow(uv, 6.); | |
c += snow(uv, 5.); | |
finalColor = (vec3(c)); //屏幕上雪的颜色 | |
gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(finalColor, 1), 0.5); //将雪和三维场景融合 | |
} | |
` | |
const Rain = ` | |
uniform sampler2D colorTexture; //输入的场景渲染照片 | |
varying vec2 v_textureCoordinates; | |
float hash(float x) { return fract(sin(x * 133.3) * 13.13); } | |
void main(void) { | |
float time = czm_frameNumber / 60.0; | |
vec2 resolution = czm_viewport.zw; | |
vec2 uv = (gl_FragCoord.xy * 2. - resolution.xy) / min(resolution.x, resolution.y); | |
vec3 c = vec3(.6, .7, .8); | |
float a = -.4; | |
float si = sin(a), co = cos(a); | |
uv *= mat2(co, -si, si, co); | |
uv *= length(uv + vec2(0, 4.9)) * .3 + 1.; | |
float v = 1. - sin(hash(floor(uv.x * 100.)) * 2.); | |
float b = clamp(abs(sin(20. * time * v + uv.y * (5. / (2. + v)))) - .95, 0., 1.) * 20.; | |
c *= v * b; //屏幕上雨的颜色 | |
gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(c, 1), 0.5); //将雨和三维场景融合 | |
} | |
` | |
export function createSnowStage(Cesium) { | |
var snow = new Cesium.PostProcessStage({ | |
name: 'czm_snow', | |
fragmentShader: Snow | |
}); | |
return snow; | |
} | |
export function createRainStage(Cesium) { | |
var rain = new Cesium.PostProcessStage({ | |
name: 'czm_rain', | |
fragmentShader: Rain | |
}); | |
return rain; | |
} |
生成雨和雪的 glsl 代码很神奇,就是单纯的数学计算,意识到学好数学还是很重要的,再次向大神膜拜 ○| ̄|_
# 外部调用
// 开启后期处理 | |
var collection = viewer.scene.postProcessStages; | |
var snow = createSnowStage(Cesium) | |
// var rain = createRainStage(Cesium) | |
// collection.add(rain) | |
collection.add(snow) | |
viewer.scene.skyAtmosphere.hueShift = -0.8; | |
viewer.scene.skyAtmosphere.saturationShift = -0.7; | |
viewer.scene.skyAtmosphere.brightnessShift = -0.33; | |
viewer.scene.fog.density = 0.001; | |
viewer.scene.fog.minimumBrightness = 0.8; |
最终效果就如上图展示的那样~
# 总结
通过 Shader 这种方式模拟雨雪可以不受视点位置的影响,相当于是一个全屏的后处理,当然在效果模拟上还有可以增强的地方。本文描写的步骤可以作为大家在 Cesium 上添加后期处理效果步骤的一个参考~
# 参考链接
Cesium 应用篇 -- 添加雨雪天气