文章目录
如果你是一个游戏开发者,在你使用的图形引擎中或多或少都听说过forward rendering和deferred rendering。通常你必须在你的游戏中选择一种。但它们是什么,彼此之间有什么不同,我们又该如何选择呢?
Modern Graphics Pipelines(当前主流的图形管线)
在开始之前,我们必须要知道一点现代可编程图形管线的一些知识。早些时候,我们被显卡的功能限制,不能去改变每个像素的绘制方式,除了发送一些不同的纹理外,不能去修改顶点的数据。现在时代已经改变,我们能够基于显卡的图形管线进行编程。我们能够发送代码到显卡去改变像素的外观(颜色),使用法线纹理(normal maps)改变它们外观使其变的突起,也可以添加反射。
这个代码是以geometry,vertex, fragment shaders的形式存在的,从本质上来说,它们是控制显卡如何去渲染对象。
Forward Rendering(正向渲染/前向渲染)
Forward Rendering 是大多数渲染引擎使用的渲染技术。你给显卡提供几何对象,它将几何对象分解成顶点送入顶点着色器,然后把这些顶点数据插值后分别送入片元/像素着色器,然后在它们被送入屏幕前做最终的渲染处理(模板测试,混合等)。
这是一个线性的流程,每个几何对象分别通过渲染管线一步步的处理下去并产生最终的图像。
Deferred Rendering
延迟渲染,从这个名字来看就意味着渲染是被延迟的,直到所有几何对象都已经通过渲染管线处理后,在最后才应用着色(通过光照来决定最终的像素颜色)并产生最终的图像。
那么为什么要这样来处理呢?
Deferred lighting is a modification of deferred rendering that reduces the size of the G-buffer by using more passes on the scene.
Lighting Performance(光照性能)
标准前向渲染(Forward Rendering)光照的性能消耗也是为什么要另辟蹊径选择其他渲染方式的主要原因。在标准前向渲染(Forward Rendering)管线流程中,每个灯光都会在每个顶点/或片元上执行光照计算,这也就是常说的逐顶点光照和逐片元/像素光照。
如果你在场景中有100个几何对象,并且每个几何对象有1000个顶点,你大约就有100000多变形(非常粗略的计算)。显卡还能够很轻松的处理,但是当这些多边形被发送到片元着色器时, 昂贵的对灯光消耗会使性能急剧下降。开发者可以尝试放置光照计算到顶点着色器减少片元着色器对光照的计算。
不管它是不是此像素上最顶层的片元,还是被遮挡的片元,昂贵的光照计算都会在每个多边形的每个可见片元上执行。如果屏幕的分辨率是1024x768,你有将近800000个像素需要被渲染。你能很轻易的就达到每帧百万级的片元操作。并且大多数的片元还会被剔除(深度测试阶段),那么对于此片元的光照就算就白费了。
如果你要对这样一个达到百万级片元的场景的每一灯光进行渲染,那么你在每一帧将跃升的一个灯光数量x1000000个片元的操作上!想象一下你有一个小镇的街道上面布满点光源!!!!!
计算前向渲染(Forward Rendering)复杂度的公式参见: big O notatio,复杂度公式:O(num_geometry_fragments * num_lights)。你能看到这里的复杂度是和几何对象数量和灯光数量直接相关的。
片元是一个最终可能在屏幕上成为像素的一个”待转像素“,如果在深度测试阶段不被剔除的话,它将在屏幕上成为屏幕的最终像素。现在一些引擎通过其他的方式优化了光照计算,比如:剔除非常远的灯光,组合灯管或使用 Light maps(非常流行的,但是只能是静态的物体)。如果你有大量的灯光需要动态光照的话,我们需要一个更好的解决方案。
Deferred Rendering to the Rescue(前向渲染的救星–延迟渲染)
延迟渲染(Deferred Rendering)是一个减少光照着色对象数量有趣的方法。尤其是对于总的片元对象来说,执行光照的片元数量直接由屏幕的分辨率决定。
延迟渲染(Deferred Rendering)的复杂性,在big O notation中是O(screen_resolution * num_lights)。
现在你能明白了,你有多少的光照数量是由你对灯光数量的使用来决定的。所以你能很高兴的增加你的灯光数量。(这不意味着你可以有无限的几何对象,它们还是要经过管线的及其他处理才能到G-Buffer中。)
The Guts of Deferred Rendering(延迟渲染的细节)
每个几何对象被渲染,但是没有使用光照,使用多目标渲染(multiple render targets),绘制出多个屏幕空间大小的Buffer。深度,法线和颜色分别写入各自的buffers(图像)。然后,这些Buffers和每个灯光像的素颜色进行合成,最后生成最终的图像。
下图分别是:Color,Depth和Normals
下图是最终生成的图片:
选择哪一个呢?
一个最简短的回答是:如果你使用了大量灯光那么你就该使用延迟渲染(Deferred Rendering)了。
但是延迟渲染(Deferred Rendering)也有一些明显的缺点:
- 这个处理需要显卡支持多目标渲染,老的显卡是不支持的,所有不能在上面工作,对于这个是没有变通的方案的,除非强制要求客服换显卡。
- 它需要高带宽的显卡,你要发送大的Buffer数据,老大的显卡可能处理不了。对于这个也没有变通的方案的,除非强制要求客服换显卡。
- 你不能使用透明对象。(除非你联合 使用deferred rendering 和Forward Rendering )。
- 没有抗锯齿。
- 仅有一个类型的材质被允许,除非你使用了被叫做Deferred Lighting的延迟渲染修改。
- 阴影依赖于光照的数量,延迟渲染没有解决任何阴影的问题。
如果你没有大量的灯光或者你想能够在比较老的显卡上允许,你应该选择使用前向渲染(Forward Rendering)并且替换你的灯光使用静态光照贴图。这个结果看起来还是令人吃惊的。
总结
我希望摆脱一些光照的主题。在这里你的选择是解决渲染问题,但是在游戏开始之前就做出正确的选择是非常重要的,因为可以避免日后的修改。