KlayGE中的延迟渲染_gbuffer分配-程序员宅基地

技术标签: 图形引擎  shader  游戏引擎  图形图像  KlayGE  float  其它文章  each  引擎开发  游戏  技术理论  buffer  框架  

KlayGE中的延迟渲染系列文章将讲述在KlayGE 3.11的Deferred Rendering例子中使用的延迟渲染方法,由5篇文章组成。

Deferred Lighting的框架

KlayGE 3.11的例子已经从Deferred Shading改成了更节省带宽的Deferred Lighting。这里先对Deferred Lighting作一个简要的介绍,并假设读者已经了解了Deferred Shading。

Deferred Lighting的渲染架构可以分为三个阶段:

  1. G-Buffer的生成
  2. for each light
    {
    Lighting pass
    }
  3. Shading pass

与Deferred Shading不同的是,shading(也就是和材质相关)的计算仅仅发生在最后一个阶段。所以,G-Buffer中需要保存的信息得到极大地减小,甚至不再需要MRT。

Lighting pass

Lighting pass在Deferred Lighting框架处于核心地位,在这里我打算先把lighting pass解析清楚。一旦lighting pass表达好了,G-Buffer所需要保存的信息,以及shading pass能得到的信息也都清楚了。

我以前的系列文章游戏中基于物理的渲染推出了渲染模型总公式:

再有N个光源的情况下,每个像素的光照响应就是

对于Deferred shading来说,每一个shading pass就是执行一个

而对于Deferred lighting来说,公式需要重新整理一下:

由于cdiff是到最后的shading pass才计算,所以在每一个light pass里面,diffuse和specular必须分开才能保证结果正确:

为了把diffuse和specular放入4个通道的buffer中,就只能牺牲specular的颜色,只剩下亮度,同时cspec也简化成一个标量。所以,lighting pass的计算成了:

本系列的第一篇暂告一段落,下一篇将介绍G-Buffer的分配。



G-Buffer分配

在Deferred Rendering的框架中,不管是Deferred Shading还是Deferred Lighting,G-Buffer的分配都是非常关键的。上一篇得出的lighting pass公式如下:

从公式可以看出,在light pass里需要的量有nh,alpha,cspeclc。因为h = (v +lc) / 2(见游戏中基于物理的渲染系列文章),而lc = normalize(lp)(l是光源位置,p是要计算的点位置),所以最终需要G-Buffer提供的量有:np,alpha和cspec。要完整的保存这些量,一共需要8个通道,normal占3个,position占3个,alpha和cspec分别占一个。这样对G-Buffer来说消耗太大了,必须要缩减。

显而易见的是,normal是经过归一化的,只需要保存2个分量。http://aras-p.info/texts/CompactNormalStorage.html比较了多种保存2分量的方法,其中Spheremap transform速度和效果综合起来最佳,Crytek也在用同样的方法,即:

float2 encode(float3 normal)
{
   return normalize(normal.xy) * sqrt(normal.z * 0.5 + 0.5);
}
float3 decode(float2 n)
{
   float3 normal;
   normal.z = dot(n, n) * 2 - 1;
   normal.xy = normalize(n) * sqrt(1 - normal.z * normal.z);
   return normal;
}

下 一步是position。实际上像素所在的位置已经提供了x和y,需要保存的仅仅是z。position何以很好地从z和像素位置计算出来。这里保存的是 view space的z除以far plane。在lighting pass,pixel shader里拿到像素在view space的位置之后,做这样的计算:

p = view_dir * ((z * far_plane) / view_dir.z);

其 中,view_dir是在vertex shader中计算之后传到pixel shader。对于把光源的几何体直接作为光源几何的情况(如果你不熟悉这个,请见下篇),那么view_dir就是顶点乘上world * view矩阵之后的结果。对于用全屏的四边形作为光源几何的情况,view_dir就是把view frustum在far plane上的四个点乘上inverse(projection)矩阵之后的结果。z * far_plane就还原出了该点在view space的z,然后根据相似三角形的定理很容易就能推出这个还原公式。现在,position成功地压缩到了1个通道。

剩下的就是alpha和cspec。如果不需要fresnel,可以直接忽略cspec,留到shading pass再做,这里直接存alpha就可以了。否则,就需要把alpha和cspec放入同一个通道。我用的方法是,floor(cspec * 100)作为整数部分,clamp(alpha, 0, 255) / 256座位小数部分。这样的限制是,alpha取值范围为[0, 256),一般来说够用了。

由此,所有lighting pass需要的信息都被压进4个通道内,G-Buffer只需要1张texture,省去了MRT。

Shading Pass

shading pass需要把前面所有lighting pass积累出来的光照信息和物体本身的材质信息组合起来,得出最后的着色。物体材质中的cspec已经存在G-Buffer,并在lighting pass中计算了,所以shading pass输入的材质有cdiff,cspec,cemit,alpha。别忘了在上一篇的公式中,specular号需要乘上归一化系数(alpha + 2) / 8。另一方面,在lighting pass的结果里,rgb存的是积累的diffuse,a存的是积累的specular亮度,如果还有计算AO,那么shading所用的公式就是:

如果在G-Buffer和lighting pass因为不考虑fresnel而至保存了alpha,那么shading pass的公式就变成:

现在Deferred Lighting的3个阶段都已经得到解释,下一篇将讲解如何更快地计算lighting pass。




Anti-Alias

从Deferred Shading发明的一天起,anti-alias的问题就一直困扰着所有Deferred的方法。虽然很多无良的游戏厂商直接在Deferred Rendering的游戏里不支持AA,但确实AA对提升画面质量很有帮助。

Edge AA

在Deferred的框架里,很自然会想到用Edge AA来处理AA。其过程不外乎:

  1. 边缘检测,得到每个像素“像边缘的程度”
  2. 在shader里根据“像边缘的程度”来控制采样坐标

这本身并不是个复杂的过程,尤其是第二步,非常直截了当了,所以这里集中讨论的是如何进行边缘检测。

GPU Gems 2的“Deferred Shading in STALKER”一文提供了一种边缘检测的方法,通过把周围像素的法线差和深度差的和来判断边缘,由e_barrier这个参数来定义阈值和比例,而这个参数和分辨率有关。GPU Gems 3的“Deferred Shading in Tabula Rasa”改进了这个过程,只判断法线差和深度差最大和最小的两组。由于只是局部的相对量而已,这样就做到了和分辨率无关的边缘检测。KlayGE目前用的也是这种方法,得到的边缘如下:

Edge detection

另一个可能用于边缘检测的方法是,第二篇文章提到了如何恢复出每个pixel的view space position,每个pixel取得周围4个pixel的位置之后,就可以直接cross得出一个normal,姑且称为screen space normal。如果一个像素是连续的,那么这个normal就会很接近于G-Buffer中保存的normal,否则它们的方向就会差别很大。下图为G-Buffer中的normal:

Normal in G-Buffer这是screen space计算出的normal:

Normal in screen space把这两个normal做一次dot,小于某个阈值的就认为是边缘,得到:

screen space normal based edge利用硬件MSAA作边缘检测

前面提到的边缘检测结果虽然不错,但其实都是是参数相关的。能否就用硬件的MSAA来做边缘检测呢?在Shader model 3.0以上的GPU,vertex attribute插值的时候可以选择centroid这个modifier。开启了centroid的attribute,会选择覆盖到的sample中心来插值,而不是像素中心。所以,同一个属性,如果即有centroid又有不带centroid的版本都传给pixel shader,在pixel shader里面判断两者不一致,就表示这个pixel在边缘上。这样的话,边缘的情况就和硬件MSAA完全一致了。但其实MSAA会过渡判断边缘,所有三角形的边缘都会被认出来,即便只是物体内部的。所以谨慎使用。

能不能就用MSAA?

前面讨论了那么多都是基于Edge的AA。在Deferred Lighting框架下,难道就不能直接用MSAA?可以!这也是Deferred Lighting比Deferred Shading优秀的方面之一。Deferred Shading不能直接MSAA的本质原因是在G-Buffer之后,物体几何信息全部抛弃了。相比Deferred Lighting,在shading pass,物体会被再次渲染一遍,这个时候还是有几何信息的,如果在shading pass打开了MSAA,就可以像Forward shading那样利用硬件MSAA了。唯一不同的是,光照来自于lighting pass的texture,而不是从光源计算。就算硬件MSAA,也只是每个pixel执行一次pixel shader,在按照覆盖情况写入sample的,所以在这里视觉上几乎和Forward shading一样。

讲了这么多AA方面的事情,下一篇将讨论一些对Deferred Rendering的扩展,以及未来的工作。


展望未来

shading pass再次渲染物体的改进

Deferred Lighting最受争议的一点应属在shading pass需要再次渲染几何体了。如果物体很多,尤其是有tessellation和GS的,多渲一遍有可能抵消了lighting pass带来的性能提升。改进的方法之一就是在建立G-Buffer阶段,用类似Deferred Shading的fat G-Buffer。除了原先的一张纹理,还需要一张纹理用来存放diffuse信息。但是lighting pass和原来一样,不涉及diffuse。shading pass就变成画一个全屏四边形,从G-Buffer的第二章纹理读取diffuse,进行着色。甚至emit也这么处理。这种方法介于Deferred Shading和Deferred Lighting之间。

彩色的specular

在本系列的第一篇文章里, 为了把lighting pass中的diffuse和specular都塞到4个通道里,就只能舍弃specular的颜色,只保存亮度。如果要RGB三个通道的 specular,近似的方法是通过diffuse积累结果的颜色来计算specular的颜色。这是个很粗糙的近似,虽然不是正确的,不过能骗骗眼睛:

其中lumspec是累积出来的specular亮度,lumdiff是用累积出来的diffuse颜色计算出的亮度。epsilon是为了避免lumdiff为零。
另一种方法是lighting pass用6个通道。但是如果每个通道都是float 16的,也就是96bpp,带宽开销非常大,就不合适了。我的一个想法是把diffuse和specular都转换到YUV空间。这个空间的一个好处是Y 是float 16的,U和V都只要8 bit就可以了。所以可以这么安排MRT:第一张texture格式为G16R16F,保存diffuse和specular的Y;第二张texture 格式为ABGR8,分别保存两者的U和V。这样只有64bpp,但能保存正确的彩色diffuse和specular。由于YUV格式也是可以相加的,这 个地方仍可以用原先的lighting pass积累方法。

inferred lighting

Lighting pass可以借用inferred lighting的核心思想来加速。也就是说,lighting pass不需要全尺寸,只需要在一个比较小的render target上执行即可(比如3/4大小)。G-Buffer仍是全尺寸的,并在G-Buffer生成后作一次边缘检测。Shading pass也是全尺寸的,在采样lighting pass texture的时候,利用边缘检测的结果进行保边缘的插值(一般称为Discontinuity Sensitive Filtering,DSF),得到全尺寸lighting的近似。

DSF
上图是使用了800×450的lighting直接拉伸到1280×720做shading的结果,关闭DSF,锯齿严重。下图打开了DSF,基本解决了锯齿问题。

Anti-alias

上一篇文章讲了很多AA的方法,但那些都是在空间上做AA,比较适合近处物体。对于远处物体来说,空间上AA得到的收益有限,必须在时间上进行AA。结合上MLAA的威力,应该能有很小的代价实现很接近16xMSAA的结果。

各向异性BRDF

Crytek的“CryENGINE 3: Reaching the speed of light”里提到了在Deferred Lighting框架下加入各向异性BRDF的方法。它用了Spherical Gaussian(SG)来近似出NDF(来自于SIGGRAPH Asia 2009的All-Frequency Rendering of Dynamic, Spatially-Varying Reflectance),但这个SG只是per-object的。在G-Buffer阶段,不保存normal,而保存SG展开成lobe的系数。而 BRDF的其他几个项,Fresnel term、Geometry term,都留到shading pass才计算。这种方法的好处是,对lighting pass来说一切都是透明的,它照样可以按原来的方法累积光照,因为Microfacet BRDF中除了NDF,其他都作为公因数提取出去了(Microfacet BRDF的详细讲解可以参见“游戏中基于物理的渲染(三)”)。实际上,Fresnel term的系数是lh,必须在lighting pass做。这里相信Crytek是用了nv来代替,这样不是物理正确的,只有在高光的中心点,dot(l,h)才等于dot(n, v),其他地方dot (n,v)会更迅速地衰减,到边缘地方就非常明显了。如果不在乎这个,是可以把NDF都用SG来表示,并用统一的方法进行渲染。

保存lobe的G-Buffer是这个样子的:

lobe in G-Buffer

各向异性BRDF渲染出来的结果:

Anisotropic BRDF

KlayGE中的延迟渲染就介绍到这里了。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/pizi0475/article/details/7521399

智能推荐

linux devkmem 源码,linux dev/mem dev/kmem实现访问物理/虚拟内存-程序员宅基地

文章浏览阅读451次。dev/mem: 物理内存的全镜像。可以用来访问物理内存。/dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。调试嵌入式Linux内核时,可能需要查看某个内核变量的值。/dev/kmem正好提供了访问内核虚拟内存的途径。现在的内核大都默认禁用了/dev/kmem,打开的方法是在 make menuconfig中选中 device drivers --> ..._dev/mem 源码实现

vxe-table 小众但功能齐全的vue表格组件-程序员宅基地

文章浏览阅读7.1k次,点赞2次,收藏19次。vxe-table,一个小众但功能齐全并支持excel操作的vue表格组件_vxe-table

(开发)bable - es6转码-程序员宅基地

文章浏览阅读62次。参考:http://www.ruanyifeng.com/blog/2016/01/babel.htmlBabelBabel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行// 转码前input.map(item => item + 1);// 转码后input.map(function (item) { return item..._让开发环境支持bable

FPGA 视频处理 FIFO 的典型应用_fpga 频分复用 视频-程序员宅基地

文章浏览阅读2.8k次,点赞6次,收藏29次。摘要:FPGA视频处理FIFO的典型应用,视频输入FIFO的作用,视频输出FIFO的作用,视频数据跨时钟域FIFO,视频缩放FIFO的作用_fpga 频分复用 视频

R语言:设置工作路径为当前文件存储路径_r语言设置工作目录到目标文件夹-程序员宅基地

文章浏览阅读575次。【代码】R语言:设置工作路径为当前文件存储路径。_r语言设置工作目录到目标文件夹

background 线性渐变-程序员宅基地

文章浏览阅读452次。格式:background: linear-gradient(direction, color-stop1, color-stop2, ...);<linear-gradient> = linear-gradient([ [ <angle> | to <side-or-corner>] ,]? &l..._background线性渐变

随便推点

【蓝桥杯省赛真题39】python输出最大的数 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析-程序员宅基地

文章浏览阅读1k次,点赞26次,收藏8次。第十三届蓝桥杯青少年组python编程省赛真题一、题目要求(注:input()输入函数的括号中不允许添加任何信息)1、编程实现给定一个正整数N,输出正整数N中各数位最大的那个数字。例如:N=132,则输出3。2、输入输出输入描述:只有一行,输入一个正整数N输出描述:只有一行,输出正整数N中各数位最大的那个数字输入样例:

网络协议的三要素-程序员宅基地

文章浏览阅读2.2k次。一个网络协议主要由以下三个要素组成:1.语法数据与控制信息的结构或格式,包括数据的组织方式、编码方式、信号电平的表示方式等。2.语义即需要发出何种控制信息,完成何种动作,以及做出何种应答,以实现数据交换的协调和差错处理。3.时序即事件实现顺序的详细说明,以实现速率匹配和排序。不完整理解:语法表示长什么样,语义表示能干什么,时序表示排序。转载于:https://blog.51cto.com/98..._网络协议三要素csdn

The Log: What every software engineer should know about real-time data's unifying abstraction-程序员宅基地

文章浏览阅读153次。主要的思想,将所有的系统都可以看作两部分,真正的数据log系统和各种各样的query engine所有的一致性由log系统来保证,其他各种query engine不需要考虑一致性,安全性,只需要不停的从log系统来同步数据,如果数据丢失或crash可以从log系统replay来恢复可以看出kafka系统在linkedin中的重要地位,不光是d..._the log: what every software engineer should know about real-time data's uni

《伟大是熬出来的》冯仑与年轻人闲话人生之一-程序员宅基地

文章浏览阅读746次。伟大是熬出来的  目录  前言  引言 时间熬成伟大:领导者要像狼一样坚忍   第一章 内圣外王——领导者的心态修炼  1. 天纵英才的自信心  2. 上天揽月的企图心  3. 誓不回头的决心  4. 宠辱不惊的平常心  5. 换位思考的同理心  6. 激情四射的热心  第二章 日清日高——领导者的高效能修炼  7. 积极主动,想到做到  8. 合理掌控自己的时间和生命  9. 制定目标,马..._当狼拖着受伤的右腿逃生时,右腿会成为前进的阻碍,它会毫不犹豫撕咬断自己的腿, 以

有源光缆AOC知识百科汇总-程序员宅基地

文章浏览阅读285次。在当今的大数据时代,人们对高速度和高带宽的需求越来越大,迫切希望有一种新型产品来作为高性能计算和数据中心的主要传输媒质,所以有源光缆(AOC)在这种环境下诞生了。有源光缆究竟是什么呢?应用在哪些领域,有什么优势呢?易天将为您解答!有源光缆(Active Optical Cables,简称AOC)是两端装有光收发器件的光纤线缆,主要构成部件分为光路和电路两部分。作为一种高性能计..._aoc 光缆

浏览器代理服务器自动配置脚本设置方法-程序员宅基地

文章浏览阅读2.2k次。在“桌面”上按快捷键“Ctrl+R”,调出“运行”窗口。接着,在“打开”后的输入框中输入“Gpedit.msc”。并按“确定”按钮。如下图 找到“用户配置”下的“Windows设置”下的“Internet Explorer 维护”的“连接”,双击选择“自动浏览器配置”。如下图 选择“自动启动配置”,并在下面的“自动代理URL”中填写相应的PAC文件地址。如下..._設置proxy腳本