Grand Theft Auto V 图形研究(2)
原文链接 http://www.adriancourreges.com/blog/2015/11/02/gta-v-graphics-study-part-2/
Level of Detail
如果说Rockstar(GTA的开发商)在一个领域优于竞争对手的,那就是接下来要介绍的LOD。洛杉矶的游戏世界随着多边形和细节的多少,有许多不同的版本,即便是在线直播游戏时也不会被加载画面阻塞,使得整个游戏体验更加的身临其境。
Lights 光源
你看到的远处所有的小的光源都是真实的,你可以朝着他们的方向开车,并找到投射光源的灯泡。
Aaron Garbut
上面这段话是 Rockstar North的创始人和艺术总监Aaron Garbut,在PS3版发布不久前宣布的。
他的描述是否准确?让我们思考下面这个夜景:
简直像真的一样:你看到的每个小的光斑都都是用一个使用32x32 texture的正方形渲染出来的。
尽管用 instanced geometry(实例化几何体) 进行了大量的batch,但仍有数万多边形推送到了GPU
而且这些并不只是静态几何体:车辆的前灯也在街道上移动,实时更新。当然,这个距离并不需要渲染所有的车辆模型,只要两个前灯就足以创作视觉。但如果你要接近一些远处的光源,LOD会增加,并最终绘制汽车的整体模型。
Low-Poly Meshes 低多边形网格
我们回到前面讨论的那一帧上,游戏世界里一些相当广阔的部分在一帧中被渲染。例如我们下面讨论的山的渲染:
那么遥远的小山究竟是什么样呢?
原来这个山一点也不小,实际上Vinewood Hill(现实中的好莱坞山)是一个跨越了几平方公里的区域,以及许多的房屋和建筑。
还有伽利略天文台坐落在山顶上,以及 Sisyphus Theater(希腊剧场), Lake Vinewood(好莱坞水库),可以在所有的这些区域里探索或驾车,并由数千的draw-calls 和大量的多边形来绘制。
但在当前这个场景里,这个区域很远,所以用低多边形的版本来渲染:只有1个draw-call和2500多边形。
所有的渲染是用一个读取了多张Diffuse texture的Mesh做到的。即便有一些工具可以把Mesh转化为低多边形的版本,但这个转化并不能完全的自动化处理,即便是Rockstar的3D美术师花费几天时间来手动的微调Mesh,也不会感到惊讶的。
另外一个例子,是城市中“小首尔”区的几条街,也是由一个draw-call来绘制的。
游戏世界的低多边形版,在高效的渲染reflection cubemap这类不需要非常高细节和很高分辨率时非常的有用。游戏里如果只有一个LOD等级的话,实时的渲染环境cubemap因为包含了大量的几何体,会非常昂贵或无法实现。
Asset Streaming 资源流加载
像在GTA中给不同的LOD创建多个版本的游戏世界,是非常庞大和耗时的任务,是一个相当挑战。但是,即便你完成了这些,也仅仅做到了一半:你可以在硬盘上放几个G的model和texture,但如果不能找到方法来高效的加载到内存或GPU显存上就没有任何意义。
GTA V是实时的流加载资源,当你进入地图的另一个区域时,加载/卸载model和texture。真正使人印象深刻的是它实时的实现了并在几个小时内保持稳定。
技术上最大的成就,就是把这一些都压缩到了家用机的内存,并让游戏得以流畅的运行。我们流加载和压缩到内存的越多,就意味着可以比GTA4有更高数量级的细节。
Aaron Garbut
当然,这种流加载系统也有其局限性:例如当你切换角色时,相机要从地图的一个区域跳到另外一个区域,这种情况下,流加载系统会突然的过载,也是完全可以理解的,它需要5秒来整理和赶上进度。但GTA5通过缩小/转化/放大的动画很好的处理了这个过渡,并不会觉得完全像是加载画面。
当你正常驾车时,移动速度足够慢,流加载系统就有足够的带宽来保持更新。开飞机的情况就不一样了:它们对流加载系统来说太快了,这也就是为什么驾驶飞机的速度与现实相比大大的减慢了。飞行时,mesh会以和驾车相比更低等级的LOD来渲染以缓解流加载的压力,但有时仍然会有资源的"突然出现"。
Reflections 反射
因为前面分析的帧没有那么多的水,让我们通过下面场景更接近的看看游泳池和海洋是如何渲染的:
就像之前所见的,这个场景被正常的渲染:生成一张环境Cubemap(下图),主要用来渲染物体对周围的反射效果。
例如,游泳池的梯子的反射就归功于这张cubemap。
注意角色和一些像梯子这样的小的物体对象并没有在这张cubemap里。
在渲染水之前场景看起来像这样:
渲染水面和这个完全不同,它并不包括上面这张cubemap。
Reflection Map 反射贴图
首先,是生成"planar reflection map",这张图分辨率很低,是240x120,处理类似cubemap的生成,但只生成一张buffer并且有梯子和角色出现。
场景被颠倒的渲染,随后做对称采样就可以获得正确的反射了。
Refraction Map 折射贴图
图像的一部分被提取出来:这是水面所在的部分用来创建折射贴图,用来模拟后面的折射效果:光从水面下的射出。
这时加入一些“不透明水”的蓝色(水越深颜色越强),以及caustics(焦散)效果。最终的贴图为原始buffer的一半大小。
Combination 组合
要组合不同的buffer,再绘制一个矩形多边形来表现游泳池的水面。依靠bump texutre,逐pixel的扰动多边形的法线作出一些水运动的错觉。
海面的情况不只是扰动法线,绘制一个每帧更新顶点的整体的Mesh来模拟波浪的运动以替换之前的四边形。
根据每个pixel的法线, pixel shader在不同的点获取reflection和refraction map信息,通过菲涅尔方程计算出坐标。
最后的结果非常不错,加入真实扰动的水面效果非常有说服力。
Mirrors 镜面
镜面的渲染和水面是完全一样的技术,由于镜子只反射光,甚至比水面更简单,不用考虑来自镜面后的折射效果。
不像水面,镜面是完全平坦的,很难隐藏用较低分辨率时问题,texel相当的明显。通过设置给予较高的分辨率可以增加反射的质量。
生成 reflection map需要额外的pass渲染场景而且相当的沉重。当镜面在视口外或者玩家距离很远时(这时看起来像一个黑色的四边形),引擎可以避免执行这个额外的pass。
Spotlights 聚光灯
记得在每帧开始前生成的环境贴图并不包含任何角色和车辆,只有主要的建筑和风景吧?
这种情况下,下图中车的前灯是如何反射在潮湿的路面上的呢?
在帧分析的部分这个效果并不明显,因为那是一个白天的场景,但毕竟所有的G-Buffers 都被合并了,灯光也被顺序的绘制。每个灯泡,光投射到的其他mesh被计算了,也包括像潮湿马路的高光泽度表面上很强的反射高光的效果。
每个光源都要绘制一个mesh,这个mesh类似一个细分的八面体,只是通过修改vertex shader,使它与光晕的形状相匹配。
这个mesh没有texture,目的是为了让光晕内所有接触的Pixel可以调用一个Pixel Shader。Shader会根据Pixel深度,与光源的距离,法线以及specular/glossiness属性,来动态的计算光照。
上面的线框视图就是用来计算路灯光源的影像的Mesh。
这里你可以看到延迟管线(Deferred)和前渲染管线(Forward)相比的一大优势:延迟管线可以在场景中渲染大量的光源,而pixel shaders调用尽可能的少,只涉及了被光源影响到的pixel。而前渲染管线中,你必须要计算所有受光源影响的fragment,即使这个fragment实际并不任何光的影响,或者随后被其他的fragment遮挡。
Well, since you made it this far, you can check-out the final part about post-effects.