2.1 引言
光线从弯曲的表面反射或折射,因此只聚焦在受光面上的某些区域,于是就产生了刻蚀现象。本文从美学角度出发,不以纯物理的方式计算,使其很容易在大多数图形硬件上实现,效果又十分逼真。
2.2 刻蚀的计算
如果想正确地计算水下的刻蚀,是一个复杂的过程:这涉及到数目庞大的光线及其相互作用。为了模拟这一过程,必须从光源射出光子作为开始,一部分光子碰到海面,或者反射或者折射。对于折射部分,按照Snell折射定律:
用该公式编程很不容易,可以将公式变形为一个更容易编程的形式,例如Foley等人1996年提出的:
光子一旦入水后,即发生折射并继续前进,随着入水的深度增加光强度会衰减,最后一些光子会碰到海底并将其照亮。由于海面的波纹,经过不同路径入水的光线,可能最终照到海底相同的区域,每当这个现象发生时,在刻蚀中的聚集光线就会形成明亮的光斑,类似于透镜的聚光现象。
刻蚀其实可以通过正向或逆向光线跟踪计算。在正向光线跟踪中,要跟踪从光源射出并穿过场景的光线,累计其在不连续地区的贡献。逆向光线跟踪,则以相反的过程工作。它从海底开始,按照与入射光线相反的顺序逆向跟踪光线,计算给定点的所有入射光线综合。但是无论是正向光线跟踪计算,还是逆向跟踪计算,都非常费时,因为只有极小部分的计算对最终结果有实际意义。
2.3 方法
本文采用的方法,是逆向光线跟踪的一个简化。我们只计算到达海底光线的一个子集,该子集就是只计算垂直与地面的光线,因为那些从入水到碰上海底面,传播距离最短的光线,才会最容易形成刻蚀。这种方法计算消耗非常少,尽管物理上不“”正确“”但是非常逼真。
我们的算法如下。从海底开始,绘制海底地面后,使用第二次混合叠加渲染来渲染刻蚀。为了这样做,我们创建一个与水网格尺寸相同的海底网格,并且用刻蚀值对其逐顶点地着色。为了创建光照,采用逆向光线跟踪:从海底网格的每个顶点,垂直地向上发射光线,一直到达正好位于哪个顶点之上的水波点。然后,使用有线差分计算那个点的海面波的法线。有了矢量和法线,使用Sneel公式,创建出从水波射向空气的第二级光线。我们再计算每条光线和垂直线的夹角,愈靠近垂直的方向,那个方向进入大海的光线就越多。
2.4 使用OpenGL实现
使用两个pass,第一次渲染使用一个普通纹理渲染还地面。然后第二个pass通过刻蚀算法,逐一照亮海底网格的顶点。
2.5 使用高级着色语言实现
先前OpenGL实现中,是在CPU上执行的波函数。现在可以采用逐像素法代替逐顶点法,全面的提高了视觉质量。
关于法线的计算,不像前面提到的使用有线差分方法,我们考虑了两种方法,他们都使用波函数的偏导产生法线:
1.在屏幕空间可以渲染过程纹理,当只有小部分像素看的见时,这个方法可以节省渲染时间,但是也意味着当有很多像素看得见时,对每个像素都要做同样多的工作,这些大量的工作显然要减小帧率。
2.在纹理空间中渲染一个分辨率固定的渲染目标贴图,在纹理空间中渲染,能够固定每帧的工作量,这是一个优越性,但是却丢掉了一个好处,不能只渲染看的见的像素了。另外,也很难决定使用多大分辨率对当前场景合适。
我们可以进一步优化运算,一是使用Snell定律对入射光线的折射,二是简单的基于从水面到水底的距离,沿着波法线进行的环境映射。结果,穿过纹理中心的垂直光线是明亮的,而夹角光线则逐渐衰弱。而且,深度越大,环境反射贴图的相对尺寸越小,因为刻蚀会随距离而衰减,所以产生的刻蚀也约细。
核心点总结
- 简化的反向光线追踪
- 光照环境贴图映射