【原】实时渲染中常用的几种Rendering Path

本文转载请注明出处 —— polobymulberry-博客园

本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简单,如有错误,请大家指正。原文pdf:请点击此处下载

1. rendering path的技术基础


在介绍各种光照渲染方式之前,首先必须介绍一下现代的图形渲染管线。这是下面提到的几种Rendering Path的技术基础。

目前主流的游戏和图形渲染引擎,包括底层的API(如DirectX和OpenGL)都开始支持现代的图形渲染管线。现代的渲染管线也称为可编程管线(Programmable Pipeline),简单点说就是将以前固定管线写死的部分(比如顶点的处理,像素颜色的处理等等)变成在GPU上可以进行用户自定义编程的部分,好处就是用户可以自由发挥的空间增大,缺点就是必须用户自己实现很多功能。

下面简单介绍下可编程管线的流程。以OpenGL绘制一个三角形举例。首先用户指定三个顶点传给Vertex Shader。然后用户可以选择是否进行Tessellation Shader(曲面细分可能会用到)和Geometry Shader(可以在GPU上增删几何信息)。紧接着进行光栅化,再将光栅化后的结果传给Fragment Shader进行pixel级别的处理。最后将处理的像素传给FrameBuffer并显示到屏幕上。

2. 几种常用的Rendering Path


Rendering Path其实指的就是渲染场景中光照的方式。由于场景中的光源可能很多,甚至是动态的光源。所以怎么在速度和效果上达到一个最好的结果确实很困难。以当今的显卡发展为契机,人们才衍生出了这么多的Rendering Path来处理各种光照。

2.1 Forward Rendering

Forward Rendering是绝大数引擎都含有的一种渲染方式。要使用Forward Rendering,一般在Vertex Shader或Fragment Shader阶段对每个顶点或每个像素进行光照计算,并且是对每个光源进行计算产生最终结果。下面是Forward Rendering的核心伪代码[1]。

For each light:
For each object affected by the light:
framebuffer += object * light

比如在Unity3D 4.x引擎中,对于下图中的圆圈(表示一个Geometry),进行Forward Rendering处理。

将得到下面的处理结果

也就是说,对于ABCD四个光源我们在Fragment Shader中我们对每个pixel处理光照,对于DEFG光源我们在Vertex Shader中对每个vertex处理光照,而对于GH光源,我们采用球调和(SH)函数进行处理。

Forward Rendering优缺点

很明显,对于Forward Rendering,光源数量对计算复杂度影响巨大,所以比较适合户外这种光源较少的场景(一般只有太阳光)。

但是对于多光源,我们使用Forward Rendering的效率会极其低下。因为如果在vertex shader中计算光照,其复杂度将是 ,而如果在fragment shader中计算光照,其复杂度为 。可见光源数目和复杂度是成线性增长的。

对此,我们需要进行必要的优化。比如

  • 1.多在vertex shader中进行光照处理,因为有一个几何体有10000个顶点,那么对于n个光源,至少要在vertex shader中计算10000n次。而对于在fragment shader中进行处理,这种消耗会更多,因为对于一个普通的1024x768屏幕,将近有8百万的像素要处理。所以如果顶点数小于像素个数的话,尽量在vertex shader中进行光照。
  • 2.如果要在fragment shader中处理光照,我们大可不必对每个光源进行计算时,把所有像素都对该光源进行处理一次。因为每个光源都有其自己的作用区域。比如点光源的作用区域是一个球体,而平行光的作用区域就是整个空间了。对于不在此光照作用区域的像素就不进行处理。但是这样做的话,CPU端的负担将加重,因为要计算作用区域。
  • 3.对于某个几何体,光源对其作用的程度是不同,所以有些作用程度特别小的光源可以不进行考虑。典型的例子就是Unity中只考虑重要程度最大的4个光源。

2.2 Deferred Rendering

Deferred Rendering(延迟渲染)顾名思义,就是将光照处理这一步骤延迟一段时间再处理。具体做法就是将光照处理这一步放在已经三维物体生成二维图片之后进行处理。也就是说将物空间的光照处理放到了像空间进行处理。要做到这一步,需要一个重要的辅助工具——G-Buffer。G-Buffer主要是用来存储每个像素对应的Position,Normal,Diffuse Color和其他Material parameters。根据这些信息,我们就可以在像空间中对每个像素进行光照处理[3]。下面是Deferred Rendering的核心伪代码。

For each object:
Render to multiple targets
For each light:
Apply light as a 2D postprocess

下面简单举个例子[1]。

首先我们用存储各种信息的纹理图。比如下面这张Depth Buffer,主要是用来确定该像素距离视点的远近的。

图. Depth Buffer

根据反射光的密度/强度分度图来计算反射效果。

图.Specular Intensity/Power

下图表示法向数据,这个很关键。进行光照计算最重要的一组数据。

图.Normal Buffer

下图使用了Diffuse Color Buffer。

图.Diffuse Color Buffer

这是使用Deferred Rendering最终的结果。

图.Deferred Lighting Results

Deferred Rendering的最大的优势就是将光源的数目和场景中物体的数目在复杂度层面上完全分开。也就是说场景中不管是一个三角形还是一百万个三角形,最后的复杂度不会随光源数目变化而产生巨大变化。从上面的伪代码可以看出deferred rendering的复杂度为 。

但是Deferred Rendering局限性也是显而易见。比如我在G-Buffer存储以下数据

Depth

R32F

Normal + scattering

A2R10G10B10

Diffuse color + emissive

A8R8G8B8

Other material parameters

A8R8G8B8

这样的话,对于一个普通的1024x768的屏幕分辨率。总共得使用1024x768x128bit=20MB,对于目前的动则上GB的显卡内存,可能不算什么。但是使用G-Buffer耗费的显存还是很多的。一方面,对于低端显卡,这么大的显卡内存确实很耗费资源。另一方面,如果要渲染更酷的特效,使用的G-Buffer大小将增加,并且其增加的幅度也是很可观的。顺带说一句,存取G-Buffer耗费的带宽也是一个不可忽视的缺陷。

对于Deferred Rendering的优化也是一个很有挑战的问题。下面简单介绍几种降低Deferred Rendering存取带宽的方式。最简单也是最容易想到的就是将存取的G-Buffer数据结构最小化,这也就衍生出了light pre-pass方法。另一种方式是将多个光照组成一组,然后一起处理,这种方法衍生了Tile-based deferred Rendering。

2.2.1 Light Pre-Pass

Light Pre-Pass最早是由Wolfgang Engel在他的博客[2]中提到的。具体的做法是

  • (1)只在G-Buffer中存储Z值和Normal值。对比Deferred Render,少了Diffuse Color, Specular Color以及对应位置的材质索引值。
  • (2)在FS阶段利用上面的G-Buffer计算出所必须的light properties,比如Normal*LightDir,LightColor,Specular等light properties。将这些计算出的光照进行alpha-blend并存入LightBuffer(就是用来存储light properties的buffer)。
  • (3)最后将结果送到forward rendering渲染方式计算最后的光照效果。

相对于传统的Deferred Render,使用Light Pre-Pass可以对每个不同的几何体使用不同的shader进行渲染,所以每个物体的material properties将有更多变化。这里我们可以看出相对于传统的Deferred Render,它的第二步(见伪代码)是遍历每个光源,这样就增加了光源设置的灵活性,而Light Pre-Pass第三步使用的其实是forward rendering,所以可以对每个mesh设置其材质,这两者是相辅相成的,有利有弊。另一个Light Pre-Pass的优点是在使用MSAA上很有利。虽然并不是100%使用上了MSAA(除非使用DX10/11的特性),但是由于使用了Z值和Normal值,就可以很容易找到边缘,并进行采样。

下面这两张图,左边是使用传统Deferred Render绘制的,右边是使用Light Pre-Pass绘制的。这两张图在效果上不应该有太大区别。

2.2.2 Tile-Based Deferred Rendering

TBDR主要思想就是将屏幕分成一个个小块tile。然后根据这些Depth求得每个tile的bounding box。对每个tile的bounding box和light进行求交,这样就得到了对该tile有作用的light的序列。最后根据得到的序列计算所在tile的光照效果。[4][5]

对比Deferred Render,之前是对每个光源求取其作用区域light volume,然后决定其作用的的pixel,也就是说每个光源要求取一次。而使用TBDR,只要遍历每个pixel,让其所属tile与光线求交,来计算作用其上的light,并利用G-Buffer进行Shading。一方面这样做减少了所需考虑的光源个数,另一方面与传统的Deferred Rendering相比,减少了存取的带宽。

2.3 Forward+

Forward+ == Forward + Light Culling[6]。Forward+很类似Tiled-based Deferred Rendering。其具体做法就是先对输入的场景进行z-prepass,也就是说关闭写入color,只向z-buffer写入z值。注意此步骤是Forward+必须的,而其他渲染方式是可选的。接下来来的步骤和TBDR很类似,都是划分tiles,并计算bounding box。只不过TBDR是在G-Buffer中完成这一步骤的,而Forward+是根据Z-Buffer。最后一步其实使用的是forward方式,即在FS阶段对每个pixel根据其所在tile的light序列计算光照效果。而TBDR使用的是基于G-Buffer的deferred rendering。

实际上,forward+比deferred运行的更快。我们可以看出由于Forward+只要写深度缓存就可以,而Deferred Render除了深度缓存,还要写入法向缓存。而在Light Culling步骤,Forward+只需要计算出哪些light对该tile有影响即可。而Deferred Render还在这一部分把光照处理给做了。而这一部分,Forward+是放在Shading阶段做的。所以Shading阶段Forward+耗费更多时间。但是对目前硬件来说,Shading耗费的时间没有那么多。

Forward+的优势还有很多,其实大多就是传统Forward Rendering本身的优势,所以Forward+更像一个集各种Rendering Path优势于一体的Rendering Path。

3. 总结


首先我们列出Rendering Equation,然后对比Forward Rendering,Deferred Rendering和Forward+ Rendering[6]。

3.1 Rendering Equation

其中点 处有一入射光,其光强为 ,入射角度为 。根据函数 和 来计算出射角为 处的出射光强度。最后在辅以出射光的相对于视点可见性 。注意此处的 为场景中总共有 个光源。

3.2 Forward Renderng

由于Forward本身对多光源支持力度不高,所以此处对于每个点 的处理不再考虑所有的 个光源,仅仅考虑少量的或者说经过挑选的 个光源。可以看出这样的光照效果并不完美。另外,每个光线的 是计算不了的。

3.3 Deferred Rendering

由于Deferred Rendering使用了light culling,所以不用遍历场景中的所有光源,只需遍历经过light culling后的 个光源即可。并且Deferred Rendering将计算BxDF的部分单独分出来了。

3.4 Forward+ Rendering

可以看出Forward+和Forward最大区别就是光源的挑选上有了很到改进。

参考文献

[1] Shawn Hargreaves. (2004) “Deferred Shading”. [Online] Available:

http://hall.org.ua/halls/wizzard/books/articles-cg/DeferredShading.pdf (April 15,2015)

[2] Wolfgang Engel. (March 16, 2008) “Light Pre-Pass Renderer”. [Online] Available:

http://diaryofagraphicsprogrammer.blogspot.com/2008/03/light-pre-pass-renderer.html(April 14,2015)

[3] Klint J. Deferred Rendering in Leadwerks Engine[J]. Copyright Leadwerks Corporation, 2008.

[4] 龚敏敏.(April 22, 2012) “Forward框架的逆袭:解析Forward+渲染”. [Online] Available:

http://www.cnblogs.com/gongminmin/archive/2012/04/22/2464982.html(April 13,2015)

[5] Lauritzen A. Deferred rendering for current and future rendering pipelines[J]. SIGGRAPH Course: Beyond Programmable Shading, 2010: 1-34.

[6] Harada T, McKee J, Yang J C. Forward+: Bringing deferred lighting to the next level[J]. 2012.

【原】实时渲染中常用的几种Rendering Path的更多相关文章

  1. 渲染路径-实时渲染中常用的几种Rendering Path

    http://www.cnblogs.com/polobymulberry/p/5126892.html?utm_source=tuicool&utm_medium=referral 回到顶部 ...

  2. Python编程中常用的12种基础知识总结

    原地址:http://blog.jobbole.com/48541/ Python编程中常用的12种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时 ...

  3. 本文将介绍“数据计算”环节中常用的三种分布式计算组件——Hadoop、Storm以及Spark。

    本文将介绍“数据计算”环节中常用的三种分布式计算组件——Hadoop.Storm以及Spark. 当前的高性能PC机.中型机等机器在处理海量数据时,其计算能力.内存容量等指标都远远无法达到要求.在大数 ...

  4. C#中常用的几种读取XML文件的方法

    1.C#中常用的几种读取XML文件的方法:http://blog.csdn.net/tiemufeng1122/article/details/6723764/

  5. .NET中常用的几种解析JSON方法

    一.基本概念 json是什么? JSON:JavaScript 对象表示法(JavaScript Object Notation). JSON 是一种轻量级的数据交换格式,是存储和交换文本信息的语法. ...

  6. iOS中常用的四种数据持久化方法简介

    iOS中常用的四种数据持久化方法简介 iOS中的数据持久化方式,基本上有以下四种:属性列表.对象归档.SQLite3和Core Data 1.属性列表涉及到的主要类:NSUserDefaults,一般 ...

  7. 【转载】Python编程中常用的12种基础知识总结

    Python编程中常用的12种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时间对象操作,命令行参数解析(getopt),print 格式化输出,进 ...

  8. python中常用的九种数据预处理方法分享

    Spyder   Ctrl + 4/5: 块注释/块反注释 本文总结的是我们大家在python中常见的数据预处理方法,以下通过sklearn的preprocessing模块来介绍; 1. 标准化(St ...

  9. 请写出JavaScript中常用的三种事件。

    请写出JavaScript中常用的三种事件. 解答: onclick,onblur,onChange

随机推荐

  1. react-router 组件式配置与对象式配置小区别

    1. react-router 对象式配置 和 组件式配置    组件式配置(Redirect) ----对应---- 对象式配置(onEnter钩子) IndexRedirect -----对应-- ...

  2. C++中的命名空间

    一,命名空间(namespace)的基本概念以及由来 1.什么是标识符: 在C++中,标识符可以是基本的变量,类,对象,结构体,函数,枚举,宏等. 2.什么是命名空间: 所谓的命名空间是指标识符的可见 ...

  3. 邮件中嵌入html中要注意的样式

    工作中常会有需求向用户发送邮件,需要前端工程师来制作html格式的邮件,但是由于邮件客户端对样式的支持有限,要兼容很多种浏览器需要注意很多原则: 1.邮件使用table+css布局 2.邮件主要部分在 ...

  4. BPM助力企业数字化转型

    自九十年代末,流程管理开始引入国内,至今已经有20多年的历史了,由最初的部门级应用向企业级应用转变,大家的认知也经历了一系列的发展变化.不同阶段的信息化水平对企业的流程以及BPM平台也提出了不同的需求 ...

  5. 学习C的笔记

    [unsigned] 16位系统中一个int能存储的数据的范围为-32768~32767,而unsigned能存储的数据范围则是0~65535.由于在计算机中,整数是以补码形式存放的.根据最高位的不同 ...

  6. Android之DOM解析XML

    一.DOM解析方法介绍 DOM是基于树形结构的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树,检索所需数据.分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息 ...

  7. 设计模式之工厂模式VS抽象工厂

    一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java与模式>中分为三类:1)简单工厂模式(Simple Factor ...

  8. openresty 前端开发入门五之Mysql篇

    openresty 前端开发入门五之Mysql篇 这章主要演示怎么通过lua连接mysql,并根据用户输入的name从mysql获取数据,并返回给用户 操作mysql主要用到了lua-resty-my ...

  9. 在VMware上安装CentOS -7

    1.下载好VMware 2.准备好CentOS的镜像文件 3.打开VMware创建新的虚拟机 选择自定义高级后按下一步 继续下一步 选择稍后安装操作系统 客户机操作系统选择Linux,版本选择Cent ...

  10. Princeton Algorithms week3 Assignment

    这周编程作业是实现检测点共线的算法.和排序算法有关系的地方在于,对斜率排序后可以很快的检测出来哪些点是共线的,另外这个算法的瓶颈也在于排序的性能. 一点收获: java传参数时传递的是值,这很多人都知 ...