如何给 2D 游戏添加光照?(转)
在《Full Bore》游戏的早期开发中,我们似乎走进了死胡同。我思考了下我所喜欢的东西,当街机游戏移植到其它设备时会迅速变得无聊,但我仍喜爱干净易读的块状图形。因为我们限制了图形的种类,导致图形与图形之间看上去难以分辨,这样很不好。玩家一次只能看到300个方块,使用基于方块的排版创造一个难忘的世界只能走这么远。但我希望有不一样的体验, 我们需要一些新的东西,清晰化我们的区域,那东西就是光照。
关于本文
这篇文章写的是概念教程 – 我会记录一切《Full Bore》中使用光照的地方,但不会记录具体的实现细节。我的希望是,无论谁读到这里,不管他们对工具是否熟悉(无论是 C ++ 或 Unity 或者其它)都可以用上我所写的部分或者全部。仅供参考的是,Full Bore 都是用C ++开发的,并同时有 Direct3D 和 OpenGL 后端,但一个工具或引擎能让你从容地操作你的渲染管线来适应这种技术。当然,我没有使用像 Unity 那样的更高级的工具,所以请不要在评论里告诉我,我是多么错误!
概览
首先,下面可以快速了解下 Full Bore背后的场景渲染:
图1:漫反射图
图2 :Normal 法线图
图3:Self-Illumination 自发光图
前三个图像(点击可放大)结合光线列表和它们的属性,用来产生最终图像:
图4:Final Lit Image 最终照亮的图像
如果你熟悉图形编程,延迟着色是一个教科书般的标准实现,像你通常看到的现代第一人称射击(FPS)游戏。对于那些刚开始接触图形编程的人,这是比教科书的教学方法更适合的方法,需要为这特别的技术整合一些知识。第一关…
你的工作就是重复(有点这意思)
在3D游戏中,法线贴图可用来添加额外的细节,而模型没有加入额外的代价。在2D游戏中,法线贴图同样可以让我们的场景伪装成是有深度的,这样我们可以用照亮物体的方式显示出额外的表面细节。在Full Bore中几乎每个游戏中的精灵都有一个法线贴图。
图5:高度图,生成的法线图,法线图中的红,绿,蓝通道
它可以通过一个灰度高度贴图生成所有你想要的法线图,可以使用 Photoshop 的 NVIDIA 纹理插件或优秀的 GIMP 插件都计算生成出法线贴图。这些插件都对会高度图进行模糊处理,每个过小的像素通常会被抹去或模糊,但如果你为一个更高的分辨率游戏工作,比如, Escape Goat 2,它从高度图计算得到的法线图很棒,可能是你想要的。
但是,如果你在低分辨率的游戏上工作呢?Full Bore的美术清晰度特别的低,所以很多法线贴图计算后,会进行手工调整了或者完全手工绘制,而为了做到这一点,我们必须得清楚究竟需要开发什么,准确的说,是法线贴图里的编码。
理解法线贴图
在数学上,法线贴图是对3D矢量进行编码,将像素的方向描述成红、绿和蓝三色通道。然而,看看法线贴图,你会发现图像显示了一些人类可理解的表面细节。如果你将法线贴图分成不同的颜色通道可以变得更加明显。从纯粹的视觉观点来说,一个法线贴图是由以下明显的亮光组成,从左边或者右边是红光,从上方或下方是绿光,正面的是蓝光。这些数值还是很重要的,以另一种方式描述:
- 红色通道表示的表面的水平角度。中间值是127,而极端值都朝向左或向右(这里极端的方向由程序员决定;在Full Bore里是255朝右)
- 绿色通道表示的表面的水平角度。中间值是127,而极端值都朝向上或向下(在Full Bore里是255朝上)
- 蓝色通道是有点不同。它某种程度上表示表面是否朝向观察者。255是笔直朝向观察者,而127是指垂直于观察者,而0是指直接远离观察者。
出于明智的考虑,请务必熟悉在图像编辑程序中如何编辑和打开或关闭颜色通道。
构建法线贴图部件
从高度图计算法线图时,高细节和低细节区域的法线贴图用不同的过滤器进行计算时会更好看。
图6:高度图,4次采样,Sobel 5×5
在这种情况下,可通过将高度图分割成多个层,然后在法线过滤器后期处理时将它们结合起来。
图7:Sobel 5×5,4次采样,4次采样,合并
最后可能需要将多个法线贴图用图像编辑程序进行合并。你也可以在平滑的法线贴图加上一些细节噪声,或合并不同的形状。
图8:一张四条边交错的环状法线贴图与柱状法线贴图合并
为了做到这一点,你必须做到以下几点:
- 2份需要合并的法线贴图。
- 在一张图里更换整个蓝色通道,用50%的灰色,另外,用纯白色取代的红色和绿色通道。
- 将红绿层进行“叠加”混合,并用蓝色层作为“正片叠底”
- 根据需要调整
现在你应该能够开始制作自己的法线贴图,所以现在…
你需要渲染不同的物体
好了,你不需要这样直接渲染带法线贴图光照的精灵,这会严重限制你在屏幕上光线的数量。为了解决这个瓶颈,Full Bore采用了延迟着色,该技术多见于3D游戏也很适合我们这里想做的事。在做延迟着色时,渲染分在两个阶段。
首先,你需要运行光线着色器将所有信息写入到一个或多个帧缓存器中,可以使用多个渲染目标和编写良好的着色器(究竟怎样做取决于你用什么库或工具)来有效的完成。在Full Bore中,每一个纹理有相应的法线贴图和亮度贴图,叠加后正好与原来颜色纹理一致,当一个给定的精灵绘制出来后,绘制其它数据到相应的帧缓冲区以便其它纹理来查找就变得很轻松了。那本文上面的三张截图就显示了Full Bore三个帧缓冲的模样。其次,你可以用你的帧缓冲作为纹理绘制你的灯来照亮你的游戏。呵呵,难道我没告诉你吗?
惊喜,有几何感的灯光
灯光着色器必须执行绘制几何图形到屏幕才能看出聚光灯效果。有很多不同的方法去实现这效果,但它们都有个共同点:
- 光的几何图形能代表你想要的灯光的形状,并有合适的UV坐标。 在Full Bore中,我们将UV坐标计算放入放到着色器中,用顶点位置除以屏幕大小即可。 这能减少了对GPU带宽的使用率。
- 每个光线至少需要知道是从哪里发射出去的。 但在几何数据中单独嵌入这些信息是一种浪费,所以你可以带上色彩/亮度/等其它的着色器uniform变量(译注:uniform变量是应用程序传递给shader的变量)或作为你几何数据的一部分一起发送。
- 即使在2D游戏,每个光线在屏幕上应该有个“高度”。这参数是非常有用的,能实现你所期望的:低处的灯光主要照亮物体边缘,高处的灯光能更均匀得照亮物体。
- 即使绘制光线的成本很低,但肯定是你最大的性能杀手。 保证几何形状能最大程度的涵盖被照亮区域,当然,应该剔除哪些不可见的灯光。
以更美观的方式排列灯光是另一篇文章的主题,但可以肯定地说一旦你有了光照系统就会有更多有趣的东西。但这需要更多的法线贴图,和延迟渲染来绘制你的光线。你值得拥有!