雾
通过线框绘制模式可以看出“雾”是由几个缓慢移动的自旋透明片构成(始终绑定在摄像机上),很寻常的做法。去掉纹理过滤后会更容易看出来。
云
云实际实际上是Billboard片,出现时有缩放和透明度变化做过渡。
但是这样做只能显示云的侧面贴图,无法处理云在头顶的情况。所以游戏里视角通常是不能朝上看的。
但在第一人称视角时必须允许朝上看,所以它做了以下处理:
- 所有Billboard片的方向并不是固定朝上,而是朝向头顶中心点。朝上看的时候,所有云都指向天空中心,这样就不会出现视野旋转而云不转的情况。
- 但云在接近天空中心的时候朝向不对的问题依然不能解决,而且通过中心的时候还会旋转180度。所以它选择让云接近中心时执行消隐逻辑,任何时候头顶区域注定没有云存在(即使是暴雨天气)。
光
众所周知,点光源的渲染成本远高于平行光。
风之杖的所有点光源都能照亮场景,并体现出点光源的球形范围特征,更重要的是,它并没有灯光数量上限(第二张图两个灯光区域叠加了)。
虽然点光源说起来也就是逐像素计算和灯光的距离,算一次向量自点乘,但在场景光源较多的时候,剔除掉范围外的光源也需要很多计算——而且实际上是做不到的,因为塞尔达每个Renderer都很大,只有不同Renderer才能选择不同的光源组,然后又不可能用延迟渲染或者Tile-base按屏幕范围拆分。
风之杖用的是一个很hack的做法。它的每个光源都是一个多边形的球体。
然后设成Cull Front(正面剔除),以及ZTest Creater(被遮挡时才显示),就成了这样:
所以它的光照投影才不是个正圆,而是个微妙的Low Poly风格多边形。这并不是故意而为之,单纯是不想给这个光照球太多顶点。这样做并不用专门剔除光照,也不会有任何额外绘制,但表现力就到此为止了,最多也就是用多个不同大小的光照球叠加来制照一些“光照边缘”,因为在光照球的shader里其实是不知道墙面和球的交点在哪的。
3个不同大小的光照球叠加
但是如果有不透明物体的深度缓存,就能在屏幕上求出其交点,换算回世界坐标并和光照中心求距离,并算出正确的点光源光照来。而这其实就是延迟渲染中对光照部分的处理,所以现在看也不完全是个hack的技术了。
此外,人物的光照和投影为求效率也使用的是平行光。对于多光源的处理,它采取的是接近后“捕获”的做法,在公共区域用的则是最后一个接触的光源,在光源切换的时候是直接对两个光源的位置做插值,以避免切换时的突变。(请仔细观察人物投影方向和光照方向变化的瞬间)
影
罐子和金币等物体的影子采用了常见的透明圆片的设计(角度由下方碰撞箱的法线决定),所以可以看到这个明显的显示错误:
但是从上面看的时候,超出平台的阴影却能被隐藏。
由下图隐藏了一部分的阴影可以判定,这个阴影实际上是通过Offest将自身的深度检测向后位移了一段距离,然后设定为ZTest Greater(被遮挡)才允许显示。这是个相当“实用”的贴花技巧。
至于动态阴影部分,
个人认为是每个人物单独生成ShadowMap,然后从脚底附近的简化Mesh碰撞箱筛选三角形动态创建Receiver(依据是与人的距离,法线与光照方向的夹角,所以从崖上往下的阴影无法在悬崖上显示出来)。
因为是每个人物单独生成ShadowMap,每张贴图都可以比较小,内存占用较小。但就算只渲染一个人,这张贴图依然精度极低,锯齿不明显是对边缘半透部分step的结果。
最终锯齿部分近似为斜线
这个游戏还有个特殊的设计:一旦人物悬空,或者在梯子和挂边的时候,阴影就会固定到人物正下方。个人觉得这是为了表示人物的落点位置,单纯是为了游戏性考虑。但这样的设计确实也方便了Receiver的筛选,远处的物体将永远不可能被筛选到,而且也避免了ShadowMap精度低,拉长后过于明显的纹理走样问题。
雨
相比大片纹理产生的overdraw,大量粒子生成的多边形更加合算。雨实际上是由大量狭窄雨点三角面渲染而成的。
但由于雨点的下落速度一致,并不需要每个雨点单独作为粒子,将十几个雨点绑定成一个整体,随机水平位置生成向下位移即可。
除了雨点本身外,还需要生成落在地面溅起的水花。用物理检测实现是不可能的。通过反复观察水花生成的“错误”,风之杖的水花应该只是简单地在低于视角的范围随机生成(高于视角看到的是Mesh背面,即使生成了也看不到),但是用的是ZTest Greater,也就是只有当水花被建筑遮挡的时候才会显示。这样水花就不会出现在空中。
但是当地面高低差较大的时候,由于水花范围没有那么广,无法落在建筑下方,就无法显示出来。
此外,这也会错误地导致墙面上出现水花,但因为墙壁的水花和视线几乎平行,是一个极扁的圆,错误生成了也不是很明显。
气流扭曲
风之杖中广泛使用了这个效果。做法很常规:在绘制完其他全部物体后,Grab Pass,最后绘制扭曲部分(不用Grab Pass直接取上一帧图像会采样扭曲效果本身,导致越偏越远)
扭曲效果的本体是一个普通的半透圆片贴图粒子,除了透明部分外,色彩部分直接用的Grab Pass的贴图再加一个噪波贴图位移产生扭曲。并没有什么可说的。
但是在一些特殊情况下,“全屏”+“高速”,它则用了一个更近似的方案
GrabPass还是依旧,但这个效果并没有用噪波纹理,甚至没有用透明纹理,而是疯狂甩出了大量高速移动的带Grab贴图的不透明粒子。
偏移摄像机后
取一帧查看
它只是稍微偏移了下采样的坐标,然后靠不透明片随机叠加,单帧的效果其实有一定破绽,但在连续画面中是看不出来的。而因为不透明物体重叠会正常pixel kill,性能不差。
非要这样做而不是用正常的后处理,应该主要还是为了体现出气流运动的方向,用噪波的效果未必比这个好。运算量也确实要少一些,就像上图的情况,其实有一部分屏幕根本没绘制,而后处理至少要绘制一屏。省掉的噪波纹理那方面就更不用说了。
岩浆
这个岩浆效果的特点是:有实际高度变化,且足够随机(并且符合岩浆的特征)
有经验的人很快能看出,岩浆的颜色=水面高度(取世界坐标Z作为依据lerp),不需要纹理。剩下的问题就是如何生成这样的一个岩浆高度场。
正弦波叠出来的都比较接近波浪,而岩浆池的波形特点则是“沸腾”,简而言之是个混沌系统,这是简单的周期波模拟不出来的。虽然FFT理论上可能能模拟出来(但这就扯远了)
实际上是这样的:
看不明白就再放一个比较“稀疏”的例子
说白了,还是随机“粒子”。
每个粒子是个两层的8边形,中间凸起。
随机位置生成粒子后,从水平面下凸起来,然后再沉回去,作一个周期,然后移动到下一个随机地点再次出现。
粒子依然是不透明物体以减少OverDraw,因为颜色是由高度决定的并不需要半透过度,产生和消失都在“水面”以下,也不需要半透渐隐。
叠加得越多,效果越自然。
顶点数看上去很夸张,但也只是看上去而已(线段长,交叉多)。实际上一个粒子也就16个顶点,而一个湖有50个粒子效果就已经不错了,随便一个水面需要的顶点数其实很轻松就能超过它。
效率问题反而在OverDraw上,虽然是不透明物体,依然还是有概率重复计算的。按Z排序后能缓解,但边界处依然存在。
不过基本没啥问题的,毕竟风之杖只是个NGC游戏,这个能跑还有啥不能跑。
很容易联想到,是否普通的水体,或者海是否也能用类似的方法实现……
理论上是可以的,只是需要处理法线连续,所以必须先把高度场渲染到纹理处理,然后再映射到网格上。
神海4用到的Wave Particles听说就是类似的技术。
水体
水的部分内容较多,分成多个小节描述
(一)边缘
水是透明的,人对水的感知更多依附于水与其他物体的交界面。
这个游戏中大部分水都是一张纯蓝色的贴图,然后在边缘处单独拉一个Renderer,使用特殊材质,拉好UV。
使用两张贴图,4次采样。同种贴图UV展开方向是相反的,相加后saturate,这样出来的纹理看上去并没有那么重复。
白沫
深色倒影
(二)海浪拍岸
这里取了个巧,任何位置的拍岸节奏都是一样,只有UV x轴的步进速度不同。
海岸分3层,由下至上是:
1.波浪退去时潮湿的部分:
UV并不是位移,而是直接贴边对y轴缩放,y轴像素拉伸产生的模糊模拟了湿边的扩散。
2.有实体的海浪,在图示范围正弦摆动,一个完整周期要摆动两次。
这个波还有个不易察觉的细节:
接近岸边倒极限的时候会显示一个水面纹理,二次采样,相反方向运动
使用的纹理
除了提供细节外,还提供了一点软边缘的效果:接近岸边的部分变亮了,更像沙滩的颜色。
3.从深处来的浪花:看着像海浪,其实只有白沫,看着同时存在两个波,其实是通过repeat方式复制出来的。
这个浪花一直保持匀速推进。
整个设计巧妙的地方在于,两个波有一段时间是重合的。而第二种波的频率比第三种大,它推到最内侧之后,还会时间回来,再去接下一个波。重合期间由于一个是匀速,一个是正弦波,所以会错开一点,使得白沫的变化更多。
正因为这个波只有白沫没有蓝色的海水部分,才能和下层的白沫重合在一起。
不过回退的时候处理不好就会出现这个状况:
波2回退的时候,波3还没有完全消失
波3的覆盖范围需要比波2稍短一些。波3的白沫需要在波2回退之前滚动出网格外。
(三)海平面交界线
这是不太容易注意到但是非常重要的细节。海水靠近地平面的地方应该变亮,并逐渐过渡到接近天空的颜色,也就是一种类似菲涅尔的现象。
我们肯定不愿意在frag上正常算一次viewDir点乘法线。但这整个海只是一个Quad,也没法交给顶点去算,那怎么才能让远处的颜色发生变化呢?
用远景雾确实可以,但这还是要算。
调整镜头后,我们可以俯瞰整个海的Quad的形状。可以看到这个Quad并不是纯色,并且其图像在任何方向观察都一样,所以它很可能是贴了个这样的贴图:
放大到足够大,以接近水平的角度观察,视觉上就产生了这样的效果。
(四)波浪
在大陆附近,海面是由一个Quad组成的,这多半是为了性能。为了表现海浪,风之杖就搞了个Billbroad粒子糊弄事。
一眼就能看出来简单实现方法,却不得不说效果还成。
但是一旦进入深海,大陆的资源被释放掉后,就会换成一个正规的海浪模拟。
没有用尖波(Gerstner),海浪也不大,应该就是两个不同频率的正弦波的叠加,在顶点上直接计算。
海浪模拟为了节约计算成本,按说会做LOD,不过移动摄像机后发现LOD层级是一样的,只是到最远处变回了最初的Quad海面。
为了方便视域裁剪按说要对海面分块,规则是近处分块密,远处分块稀疏,这样才能在满足裁剪的情况下减少DrawCall。
不过它LOD都没做,估计就是最大粒度按扇形随便分了下下吧。
另外这游戏整个海面是跟着主角一起移动的(意外地破绽不大),不需要动态创建销毁,其实很容易做分块。
进入深海后海面也有了纹理
两次采样,第二次UV翻倍,而且染成黑色纹理。同时还要做一次随机扰波增加波纹细节。
越远纹理则越淡。
(五)船只尾迹
其实就是个随时间变宽的MeshTail,但是带了一个偏移每一段UV的正弦摆动效果(两个不同频率的正弦波叠加)
贴图:
作者:flashyiyi
专栏地址:https://zhuanlan.zhihu.com/p/34960962