《天涯明月刀》中的雨景渲染
天涯明月刀ol天气效果视频:
http://v.qq.com/page/b/z/f/b0012wadazf.html?start=17
http://v.qq.com/cover/z/zyeg1tfa935xma0.html?vid=z0012ankmbw
天气系统可以带给玩家强烈的游戏带入感,可以使游戏显得更上流(咦?),于是大家纷纷都有了。下雨是天气系统的重要组成部分,大多数游戏是这样表现雨的:
天涯明月刀ol的画面表现定位很高,可以说是倍受风景党们期待的一款游戏。如果我也这样做一个雨……
这种下锥子的感觉是怎么回事,考评不会拿C么?
真实的雨氛围应该是什么样的呢?
l 雨要绵密,你数不清有多少雨丝,但是可以判断出是大雨还是小雨:
l 地面会潮湿,泥泞,反光增强:
l 路面有积水和水花:
l 水面上有涟漪:
l 天空与场景变得灰暗,起雾、能见度降低
l 乌云密布电闪雷鸣
以下就是在QS-Game中,为了营造一个能让玩家沉浸其中的雨景,我做过的工作。
1、 雨丝渲染Raindrops
雨丝是雨景渲染工作中最重要、最复杂也是研究资料最多的一个,通常有两种作法。
1. 最常见的一种是用粒子模拟,效果见图1、图2。他的优点是:每个雨滴实际存在于3D场景中,所以雨的运动轨迹容易控制,随风倾斜,视角变换都很容易实现。缺点是:粒子数量不能太多,否则影响性能,这样就无法做密集的雨。如图2,用了10000个粒子,依然感觉很稀疏。但是又想营造出倾盆大雨的效果,怎么办呢,那就加长每个粒子吧,结果就这样显得很生硬。
2. 另一种方法是在屏幕空间做一个纹理动画,他与粒子雨的优缺点正好颠倒。纹理动画是一个后处理,表现任意降雨强度都是相同的性能开销。但由于他不真正处于3D空间中,雨的倾斜就做不了,在任何视角,雨在屏幕上的运动方向都是相同的。例如在ATI Toy shop demo中使用四层纹理,每层以不同的速度运动,以模仿降雨的纵深感。但他没有解决视角问题,只是通过锁定视角范围,掩饰这个破绽。仔细观察,在看向地面时,降雨方向将近要与地面平行,QS-Game几乎是全视角,显然这种方法也不适合。
为了表现密集、真实感强的雨,我选择用纹理模拟的方法,这就要解决几个问题:雨的方向与视角变换;深度视差;场景遮挡。
最终,我用类似Flight Simulator 2004(微软模拟飞行)里的方法解决视角问题,在3D空间中创建一个椎体包围相机,在椎体上做纹理动画,通过调整椎体的倾斜度表现雨的方向,还可以通过增大椎体倾斜和拉长、加快纹理动画表现出镜头移动加速的效果。
椎体的上下两个顶点解决了向上向下看的问题,改变两个顶点的顶点色,使俯视、仰视时的效果更自然
深度视差,就是近大远小的问题,参考Toy shop demo,使用四层纹理,表现远近,越远的层纹理tilling值越大,运动速度越慢,而且每层的动画方向稍有不同,表现出雨滴运动的不规则、交错感。
屋子里不能下雨,就要解决雨与场景物体的遮挡问题。用雨纹理的一个通道存储雨滴的线性深度值,这样在pixelshader里就能计算出雨滴在3D空间中的虚拟位置,然后类似阴影的作法,用一个正交摄像机顺着雨下落的方向,渲染一个深度图。深度图不用很大,256的精度就够用。在pixelshader里把雨滴的虚拟位置转换到投影摄像机空间中,然后雨滴的深度与深度图中的比较,判断出雨是否被遮挡。
Pixel Shader:
2 、潮湿
简单的,通过增强材质的Gloss、Specular,加深diffuse,可以得到一个不错的潮湿感觉
3、水花渲染Splash
水花是用GPU粒子做的,并在粒子上做一个帧动画。
粒子不可能铺在整个场景中,只需要在相机周围一定范围内出现。但是由于GPU粒子位置计算是使用一个初始属性和当前时间定义的闭合函数,所以粒子系统位置不能一直跟随相机,可以用九宫格方法解决这个问题。如图,创建9个粒子系统环绕相机,当相机走入红点位置,将后面的3个粒子系统移动到前面。
用九宫格的方法在相机周围铺上一层水花粒子,在vertexshader中,利用上面渲染的碰撞深度图,用类似的方法,由深度计算出碰撞点的位置,就是水花应该在的位置,如果超出深度图范围,可以把水花移出场景坐标外。
4、涟漪
涟漪渲染是参考的Game Programming Gems1里的方法,不过书上的渲染方法是在cpu中通过下面的公式计算得到高度,然后变形网格渲染。而我把计算过程放到GPU中,把涟漪起伏渲染成一张高度图,再通过高度图得到法线贴图,最后作用到水面渲染上。
这个是水波高度计算公式,是一个近似简化公式,推导过程略。
c表示波的传播速度,h是点间距,GPU中一个像素对应一个计算点,所以间距定为1,Δt是帧间的时间差。
公式的意义为:计算点在当前帧的高度 = 系数A*(上一帧周围四个点的高度和) + 系数B*计算点上一帧高度 -计算点上上一帧高度
从公式可以看出,计算当前帧某点的高度值,需要前两帧的高度数据,可以使用两张G16R16F RT,渲染一个RT时,采样另一个RT,得到上一帧和上上帧的高度,然后保存当前帧与上一帧的高度到渲染的RT上,作为下一帧的输入纹理。
渲染一帧的步骤
1. 随机渲染一些白点,作为波源
2. 通过上述方法渲染高度图
3. 通过高度图得到法线贴图
4. 法线贴图作用到水面渲染上
可以看到渲染出的涟漪不够圆,有点偏方,如果计算上一帧周围八个点的高度和,可以改善这个问题,但是效率会下降。动态时偏方这个问题不会感觉很明显。
5、积水效果
水平面积水:把上面的涟漪法线作用到材质上,并稍微扰动底色。
垂直面流水:静态图不明显,有水流下石头的效果。同样的使用一张流水法线贴图,作用在表面上,并稍微扰动底色。
关键是shader中如何区分水平面与垂直面
6、雷电
雷电的形状要千变万化,扭曲的要风骚。实现复杂度与效果综合考量,决定使用分型法。
算法的基本思想是:首先给定一根直线,取直线中点位置,在垂直于直线的方向上随机偏移,连接起点与偏移后的中点、偏移后的中点与终点,生成两根直线,在第一根直线的方向上随机位置生成一根枝杈,在这三根直线上重复上面的步骤,不断迭代,直到结果满意为止。
生成的闪电形态如下:
然后每一小段生成一个始终面向摄像机的矩形用于渲染,有一个问题是,当闪电比较粗时,交界处会很明显
可以这样解决:每一段延伸出宽的一半,然后pixel shader中衰减边缘。
闪电效果图:
参考资料:
强烈推荐
https://seblagarde.wordpress.com/2012/12/27/water-drop-2a-dynamic-rain-and-its-effects/
ATI ToyShop demo
微软模拟飞行中的雨雪渲染
http://ofb.net/~niniane/rainsnow/
NVIDIA SDK 10 Lightning
http://developer.download.nvidia.com/SDK/10.5/direct3d/samples.html
Game Programming Gems 1:2.6 Interactive Simulation of Water Surfaces