Difference between revisions of "延迟渲染"
Gongminmin (Talk | contribs) (Created page with "本文讲述的是KlayGE的Deferred Rendering例子中使用的延迟渲染方法。 == Deferred Lighting的框架 == KlayGE 3.11的例子已经从Deferred Shading改成了更...") |
Gongminmin (Talk | contribs) |
||
Line 26: | Line 26: | ||
Lighting pass在Deferred Lighting框架处于核心地位,在这里我打算先把lighting pass解析清楚。一旦lighting pass表达好了,G-Buffer所需要保存的信息,以及shading pass能得到的信息也都清楚了。 | Lighting pass在Deferred Lighting框架处于核心地位,在这里我打算先把lighting pass解析清楚。一旦lighting pass表达好了,G-Buffer所需要保存的信息,以及shading pass能得到的信息也都清楚了。 | ||
− | + | [[基于物理的BRDF]]推出了渲染模型总公式: | |
<center><math>L_{o}(\mathbf{v})=\pi\rho(\mathbf{l_c}, \mathbf{v})\otimes \mathbf{c}_{light} (\mathbf{n} \cdot \mathbf{l_c})=(\mathbf{c}_{diff} + \frac {\alpha + 2} {8}(\mathbf{n} \cdot \mathbf{h})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_c},\mathbf{h})) \otimes \mathbf{c}_{light} (\mathbf{n} \cdot \mathbf{l_c})</math></center> | <center><math>L_{o}(\mathbf{v})=\pi\rho(\mathbf{l_c}, \mathbf{v})\otimes \mathbf{c}_{light} (\mathbf{n} \cdot \mathbf{l_c})=(\mathbf{c}_{diff} + \frac {\alpha + 2} {8}(\mathbf{n} \cdot \mathbf{h})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_c},\mathbf{h})) \otimes \mathbf{c}_{light} (\mathbf{n} \cdot \mathbf{l_c})</math></center> | ||
Line 64: | Line 64: | ||
<math>+ ((\mathbf{n} \cdot \mathbf{h_N})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_{cN}},\mathbf{h_N})) \otimes \mathbf{c}_{lightN} (\mathbf{n} \cdot \mathbf{l_{cN}}))</math></center> | <math>+ ((\mathbf{n} \cdot \mathbf{h_N})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_{cN}},\mathbf{h_N})) \otimes \mathbf{c}_{lightN} (\mathbf{n} \cdot \mathbf{l_{cN}}))</math></center> | ||
− | + | 由于'''c<sub>diff</sub>'''是到最后的shading pass才计算,所以在每一个light pass里面,diffuse和specular必须分开才能保证结果正确: | |
<center><math>Diffuse: \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | <center><math>Diffuse: \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | ||
<center><math>Specular: ((\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \otimes \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | <center><math>Specular: ((\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \otimes \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | ||
− | + | 为了把diffuse和specular放入4个通道的buffer中,就只能牺牲specular的颜色,只剩下亮度,同时'''c<sub>spec</sub>'''也简化成一个标量。所以,lighting pass的计算成了: | |
<center><math>float4(1, 1, 1, (\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(c_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \times \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | <center><math>float4(1, 1, 1, (\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(c_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \times \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | ||
+ | |||
+ | == G-Buffer的分配 == | ||
+ | |||
+ | 在Deferred框架中,不管是Deferred Shading还是Deferred Lighting,G-Buffer的分配都是非常关键的。前面得出的lighting pass公式如下: | ||
+ | |||
+ | <center><math>float4(1, 1, 1, (\mathbf{n} \cdot \mathbf{h_n})^{\alpha} F(c_{spec}, \mathbf{l_{cn}},\mathbf{h_n})) \times \mathbf{c}_{lightn} (\mathbf{n} \cdot \mathbf{l_{cn}})</math></center> | ||
+ | |||
+ | 从公式可以看出,在light pass里需要的量有'''n''','''h''',alpha,c<sub>spec</sub>,'''l<sub>c</sub>'''。因为'''h''' = ('''n''' + '''l<sub>c</sub>''') / 2(见[[基于物理的BRDF]]),而'''l<sub>c</sub>''' = normalize('''l''' - '''p''')('''l'''是光源位置,'''p'''是要计算的点位置),所以最终需要G-Buffer提供的量有:'''n''','''p''',alpha和c<sub>spec</sub>。要完整的保存这些量,一共需要8个通道,normal占3个,position占3个,alpha和c<sub>spec</sub>分别占一个。这样对G-Buffer来说消耗太大了,必须要缩减。 | ||
+ | |||
+ | 显而易见的是,normal是经过归一化的,只需要保存2个分量。[http://aras-p.info/texts/CompactNormalStorage.html 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和c<sub>spec</sub>。如果不需要fresnel,可以直接忽略c<sub>spec</sub>,留到shading pass再做,这里直接存alpha就可以了。否则,就需要把alpha和c<sub>spec</sub>放入同一个通道。我用的方法是,floor(c<sub>spec</sub> * 100)作为整数部分,clamp(alpha, 0, 255) / 256座位小数部分。这样的限制是,alpha取值范围为[0, 256),一般来说够用了。 | ||
+ | |||
+ | 由此,所有lighting pass需要的信息都被压进4个通道内,G-Buffer只需要1张texture,省去了MRT。 | ||
+ | |||
+ | == Shading Pass == | ||
+ | |||
+ | shading pass需要把前面所有lighting pass积累出来的光照信息和物体本身的材质信息组合起来,得出最后的着色。物体材质中的c<sub>spec</sub>已经存在G-Buffer,并在lighting pass中计算了,所以shading pass输入的材质有'''c<sub>diff</sub>''','''c<sub>spec</sub>''','''c<sub>emit</sub>''',alpha。别忘了在前面的公式中,specular号需要乘上归一化系数(alpha + 2) / 8。另一方面,在lighting pass的结果里,rgb存的是积累的diffuse,a存的是积累的specular亮度,如果还有计算AO,那么shading所用的公式就是: | ||
+ | |||
+ | <center><math>\mathbf{c}_{emit} + (lighting.rgb * \mathbf{c}_{diff} + \frac{\alpha + 2}{8} * lighting.a) * ao</math></center> | ||
+ | |||
+ | 如果在G-Buffer和lighting pass因为不考虑fresnel而至保存了alpha,那么shading pass的公式就变成: | ||
+ | |||
+ | <center><math>\mathbf{c}_{emit} + (lighting.rgb * \mathbf{c}_{diff} + \frac{\alpha + 2}{8} * \mathbf{c}_{spec} * lighting.a) * ao</math></center> |
Revision as of 01:50, 27 January 2011
本文讲述的是KlayGE的Deferred Rendering例子中使用的延迟渲染方法。
Deferred Lighting的框架
KlayGE 3.11的例子已经从Deferred Shading改成了更节省带宽的Deferred Lighting。这里先对Deferred Lighting作一个简要的介绍,并假设读者已经了解了Deferred Shading。
Deferred Lighting的渲染架构可以分为三个阶段:
1. for each object { 填充G-Buffer } 2. for each light { Lighting pass } 3. for each object { 执行shading }
与Deferred Shading不同的是,shading(也就是和材质相关)的计算仅仅发生在最后一个阶段。所以,G-Buffer中需要保存的信息得到极大地减小,甚至不再需要MRT。
Lighting pass
Lighting pass在Deferred Lighting框架处于核心地位,在这里我打算先把lighting pass解析清楚。一旦lighting pass表达好了,G-Buffer所需要保存的信息,以及shading pass能得到的信息也都清楚了。
基于物理的BRDF推出了渲染模型总公式:
再有N个光源的情况下,每个像素的光照响应就是
对于Deferred shading来说,每一个shading pass就是执行一个
而对于Deferred lighting来说,公式需要重新整理一下:
由于cdiff是到最后的shading pass才计算,所以在每一个light pass里面,diffuse和specular必须分开才能保证结果正确:
为了把diffuse和specular放入4个通道的buffer中,就只能牺牲specular的颜色,只剩下亮度,同时cspec也简化成一个标量。所以,lighting pass的计算成了:
G-Buffer的分配
在Deferred框架中,不管是Deferred Shading还是Deferred Lighting,G-Buffer的分配都是非常关键的。前面得出的lighting pass公式如下:
从公式可以看出,在light pass里需要的量有n,h,alpha,cspec,lc。因为h = (n + lc) / 2(见基于物理的BRDF),而lc = normalize(l - p)(l是光源位置,p是要计算的点位置),所以最终需要G-Buffer提供的量有:n,p,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的公式就变成: