概述
当空间中包含足够稠密的光散射介质(例如气体分子和气溶胶)时,光源透过遮挡物的缝隙投射的光线打在空气中的尘埃上,产生散射进入到人眼中,这些光线看起来像是一道一道的光柱,被称为体积光。
在图形渲染中,体积光对画面的质感能起到很大的提升作用。体积光的渲染主要有BillBoard贴片、径向模糊和光线追踪 3 种方式。BillBoard贴片是把带有光柱效果的图片叠放在原始场景之上,实现方式不够灵活,换另一个场景就得另一组图片;径向模糊用后处理方式实现,效果不错,性能开销较小;光线追踪效果最好,算法较为复杂,性能消耗相对也较大。
在以上3种方式中,径向模糊在算法复杂度、性能开销、渲染效果等方面的表现相对均衡,同时它是在后处理阶段实现的,正好 Cesium 提供了一套现成的后处理机制,因此我们不用考虑后处理的基本流程,只要提供片元着色器和相应的 uniform 变量即可。本文主要讨论基于径向模糊算法的体积光及其在Cesium上的实现思路。
图1 体积光效果
径向模糊算法
径向模糊算法的核心思想是:在当前片元到光源的路径上选取一系列的采样点,考虑权重和衰减等因素的影响算出采样颜色,采样颜色之和为当前片元最终的颜色。算法逻辑如图2所示,当前片元径向模糊后的颜色 Color 是所有采样颜色之和,屏幕空间中每个片元都按照这个逻辑计算一次。图3是径向模糊前后对比的示例,可以看出是朝一个方向模糊的,这也是径向的含义。表1是径向模糊的GLSL代码片段,虽然用到了循环,但实际使用中并没有感觉性能受到多大影响。
图2 算法逻辑
图3 径向模糊前后对比
/// Our light scattering pass texture
uniform sampler2D UserMapSampler;
/// Indicate where is the light source on the screen (2D position)
uniform vec2 lightPositionOnScreen;
void main()
{
float decay=0.96815;
float exposure=0.2;
float density=0.926;
float weight=0.58767;
/// NUM_SAMPLES will describe the rays quality, you can play with
int NUM_SAMPLES = 100;
vec2 tc = gl_TexCoord[0].xy;
vec2 deltaTexCoord = (tc — lightPositionOnScreen.xy);
deltaTexCoord *= 1.0 / float(NUM_SAMPLES) * density;
float illuminationDecay = 1.0;
vec4 color =texture2D(UserMapSampler, tc.xy)*0.4;
for(int i=0; i < NUM_SAMPLES ; i++)
{
tc -= deltaTexCoord;
vec4 sample = texture2D(UserMapSampler, tc)*0.4;
sample *= illuminationDecay * weight;
color += sample;
illuminationDecay *= decay;
}
gl_FragColor = color;
}
表1 径向模糊 GLSL 代码片段
Cesium中体积光的实现思路
1、在Cesium提供的后处理机制中,我们不用自己去离屏渲染原始场景的纹理,在后处理阶段的片元着色器中可以直接拿到颜色纹理(colorTexture)和纹理坐标(v_textureCoordinates)。
图4 原始场景纹理
2、光源正常渲染,遮挡光源的物体都渲染为黑色,得到一张采样纹理。建议在Cesium中把太阳和大气都作为光源,如果只把太阳作为光源,最后体积光的效果不明显。
图5 光源正常渲染、遮挡物涂黑的纹理
3、把上一步渲染的纹理传给径向模糊的着色器,在这张纹理上采样,逐片元算出径向模糊后的颜色,得到图6所示的径向模糊纹理。
图6 径向模糊纹理
4、把上一步经过径向模糊的纹理和原始纹理叠加就可以看到如图7所示的体积光的效果了。
图7 Cesium中的体积光效果