此文信息主要摘自 Adrian Courrèges 同学的 GTA V - Graphics Study 系列 (1, 2, 3),俺昨晚从笔记拷出来时,给 Adrian 同学发了一封邮件,请他允许我 reblog 一下。在得到 Adrian Courrèges 同学的同意后,俺就放上来了。

本文信息提炼后,去掉了绝大部分科普,如有不适,请配合原文的更多细节。

img00

环境渲染

  • 最外层的 cubemap 是每一帧实时生成的,目的是简化后续真实反射的渲染。
  • 这个 cubemap 是一张低精度的 128*128 纹理,每个面 30 左右 drawcall,都是地表天空等较大像素贡献的多边形
  • 全部是静态物体,所以车辆的外壳反射不了其他的车和角色
  • 这个 cubemap 随后被转成了双抛面图 (Dual-Paraboloid Map),投影过程类似球面映射,这样的话相关 PS 开销就从 6 个面降到了 2 个面 (一上一下,各 128*128),由于摄像机一般在车顶斜后方45度,反射的绝大部分时间都只用访问朝上的那个面,如果用 cubemap 就是 3~4 个面。整体来看,图像质量上四个侧面受影响最大,顶面和底面保留最多,由于摄像机看到的车顶通常反射顶面,所以效果损失很小

img01

主渲染流程

img02

  • GTA V 的主渲染流程输出到 5 个 RT 并汇入最终的每个像素,透明物体后期另行处理。五个 RT 分别是 Diffuse/Normal/Specular/Irradiance/(Depth/Stencil),绘制这些主体像素大约花了 1900 drawcall。
    • Diffuse RT 内保存了每个像素所属物体的本身的平坦材质色,一个特例 (看车的引擎盖可以发现) 是部分物体保存了方向光的结果,A 通道下面单独谈
    • Normal RT 里保存了每个像素的法线信息,alpha 通道存了一些植被相关的遮罩信息
    • Specular RT 内 RGB 通道分别保存了高光,光泽度和菲涅尔强度信息
    • Irradiance RT 内在 R 和 G 通道保存了主次光源对每个像素的贡献,B 通道存了自发光信息 (车灯,路灯,霓虹灯什么的) A 通道存了一些角色皮肤和植被的标记
    • Depth/Stencil 共用一个 RT,分开说
      • 深度图保存了每个实际绘制的像素到摄像机的距离,每个像素的实际 Z 值是以对数形式保存的,因为 float 越接近 0 越精确,使用对数 (或倒数) 能有效提高超远距离物体的精度 (降低 Z-Fighting)
      • Stencil 用来标记每个像素上各种不同物件产生的不同信息,便于后期做针对性处理,具体在不同的位段做过标记的像素有:玩家控制角色/玩家控制车辆/NPC/NPC车辆/植被/天空
  • 整个渲染以“Front-to-Back”方式进行,这样可以最大化 early-z 的效果。这样的结果是越往后,单个 drawcall 上被拒掉的像素就越多,越省 PS 时间
  • 主渲染流程完成 (也就是这些G-Buffer合并之后) 后如下图 (无天空,水体及透明物体,但含所有的光照和阴影)

img03

裁剪、 LOD 和 Alpha Stippling

  • (裁剪和LOD) GTA V 内判断一个物体是否渲染,是否以较高的精度渲染,是以单个物件为单位,在一个 compute shader 里完成的
  • 解释一下前面的 Diffuse 的 A 通道做了啥事

img04

这个看起来像棋盘格一样的东西叫 alpha stippling,是用来规律性地有选择地拒掉像素的技术。目的是更平滑的 LOD 切换。传统 LOD 的不同级别之间切换的时候,会有 popping,而 stipple 之后普通物体看起来就会“发虚” (尤其是边缘),这样切换时 popping 会弱化很多

阴影 (CSM) 和其他杂项 (RM/SSAO/SSS)

img05

  • CSM 输出到 4 个 1024*1024 单位,但显存内是一张连续的 1024 * 4096 贴图 (注:这个不错,以前的实践是根据摄像机距离使用不同尺寸的 shadow map,GTA V 的统一尺寸更合理,性能也更好)。
  • 四张贴图由四个不同参数的摄像机由近及远分别生成,越近的地方提供越多的细节;这样的问题是需要画四遍,好在 fov 很小裁剪效率更高,最终算下来 CSM 费了 1000 个 drawcall,这里的 drawcall 很廉价 (因为输入/运算/输出量都很小)
  • shadow map 也经过了与上面类似的抖动处理,这样边缘经过模糊后会更平滑
  • 关于模糊有个小优化:先弄个 1/8 尺寸的贴图做一个轻量级的 blur,以明确哪些地方无阴影/部分阴影/全阴影,然后做全深度的 blur 时就能忽略那些无阴影和全阴影的像素了,可以省掉大量的像素操作
  • (Planar Reflection Map) 这玩意费了 600 drawcall 反着画在一个 240*120 的纹理上,用于大面积水面的反射
  • (SSAO) 半精度,做了一下深度相关的模糊
  • (SSS) 嘴唇处的 3S 效果是很显著的,对比上面两张大图。由于 Stencil Buffer 里一个位段专门存了玩家控制角色的像素贡献,而 Specular Map 内的 a 通道存了皮肤的像素贡献,通过这些信息可以做到在最少的像素上做 3S (对脸部专门处理性价比高一些,因为这是个看脸的世界-_-)

水面,雾效,大气 (体积阴影)

  • 用了两张图:Diffuse Map 里存了水的本色,Opacity Map 在 R 和 G 通道里分别存了水的透明度和那一像素的水深信息 (用于透明度贡献率的计算) 存水深还有个好处是,顺便把 z 值过了确定是否可见 (也就是说 G 通道决定了是否对最终效果有贡献)
  • 最后实际绘制用了先前提到的反射图,折射图和 Bump Map

img06

  • (light-shaft map) 用于把阳光无法直射的区域的亮度压下去,半精度,顺便 blur 了一下让效果更自然
  • 雾效的最重要的作用是掩盖远处低模的细节,数据来源以之前输出的深度图为主
  • 天空 (半球,一个 drawcall) 和云 (环状 mesh,见三国志9) 没有用生成的 (反正硬盘不值钱哇哈哈哈)

透明物体和修修补补

  • 墨镜,挡风玻璃,大灯的灯罩
  • 扬起的尘埃尽可能 instancing 了
  • 这一帧所有透明物体共 11 个 drawcall
  • 利用前面存下来的像素把前面提到的参与 Alpha Stippling 的像素融合一下

后处理, AA 和 Lens Distortion

  • Tone Mapping 使用了 Filmic Tonemapping Operators (神海2)
  • HDR 用了四分之一精度,亮度用一个独立 compute shader 存到一个单像素纹理上
  • 算曝光,然后把较强光滤一把,这时候一般只剩几个离得近的车灯了
  • 然后是先往小再往大的迭代,回到半精度,最后 bloom/gamma 一下

img07

曝光控制很重要,帧与帧之间的 coherence 控制一下,注意, GTA V 的“从暗转亮”比“从亮转暗”要快 (符合真实人眼感受)

  • AA 不说了
  • Lens Distortion 一个简单的 PS 稍微形变一下,让最终成像更有镜头感

界面

左下角的 UI 小地图是预生成的小块 tile,道路和建筑全部是矢量化的,无级缩放不损精度而且省空间

img08

统计 (drawcall: 4155, textures: 1113, render targets: 88)

这儿不说啥了吧,看上面三个数字,嗯。

话题 a : LOD

R星的 LOD 确实令人发指,即使在 PS3 那 256M 内存上也是几百公里随便跑,进了游戏就没有 loading 这回事。高低精度的各种不同版本,满足不同情况下顺畅运行的需要。(当然这是游戏数据量大的原因之一)屏幕上目所能及的星星点点灯光大部分都是可找到出处的真实发光的光源。

img09

每个方块都用了同一张 32*32 贴图(屏幕右下角),一共上万个面吧,分批量按静态/动态的更新频率 batch 一下。运动着的车灯是动态更新的,晚上远距离下只画车灯就够了,跑近了再切整模。

山体都有对应的低精度模型来做基础的 diffuse 贡献,这些低模可能是先自动生成再手动调整的。这些低模还可以用来生成一些次要的像素 (反射什么的)

这些以 GB 为量级的数据不断随着玩家的移动加载/释放,大部分在内存里是压缩状态。

当视角从一个角色切到另一个(相距数公里)时,摄像机有一个拉远再拉近的效果,用动画来避免了瞬间的IO过载。正常开车的移动速度跟这个相比就慢多了,streaming 完全没有问题。而飞行速度就快多了,相应地,由于视线很远,大多细节可以去掉,或用低精度模型代替。

话题 b : 特效

img10

游泳池和海面不同,游泳池只是动一下法线,海水动法线不够,顶点也是实时更新的。反射贴图的精度很低,因为各种特效一盖就看不出来了。

img11

镜子是简化的没啥特效的水,反射质量也就是像素精度是可调的,距离超出范围就会变成黑板。

img12

这个 Anamorphic Lenses 只有迎面过来的强光才会有,跟普通 Lens 一样生成的动态 sprite。

话题 c : 景深

img13

用了 Circle of Confusion 做景深,存了 signed (-1, 1) 符号直接用来表示与焦点的关系。这个 CoC Map 用来与深度配合,获取前景/背景的信息,相邻像素的模糊关系及是否位于焦点上等信息。结果是前景的模糊融入焦点范围 (这一块用一张专门的图分离出来做模糊),背景模糊与焦点分离。

其他的 Heat Haze, God Ray 啥的,不多说了。

最后,感谢 Adrian Courrèges 同学的精彩系列文章。


Gu Lu

Comments
Write a Comment

Tags

随笔   游戏开发   Programming   C/C++   优化   Unity   C++   知乎   游戏设计   比特币   Unity3D   区块链   软件开发   Bitcoin   引擎设计   系统架构   Production   idtech   中国文化   加密货币   项目管理   游戏评论   资源管理   资源流水线   效率   道德经   网络   方法论   模板编程   Blockchain   Lua   Blockchain Computing   Oculus   GDC   渲染   VR   PerfAssist   Unity MemoryProfiler   BCH   经济学   信息过载   行业报告   字体   Productivity   图形   网络编程   Dice   协程   EMC   Premake   测试   中间件   Game Engine   新手引导   区块链游戏   Methodology   CI   命令行解析   goroutine   ndk   Ethereum   nanomsg   自动化   Scripting   摘录   Debugging   同步技术   cppcon   C++模板   DOOM3   技术评估   Unity GC   C++11   学习方法   Surface Pro 3   Engine Evaluation   CRT   文化   笔记   golang   图形编程   多线程   ETH   Bitcoin Cash   cppcon14   Bitcoin SV   Visual Studio   Unity Coroutine   跨语言可变参数列表   团队协作   货币   Deployment   Visual Assist   工程改进   Michael Abrash   exp   开放世界   量子计算   域名   虚拟现实   系统重构   slua   遮挡剔除   完美转发   协作式调度   Modern C++   类型推导   Memory Debugging   个人成长   小故事   BTC   暴雪   产品   历史   错误处理   Unity Profiler   MOD  

知识共享许可协议
本作品由Gu Lu创作,采用知识共享Attribution-NonCommercial-NoDerivatives 4.0 国际许可协议进行许可。