【GPU精粹与Shader编程】(三) 《GPU Gems 1》全书核心内容提炼总结 · 下篇
本文首发于个人的知乎专栏:https://zhuanlan.zhihu.com/p/36499291
题图背景来自《神秘海域4》。
系列文章前言
《GPU Gems》1~3 、《GPU Pro》1~7 以及《GPU Zen》组成的饕餮盛宴,共11本书,合称“GPU精粹三部曲“,是游戏开发、计算机图形学和渲染领域的业界顶尖大牛们一线经验的合辑汇编,是江湖各大门派武林绝学经典招式的精华荟萃,是了解业界各种高阶知识和技法Trick,将自己的游戏开发、图形学与渲染能力提升到下一个高度的捷径。
本文内容概览与关键词
本篇文章将总结提炼“GPU精粹三部曲“11本书中的第一本《GPU Gems 1》全书的核心内容的下半部分,是【GPU精粹与Shader编程】系列文章正篇的第二篇,全文共1万5千余字。
本文是对两周前,本计划发出但因知乎专栏字数限制,最终发布在GitHub中的“《GPU Gems 1》全书核心内容提炼总结 · 下篇”相关的内容进行了增改,修订,优化与内容的翔实后的新版本。
本文内容的关键词:
- 真实感皮肤渲染(Realistic Skin Rendering)
- 次表面散射(Subsurface Scattering)
- 环境光遮蔽(Ambient Occlusion)
- 实时辉光(Real-Time Glow)
- 阴影的渲染(Shadow Rendering)
- 透视阴影贴图(Perspective Shadow Maps)
- 逐像素光照的可见性管理(Managing Visibility for Per-Pixel Lighting)
- 空间BRDF(Spatial BRDFs)
- 基于图像的光照(Image-Based Lighting,IBL)
- 纹理爆炸(Texture Bombing)
- 颜色控制(Color Controls)
- 景深 (Depth of Field)
- 高品质的图像滤波(High-Quality Filtering)
- 快速滤波宽度的计算(Fast Filter-Width Estimates )
- 高动态范围(High-Dynamic-Range , HDR)
先放一张来自《神秘海域4》真实感皮肤渲染的效果图:
本文的GitHub版本
不少朋友们喜欢看GitHub版本的文章,且GitHub版本的文章中没有单篇文章的字数限制,可以一次完整地看上下两篇,而且最新的勘误和修订第一时间会在GitHub进行。
【本文的GitHub版本传送门】:
QianMo/Game-Programmer-Study-Notes
目录 · 核心内容导航Highlight
【说明】下文目录中加粗的标题为本文将包括的内容,非加粗的标题已在上次的更新(GPU
Gems 1》全书核心内容提炼总结 · 上篇)中发布。
另外需要注意,本文将原来在上篇中目录为次核心内容的“二十一、实时辉光(Real-Time
Glow)”提升为了主核心内容,现为“六、实时辉光(Real-Time Glow)”。
本文将进行重点提炼总结的主核心内容有:
- 一、用物理模型进行高效的水模拟(Effective Water Simulation from Physical
Models) - 二、Dawn Demo中的皮肤渲染(Skin in the Dawn Demo)
- 三、无尽波动的草地叶片的渲染(Rendering Countless Blades of Waving Grass)
- 四、次表面散射的实时近似(Real-Time Approximations to Subsurface Scattering)
- 五、环境光遮蔽(Ambient Occlusion)
- 六、实时辉光(Real-Time Glow)
本文将进行提炼总结的次核心内容有:
- 七、水焦散的渲染 (Rendering Water Caustics)
- 八、 Dawn Demo中的动画(Animation in the "Dawn" Demo)
- 九、 改良的Perlin噪声实现(Implementing Improved Perlin Noise)
- 十、Vulcan Demo中的火焰渲染(Fire in the "Vulcan" Demo)
- 十一、衍射的模拟(Simulating Diffraction)
- 十二、高效的阴影体渲染(Efficient Shadow Volume Rendering)
- 十三、电影级光照(Cinematic Lighting)
- 十四、阴影贴图抗锯齿(Shadow Map Antialiasing)
- 十五、全方位阴影映射(Omnidirectional Shadow Mapping)
- 十六、使用遮挡区间映射产生模糊的阴影(Generating Soft Shadows Using Occlusion Interval Maps)
- 十七、透视阴影贴图(Perspective Shadow Maps: Care and Feeding)
- 十八、逐像素光照的可见性管理(Managing Visibility for Per-Pixel Lighting)
- 十九、空间BRDF(Spatial BRDFs)
- 二十、基于图像的光照(Image-Based Lighting)
- 二十一、纹理爆炸(Texture Bombing)
- 二十二、颜色控制(Color Controls)
- 二十三、景深 (Depth of Field)
- 二十四、高品质的图像滤波(High-Quality Filtering)
- 二十五、用纹理贴图进行快速滤波宽度的计算(Fast Filter-Width Estimates with Texture Maps)
- 二十六、OpenEXR图像文件格式与HDR(The OpenEXR Image File Format and HDR)
PS : 因为知乎专栏的单篇文章字数限制在1万5千字的原因,本文的“第二部分 · 次核心内容提炼总结”中所有小结的【本章配套源代码汇总表】子栏目,二十四、二十五两节,以及参考文献、将在下文中略去。需要查看该栏目内容的同学,请移步至本文的GitHub版本:
QianMo/Game-Programmer-Study-Notes
《GPU Gems 1》配套资源与源代码
这一节提供了一些,《GPU Gems 1》书本的配套资源的链接,以及配图源代码的下载。
PS:配套的不少工程中不仅包含完整的源码,也直接包含经过编译后的exe执行文件,可以直接运行后查看效果。
- 原书全文的Web版本:
https://developer.nvidia.com/gpugems/GPUGems/gpugems_pref01.html
- 我维护了的一个名为“GPU-Gems-CD-Content”的GitHub仓库,并整理好了逐章内容的快捷导航,以备份、珍贵、快速查阅这些优质资源:
https://github.com/QianMo/GPU-Gems-CD-Content
第一部分 · 主核心内容提炼总结
四、次表面散射的实时近似(Real-Time Approximations to Subsurface Scattering)
【章节概览】
次表面散射(Subsurface Scattering),简称SSS,或3S,是光射入非金属材质后在内部发生散射,最后射出物体并进入视野中产生的现象,即光从表面进入物体经过内部散射,然后又通过物体表面的其他顶点出射的光线传递过程。
要产生使人信服的皮肤和其他半透明材质的渲染效果,次表面散射(Subsurface Scattering)的渲染效果十分重要。
另外需要提出,在《神秘海域4》中皮肤的渲染效果,很令人惊艳。当然,《神秘海域4》中令人惊艳的,远远不止皮肤的渲染。
本章即描述了次表面散射的几种实时近似方法,关于皮肤的渲染,也关于近似地去模拟透明材质的几种不同方法。
【核心内容提炼】
4.1 次表面散射的视觉特性(The Visual Effects of Subsurface Scattering)
要重现出任何视觉效果,经常的做法是考察这种效果的图像,并把可视的外观分解为其组成要素。在观察半透明物体的相片和图像时,能注意到如下几点,即次表面散射(Subsurface
Scattering)的视觉特性:
1、首先,次表面散射往往使照明的整体效果变得柔和。
2、一个区域的光线往往渗透到表面的周围区域,而小的表面细节变得看不清了。
3、光线传入物体越深,就衰减和散射得越严重。
4、对于皮肤来说,在照亮区到阴影区的衔接处,散射往往会引起微弱的倾向于红色的颜色偏移。这是由于光线照亮表皮并进入皮肤,接着被皮下血管和组织散射和吸收,然后从阴影部分离开。且散射在皮肤薄的部位更加明显,比如鼻孔和耳朵周围。
4.2 简单的散射近似(Simple Scattering Approximations)
近似散射的比较简单技巧是环绕照明(Warp Lighting)。正常情况下,当表面的法线对于光源方向垂直的时候,Lambert漫反射提供的照明度是0。而环绕光照修改漫反射函数,使得光照环绕在物体的周围,越过那些正常时会变黑变暗的点。这减少了漫反射光照明的对比度,从而减少了环境光和所要求的填充光的量。环绕光照是对Oren-Nayar光照模型的一个粗糙的近似。原模型力图更精确地模拟粗糙的不光滑表面(Nayar and Oren 1995)。
下图和代码片段显示了如何将漫反射光照函数进行改造,使其包含环绕效果。
其中,wrap变量为环绕值,是一个范围为0到1之间的浮点数,用于控制光照环绕物体周围距离。
float diffuse = max(0, dot(L, N));
float wrap_diffuse = max(0, (dot(L, N) + wrap) / (1 + wrap));
为了在片元函数程序中的计算可以更加高效,上述函数可以直接编码到纹理中,用光线矢量和法线的点积为索引。
而在照明度接近0时,可以显示出那种倾向于红的微小颜色漂移,这是模拟皮肤散射的一种廉价方法。而这种偏向于红色的微小颜色漂移,也可以直接加入到此纹理中。
另外也可以在此纹理的alpha通道中加入镜面反射高光光照的功率(power)函数。可以在示例代码Example 16-1中的FX代码展示了如何使用这种技术。对比的图示如下。
Example 16-1 摘录纳入了环绕照明的皮肤Shader效果的代码(Excerpt from the Skin
Shader Effect Incorporating Wrap Lighting)
// 为皮肤着色生成2D查找表(Generate 2D lookup table for skin shading)
float4 GenerateSkinLUT(float2 P : POSITION) : COLOR
{
float wrap = 0.2;
float scatterWidth = 0.3;
float4 scatterColor = float4(0.15, 0.0, 0.0, 1.0);
float shininess = 40.0;
float NdotL = P.x * 2 - 1; // remap from [0, 1] to [-1, 1]
float NdotH = P.y * 2 - 1;
float NdotL_wrap = (NdotL + wrap) / (1 + wrap); // wrap lighting
float diffuse = max(NdotL_wrap, 0.0);
// 在从明到暗的转换中添加颜色色调(add color tint at transition from light to
dark)
float scatter = smoothstep(0.0, scatterWidth, NdotL_wrap) *
smoothstep(scatterWidth * 2.0, scatterWidth,
NdotL_wrap);
float specular = pow(NdotH, shininess);
if (NdotL_wrap <= 0) specular = 0;
float4 C;
C.rgb = diffuse + scatter * scatterColor;
C.a = specular;
return C;
}
// 使用查找表着色皮肤(Shade skin using lookup table)
half3 ShadeSkin(sampler2D skinLUT,
half3 N,
half3 L,
half3 H,
half3 diffuseColor,
half3 specularColor) : COLOR
{
half2 s;
s.x = dot(N, L);
s.y = dot(N, H);
half4 light = tex2D(skinLUT, s * 0.5 + 0.5);
return diffuseColor * light.rgb + specularColor * light.a;
}
4.3 使用深度贴图模拟吸收(Simulating Absorption Using Depth Maps)
吸收(Absorption)是模拟半透明材质的最重要特性之一。光线在物质中传播得越远,它被散射和吸收得就越厉害。为了模拟这种效果,我们需要测量光在物质中传播的距离。而估算这个距离可以使用深度贴图(Depth Maps)技术[Hery 2002],此技术非常类似于阴影贴图(Shadow Mapping),而且可用于实时渲染。
深度贴图(Depth Maps)技术的思路是:
在第一个通道(first pass)中,我们从光源的视点处渲染场景,存储从光源到某个纹理的距离。然后使用标准的投射纹理贴图(standard projective texture mapping),将该图像投射回场景。在渲染通道(rendering pass)中,给定一个需要着色的点,我们可以查询这个纹理,来获得从光线进入表面的点(di)到光源间距离,通过从光线到光线离开表面的距离(do)里减去这个值,我们便可以获得光线转过物体内部距离长度的一个估计值(S)。如上图。
原文中详细分析了此方法的实现过程,也附带了完整的Shader源码,具体细节可以查看原文,这里因为篇幅原因就不展开了。
也有一些更高端的模型试图更精确地模拟介质内散射的累积效应。
一种模型是单次散射近似(Single Scattering Approximation),其假设光在材质中只反弹一次,沿着材质内的折射光线,可以计算有多少光子会朝向摄像机散射。当光击中一个粒子的时候,光散射方向的分布用相位函数来描述。而考虑入射点和出射点的菲涅尔效应也很重要。
另一种模型,是近似漫反射(Diffusion Approximation),其用来模拟高散射介质(如皮肤)的多次散射效果。
4.4 纹理空间的漫反射(Texture-Space Diffusion)
次表面散射最明显的视觉特征之一是模糊的光照效果。其实,3D美术时常在屏幕空间中效仿这个现象,通过在Photoshop中执行Gaussian模糊,然后把模糊图像少量地覆盖在原始图像上,这种“辉光”技术使光照变得柔和。
而在纹理空间中模拟漫反射[Borshukov and Lewis 2003],即纹理空间漫反射(Texture-Space Diffusion)是可能的,我们可以用顶点程序展开物体的网格,程序使用纹理坐标UV作为顶点的屏幕位置。程序简单地把[0,1]范围的纹理坐标重映射为[-1,1]的规范化的坐标。
另外,为了模拟吸收和散射与波长的相关的事实,可以对每个彩色通道分为地改变滤波权重。
同样,原文中详细分析了此方法的实现过程,也附带了完整的Shader源码,具体细节可以查看原文,这里因为篇幅原因就不展开了。
再贴几张基于次表面散射的皮肤渲染效果图,结束这一节。
【核心要点总结】
文中提出的次表面散射的实时近似方法,总结起来有三个要点:
1) 基于环绕照明(Warp Lighting)的简单散射近似,Oren-Nayar光照模型。
2) 使用深度贴图来模拟半透明材质的最重要特性之一——吸收(Absorption)。
3)基于纹理空间中的漫反射模拟(Texture-Space Diffusion),来模拟次表面散射最明显的视觉特征之一——模糊的光照效果。
【本章配套源代码汇总表】
Example 16-1 摘录纳入了环绕照明的皮肤Shader效果的代码(Excerpt from the Skin
Shader Effect Incorporating Wrap Lighting)
Example 16-2 深度Pass的顶点Shader代码(The Vertex Program for the Depth Pass)
Example 16-3 深度Pass的片元Shader代码(The Fragment Program for the Depth
Pass)
Example 16-4 使用深度贴图来计算穿透深度的片元Shader代码(The Fragment Program
Function for Calculating Penetration Depth Using Depth Map)
Example 16-5 用于展开模型和执行漫反射光照的顶点Shader代码(A Vertex Program to
Unwrap a Model and Perform Diffuse Lighting)
Example 16-6 用于漫反射模糊的顶点Shader代码(The Vertex Program for Diffusion
Blur)
Example 16-7 用于漫反射模糊的片元Shader代码(The Fragment Program for Diffusion
Blur)
【关键词提炼】
皮肤渲染(Skin Rendering)
次表面散射(Subsurface Scattering)
纹理空间漫反射(Texture-Space Diffusion)
环绕照明(Warp Lighting)
深度映射(Depth Maps)
五、环境光遮蔽(Ambient Occlusion)
【章节概览】
在某种意义上,这篇文章属于环境光遮蔽的启蒙式文章。
环境光遮蔽(Ambient Occlusion),简称AO,是一种用于计算场景中每个点对环境光照的曝光程度的一种着色渲染技术。
本章讲到了如何使用有效的实时环境光遮蔽技术,对物体遮蔽信息及环境进行预处理,综合这些因素给物体创建逼真的光照和阴影。
【核心内容提炼】
5.1 概述
首先,本文中讲到,环境光遮蔽(Ambient Occlusion)一般而言有两种理解:
1)将环境光遮蔽视为“智能”的环境光项,其在模型表面的变化取决于在每点可见多少外部环境。
2)将环境光遮蔽视为一个漫反射项,其能有效地支持复杂分布的入射光线。
文中将考虑上述的第二种解释。
其基本思路是,假如预处理一个模型,计算它上面每个点可以看到多少外部环境,可以相反地计算有多少环境被模型的其他部分遮挡,然后在渲染时使用这个信息计算漫反射着色项的值。其结果是模型上的裂缝变暗,而模型的暴露部分会接收更多的光线,因此更明亮。这种效果实质上比使用标准的着色模型更逼真。
另外,这个方法可以扩展为使用环境光作为照明源,用代表各个方向入射光的环境贴图没来决定物体上每个点光的颜色。为了这个特性,除了记录在点上可以看到多少外部环境之外,也记录大部分可以光从哪个方向到达。这两个量有效地定义了从外面进入场景的未被遮挡的方向圆锥体,可以一起用来做为来自环境贴图的极端模糊的查询,模拟着色点上来自感兴趣的方向圆锥体的全部入射照度。
5.2 预处理步骤(The Preprocessing Step)
给定一个任意的着色模型,环境光遮蔽算法需要知道模型上每点的两个信息:
(1)该点的“可到达度(accessibility)”- 即该点上方半球的哪一部分未被模型的其他部分遮挡;
(2)未被遮挡的入射光的平均方向。
通过下图在平面上说明这两个概念。给定在表面上的点P,其法线为N,P点上半球的2/3被场景中其他几何体遮挡,半球另外的1/3不被遮挡。入射光的平均方向用B表示,其在法线N的右侧。大致来说,在P点的入射光的平均颜色,可以通过求围绕B矢量的未遮挡入射光的圆锥体的平均值得到。
下面贴出的伪代码显示了我们的基本方法。在每个三角形的中心,我们产生一组以表面法线为中心的半球形光线,跟踪每道光线进入场景,记录哪些光线与模型相交,标志不能从环境接收的光线,以及不被遮挡的光线。接着我们计算不被遮挡的光线的平均方向,这给出了入射光平均方向的近似值。(当然,我们计算的方向实际上可能会被遮挡,但我们选择忽略不计这个问题。)
Example 17-1 计算环境光遮蔽量的基本算法伪代码 (Basic Algorithm for Computing Ambient Occlusion Quantities)
For each triangle {
Compute center of triangle
Generate set of rays over the hemisphere there
Vector avgUnoccluded = Vector(0, 0, 0);
int numUnoccluded = 0;
For each ray {
If (ray doesn't intersect anything) {
avgUnoccluded += ray.direction;
++numUnoccluded;
}
}
avgUnoccluded = normalize(avgUnoccluded);
accessibility = numUnoccluded / numRays;
}
生成这些光线的简单方法是使用拒绝采样法(rejection sampling):检测在x,y和z为-1到1区间的3D立方体中随机生成的光线,并拒绝不在单位半球中与法线相关的光线。
能通过这次检测的光线方向可视分布理想的光线方向。列表17-2的伪代码表示出了此方法的实现思路。
当然,也可以用更复杂的蒙特卡洛(Monte Carlo)采样法来得到更好的样本方向的分布。
Example 17-2 使用拒绝采样法计算随机方向的算法伪代码(Algorithm for Computing
Random Directions with Rejection Sampling)
while (true) {
x = RandomFloat(-1, 1); // random float between -1 and 1
y = RandomFloat(-1, 1);
z = RandomFloat(-1, 1);
if (x * x + y * y + z * z > 1) continue; // ignore ones outside unit
// sphere
if (dot(Vector(x, y, z), N) < 0) continue; // ignore "down" dirs
return normalize(Vector(x, y, z)); // success!
}
另外,用图形硬件代替光线追踪软件,有可能加速遮挡信息的计算。
5.3 使用环境光遮蔽贴图进行渲染(Rendering with Ambient Occlusion Maps)
使用环境光遮蔽贴图进行着色的基本思想是:
可以直接在着色点处使用之前已计算好的,有多少光线能到达表面的,优质的近似值信息。
影响这个数值的两个因素是:
(1)在此点上方半球的哪个部分不被点和环境贴图之间的几何体遮挡。
(2)沿着这些方向的入射光是什么。
下图显示了两种不同的情况。在左图中,只能看到着色点上面的一小部分分享,由方向矢量B和围绕它的方向圆锥体所表示,该点的可到达度非常低。而在右图中,沿着更大范围的方向有更多的光线到达给定点。
在预处理中计算的可访问性值告诉我们哪一部分半球可以看到环境贴图,而可见方向的平均值给出一个近似方向,围绕它计算入射光。虽然这个方向可能指向一个实际被遮挡的方向(例如,如果半球的两个独立区域未被遮挡,但其余的部分被遮挡,平均方向可能在这两者之间),但在实践中其通常运行良好。
另外需要注意,实时环境光遮蔽的常用廉价方案是预先计算网格表面几个位置的平均可见性值,存储于贴图中,然后将这些值在运行时与图形硬件提供的未遮挡光照相乘。
【核心要点总结】
给定一个任意的着色模型,环境光遮蔽算法需要知道模型上每点的两个信息:
1)该点的可到达度(accessibility)。
2)未被遮挡的入射光的平均方向。
文中提出的环境光遮蔽方法,总结起来有三个要点:
- 采用了多种在实践中运行良好的近似方法。
- 主要为预处理操作,将相对昂贵的计算事先准备好,且仅计算在渲染时进行快速着色所需的正确信息。
- 预处理不依赖于光照环境贴图,因此可以轻松使用场景中的动态照明。
【本章配套源代码汇总表】
Example 17-1 计算环境光遮蔽量的基本算法伪代码(Basic Algorithm for Computing
Ambient Occlusion Quantities)
Example 17-2 使用拒绝采样法计算随机方向的算法伪代码(Algorithm for Computing
Random Directions with Rejection Sampling)
Example 17-3 使用可到达度和环境映射进行着色的片元Shader(Fragment Shader for
Shading with Accessibility Values and an Environment Map)
Example 17-4 latlong( )函数的定义(The latlong() Function Definition)
Example 17-5 computeBlur( )函数的定义(The computeBlur() Function Definition)
【关键词提炼】
环境光遮蔽(Ambient Occlusion)
拒绝采样(Rejection Sampling)
环境光遮蔽贴图(Ambient Occlusion Maps)
六、实时辉光(Real-Time Glow)
【章节概览】
这章讲到2D光照效果中的辉光(Glow)和光晕(Halo),展示了如何通过图像处理方法完全地改善画面及3D人物的渲染感官。
【核心内容提炼】
光源的辉光(Glow)和光晕(Halo)是自然界导出可见的现象,他们提供了亮度和气氛强烈的视觉信息。
在观看计算机图形、胶片和印刷品时,到达眼睛的光强度是有限的,因此,辨别光源强度的唯一方法是通过它们周围产生的辉光(Glow)和光晕(Halo),具体可以参考[Nakamae et al.1990]。这些辉光可以再现强烈光线的视觉效果,并使观察者感知非常明亮的光源。即使物体周围的微妙光晕也会让人觉得它比没有光辉的物体更亮。
在日常生活中,这些发光和光晕是由大气中或我们眼中的光散射引起的(Spencer 1995)。使用现代图形硬件,可以通过几个简单的渲染操作来再现这些效果。这使得我们可以使用明亮而有趣的物体来填满实时渲染的场景,物体会显得更为逼真或更具表现力,并且这是克服图形渲染中传统的低动态范围图形过于平庸的优雅手段之一。
有几种方法可以创建场景中的辉光。对于小的类似的点,可以把一个平滑的“辉光”纹理应用到公告牌几何体上,而让公告板几何体在屏幕范围内跟随物体运动。
对于大的辉光源或复杂的辉光形状,要创建辉光,最好对2D场景的渲染进行后处理。这章重点讲到了后处理的实时辉光处理方法。如下图。
渲染后处理辉光的步骤:
Step 1、辉光的指定和渲染(Specifying and Rendering the Sources of Glow)
Step 2、模糊辉光源(Blurring the Glow Sources)
Step 3、适配分步卷积(Adapting the Separable Convolution)
Step 4、在GPU上进行卷积(Convolution on the GPU)
上图展示了如何有效地创建模糊的两步分解法:首先,在一根轴上模糊于(a)中的辉光源的点,产生(b)中所示的中间结果,然后在另一个轴上模糊这个结果,产生显示在(c)中的最终模糊。
(a)用标准方法渲染3D模型(b)为一个由美术同学创建的辉光源纹理的渲染,目的是指定辉光面积的图案和强度(c)为将辉光应用到标准的渲染结果后,得到的富有表现力的英雄角色效果。
另外,在辉光中使用的这个卷积和模糊方法还可以用于多种其他效果。它能用来计算景深效果的不同聚焦度,景深的信息可以用来控制模糊度。它也能用来模糊投影的纹理阴影的边缘,并且累积深度阴影映射的接近百分比过滤(percentage-closer filtering )结果。
而大面积的卷积能被应用于一个环境映射,以创建一个近似的辐照度映射,从而得到更逼真的场景照明(Ramamoorthi和Hanrahan 2001有相关论述)。用大面积的卷积也可以实现许多非真实感渲染技术和其他的特别效果。其中包括镀着霜的玻璃、模拟衍射的透镜摇曳,以及渲染皮肤时用的近似次表面散射。
大片的模糊和卷积能有效地在多种图像硬件上实时地计算,而处理和创建这些效果的代码可以容易地封装成几个C++类或一个小库。
总之,屏幕辉光是一种很赞的效果,能够容易地扩展到几乎每一种情形,并且变化多端,通过其还够延伸创建出很多其他的效果。最终的效果虽然细微但却有张力,值得在各种游戏中采用。
【核心要点总结】
渲染后处理辉光的步骤:
Step 1、辉光的指定和渲染(Specifying and Rendering the Sources of Glow)
Step 2、模糊辉光源(Blurring the Glow Sources)
Step 3、适配分步卷积(Adapting the Separable Convolution)
Step 4、在GPU上进行卷积(Convolution on the GPU)
【关键词提炼】
实时辉光(Real-Time Glow)
光晕(Halo)
后处理(Post-Processing)
图像处理(Image Processing)
第二部分 · 次核心内容提炼总结
十六、使用遮挡区间映射产生模糊的阴影(Generating Soft Shadows Using Occlusion Interval Maps)
【章节概览】
这章介绍了一种渲染软阴影的技术,称为遮挡区间映射(Occlusion Interval Maps),能够正确地在静态场景中渲染出光源沿着预定路径移动时产生的模糊阴影。之所以叫遮挡区间映射,是因为此算法使用纹理贴图来存储这种光源可见、而本身被遮挡的区间。
【核心要点】
对于需现实的加油站的Demo,文章一开始本打算使用一种预计算的可见度技术,例如球谐光照(Spherical Harmonic Lighting [Sloan et al.2002])来实现,但可惜的是无法达到目的,因为球谐光照适用的是非常低频的光照,不适用于像太阳那样小面积的光源。所以后来才开发出遮挡区间映射这种新的预计算可见度的技术,它能够支持实时太阳照射的软阴影。通过把问题简化为在固定轨道上的线性光源来达到目的。
需要注意,遮挡区间映射(Occlusion IntervalMaps)技术有一些局限性,只对沿固定轨道传播的单条光线的静态场景适用。这意味着它对人物和其他动态物体的阴影无效。但是其适用于静态户外场景中的阴影渲染。并且此技术因为遮挡区间映射对每个通道需要8位的进度,纹理压缩将导致视觉效果失真。因此,必须禁用纹理压缩,从而增加了纹理用量。
使用遮挡区间映射(Occlusion Interval Maps)技术,通过损失一定运行性能来获得在静态场景上实时运行的软阴影算法。遮挡区间映射(Occlusion Interval Maps)可以用作静态光照贴图的替代品,从而实现动态效果,可以得到从日出到日落光照明变化的动态效果。如下图。
注意加油站墙上的阴影在图形的左上方有清楚的边界,但是它朝着右下方变得模糊而柔软。这种相联系的清晰和模糊的变化是真实感软阴影的重要性质,而这是由遮挡区间映射得到的。
上图中汽车篷布上的木板形成了复杂的阴影。这对算法来说是最坏的情况。这些木头条使得篷布上的遮挡区间映射必须存储在5个不同的纹理中,对于场景中的大多数物体,4个纹理就足以取得所有的阴影。
【关键词提炼】
阴影渲染(Shadow Rendering)
软阴影(Soft Shadows )
遮挡区间映射(Occlusion Interval Maps)
十七、透视阴影贴图(Perspective Shadow Maps: Care and Feeding)
【章节概览】
透视阴影贴图(Perspective Shadow Maps, PSMs)是由Stamminger和Drettakis在SIGGRAPH 2002上提出的一种阴影贴图(Shadow Maps)流派的方法。
透视投影贴图方法的基本思想是,为了减少或消除阴影贴图的失真走样,对投射到大像素区域的物体取最大的阴影贴图纹素密度。
这章提出了一种优化透视阴影贴图(Perspective Shadow Maps)方法的新思路,对其三种缺陷都一一进行了改进。
【核心要点】
这章首先讲到动态阴影的创建,目前主要有两个算法流派:
- 阴影体(shadow volumes)/模板阴影(stencil shadows)
- 阴影贴图(Shadow Maps)
阴影体和阴影贴图算法之间的不同之处在于,是涉及到物体空间(object space)还是图像空间(image space)。
- 阴影体(Shadow Volumes)是物体空间(Object Space)的阴影算法,通过创建表示阴影遮挡的多边形结构来工作,这意味着我们始终具像素精确但较“硬”的阴影。此方法无法处理没有多边形结构的对象,比如经过alpha测试修改的几何图形或经过位移映射的几何体(displacement mapped geometry)。此外,绘制阴影体需要大量的填充率,这使得很难将它们用于密集场景中的每个对象上,特别是在存在多个灯光时。
- 阴影贴图(Shadow Maps)是图像空间(Image Space)的阴影算法,它可以处理任何物体(如果能够渲染一个物体,就能得到它的阴影),但是存在走样(aliasing,锯齿)的问题。走样时常发生在有较宽或全方位光源的大场景中。问题在于阴影映射中使用的投影变换会改变阴影贴图像素的大小,因此摄像机附近的纹理像素变得非常大。因此,我们必须使用巨大的阴影贴图(四倍于屏幕分辨率或更高)来实现更高的质量。尽管如此,阴影贴图在复杂场景中却比阴影体要快得多。
透视阴影贴图(Perspective Shadow Maps, PSMs)是由Stamminger和Drettakis 在SIGGRAPH 2002上提出的一种阴影贴图(Shadow Maps)流派的方法,通过使用在投射后空间(post-projective space)中的阴影贴图来去除其中的走样,而在投射后空间中,所有近处的物体都比远处的大。不幸的是,使用原始算法很困难,因为只有要某些情况下才能正常工作。
以下是透视阴影映射算法的三个主要问题和解决方案:
1、当光源在摄像机后面的时候,有一个虚拟的摄像机锥体。若在锥体内保持所有潜在的阴影投射体,阴影质量就会变得很差。
解决方案:是对光源矩阵使用特别的投射变换,因为投射后空间可以使用某些在通常空的世界空间中不能做的投射技巧。它使我们可以建立特殊的投射矩阵,可以看做“比无限远更远”。
2、光源在摄像机空间中的位置对阴影质量影响很大,对于垂直的方向光,完全没有走样问题,但是当光源朝向摄像机并迎面靠近它时,阴影映射走样就很严重。
解决方案:把整个单位立方体保持在一个阴影贴图纹理中,对于遇到的问题,有两个办法,每个办法仅解决问题的一部分:单位立方体裁剪法,把光源摄像机对准单位立方体的必要部分;立方体映射法,使用多个纹理来存储深度信息。
3、最初的文章没有讨论过偏置(bias)问题。偏置是随透视阴影贴图而带来的问题,因为纹素的面积以不均匀方式分布,这意味着偏置不再是常量,而是与纹素的位置有关。
解决方案:使用在世界空间中的偏置(而且不再分析双投射矩阵的结果),然后把这个世界空间偏置转换到投射后空间。
【关键词提炼】
阴影渲染(Shadow Rendering)
阴影贴图(Shadow Maps)
透视阴影映射(Perspective Shadow Maps,PSMs)
紧邻百分比过滤(percentage-closer filtering ,PCF)
单位立方体裁剪法(Unit Cube Clipping)
十八、逐像素光照的可见性管理(Managing Visibility for Per-Pixel Lighting)
【章节概览】
这章讲到了可见性在逐像素渲染光照场景中的作用,也考虑如何使用可见性减少必须渲染的批次数量,从而改善性能。
【核心要点】
如下伪代码说明在一个场景中必须渲染的批次数:
For each visible object
For each pass in the ambient shader
For each visible batch in the object
Render batch
For each visible light
For each visible shadow caster
For each pass in the shadow shader
For each shadow batch in the object
Render batch
For each lit visible object
For each pass in the light shader
For each visible batch in the object
Render batch
正如伪代码所述,为了减少批次数,可以进行一些与非可见性相关的优化。最应该优化的是渲染每个光照所必须的通道数。批次数随通道数线性增加,因此,我们应该最小化受限于CPU的游戏通道数。
我们可以使用可见性来减少批数。其中,为了减少批次,各个部分(可见部分、光源部分、光照部分、阴影部分)的集合分开讨论并生成。
可见性不仅能有效改善CPU的性能,也同样可以改善GPU的性能。对模板体执行逐像素光照时,填充率的消耗(模板体的填充或多次渲染大的物体)很快就变成了瓶颈,但可以使用剪切矩形(scissor rectangle)限制显卡渲染的面积,解决此问题。
逐像素的照明需要大量的批次数和极高的填充率,所以要减少渲染的物体数和它们影响的屏幕面积。而使用这章中介绍的标准可见性算法和技术,可以充分改善运行性能。
【关键词提炼】
逐像素光照(Per-Pixel Lighting)
可见性管理(Managing Visib1ility)
性能优化(Performance Optimization)
批次(Batch)
十九、空间BRDF(Spatial BRDFs)
【章节概览】
这章主要先聊到了空间双向反射分布函数(SBRDF),接着文章讨论了压缩SBRDF表达式,以及由离散光或环境贴图所照明的SBRDF的渲染方法。
【核心要点】
SBRDF是纹理贴图和双向反射分布函数(BRDF)的组合。纹理贴图存储了反射或其他的属性,它们沿着2D表面上的空间变化,而BRDF存储的是表面上单个点的反射,包括从入射角到出射角的全部分布。
SBRDF对标准点光源或方向光源照明的SBRDF表面,文中直接贴图了Shader源码,具体可以参考原文。
SBRDF除了可以用点光源或方向光源照明之外,还可以用环境贴图中所有方向的入射光进行照明。关键是在渲染前用BRDF的一部分卷积环境贴图。对于大多数的BRDF表达式,必须分别处理各个不同的BRDF。但因为一个SBRDF可能有上百万个不同的BRDF,所以这样做不可能。这篇文章采取的的做法是,简单地用一个Phong叶片卷积环境贴图,叶片可以选择不同的镜面指数,如n=0、1、4、16、64、256、这些贴图能存储在不同级别的立方体mipmap中。随后,SBRDF纹素的n值就指细节层次(LOD),用于在立方体贴图中采样适当mipmap级别。
【关键词提炼】
双向反射分布函数(BRDF)
空间双向反射分布函数(SBRDF)
离散光(Discrete Lights)
环境贴图(Environment Maps)
二十、基于图像的光照(Image-Based Lighting)
【章节概览】
这篇文章打破了当时立方体贴图环境(Cube-Map Environment)用法的桎梏,深入研究了更多可能的逼真光照效果。文章主要研究了基于图像的光照(Image-Based Lighting,IBL),包括局部化的立方体映射,类似于使用基于图像的局部光照(Localizing
Image-Based Lighting),然后介绍了如何把哪些重要的技巧用于着色模型,包括逼真的反射、阴影和漫反射/环境项。
【核心要点】
立方体贴图通常用于创建无限远环境的反射效果。但是使用少量Shader算法,我们可以将物体放置在特定大小和位置的反射环境中,从而提供高质量的基于图像的光照(Image-Based
Lighting,IBL)。
在室内环境移动模型时,最好是使用近距离的立方体贴图,距离的大小与当前的房间类似。当模型在房间移动时,根据模型在房间中的位置,适当地放大或缩小放射。这种方法得到的模拟效果使人感到更为可靠和逼真。尤其在包含窗户,屏幕和其他可识别光源的环境中。而只要加入很少的Shader数学就能将反射局部化。具体可以看原文贴出的Shader源码。
另外,我们可以将3D几何体做成立方体贴图,并且在正常地渲染环境的时候,把贴图应用到该环境的物体上。也可以使用贴图作为环境,把它投射到较简单的几何体上。
立方体贴图也能用来决定漫反射光照。Debevec的HDRShop程序能够从映射立方体光照环境积分出全部的漫反射贡献度,那么通过把表面法线带入预先卷积的立方体贴图,能够简单地查询漫反射贡献。
基于图像的光照为复杂的光照计算提供了综合而廉价的替代品,将一点数学加入纹理方法,可以大大拓宽“简单”IBL效果,给3D图像提供更强的的方位感。
【关键词提炼】
基于图像的光照(Image-Based Lighting,IBL)
立方体贴图环境(Cube-Map Environment )
基于图像的局部光照(Localizing Image-Based Lighting)
二十一、纹理爆炸(Texture Bombing)
【章节概览】
这章介绍了纹理爆炸(Texture Bombing)和相关的细胞技术,它们能在Shader中增加视觉的丰富性,图像的多样性,并减少大块纹理图案的重复性。
【核心要点】
纹理爆炸(Texture bombing)是一种程序化技术,它把小块图像以不规则的间隔放置。有助于减少团案的失真。
纹理爆炸的基本思想是把UV空间分为规则的单元栅格。然后使用噪声或者伪随机函数,把一个图像放在任意位置上的各个单元中。最终的结果是在背景上对这些图像的合成。
由于要组合数以百计的图像,因此实际上这种合成(composite)图像的方法效率并不高。而程序化(Procedural
)计算图像虽好,但是又不适合合成。这篇文章主要讲了图像合成和程序化生成这两种方法,可以发现他们各有优劣。
很显然,纹理爆炸也可以扩展到3D中,即3D程序化爆炸(Procedural 3D Bombing)
纹理爆炸有一种有趣的变化是在平面上画Voronoi区域。简言之,给定一个平面和那个平面上的一系列的点,接近那个点的面积就是点的Voronoi区域。Voronoi图案类似于树叶和皮肤上的单元形状、龟裂的泥土或爬虫类的皮。如下图。
总之,纹理爆炸和相关的细胞技术可以给Shader增加视觉的多样性。使用存储在纹理中的伪随机数表和一个小程序,可以增大一个图像或一组图像的变化,并减少大块纹理区域的重复。
【关键词提炼】
纹理爆炸(Texture Bombing)
3D程序化爆炸(Procedural 3D Bombing)
Voronoi区域(Voronoi Region)
二十二、颜色控制(Color Controls)
【章节概览】
这章将在游戏中图像处理的讨论,扩展到技术和艺术上控制颜色的方法和应用,包括将图像从一些的色彩空间中移入移出,以及快速地给任何2D或3D场景加上精美的色调。
【核心要点】
色彩校正(Color Correction)是几乎所有印刷和胶片成像应用的一部分。色彩校正可用于将彩色图像从一个色彩空间移动到另一个色彩空间。
我们在电视、杂志和电影中剪刀的大部分图像,都经过了非常小心的彩色校正和控制。对于这个过程的理解,可以帮助开发者在实时应用程序中得到同样华美的视觉效果。
色彩校正通常有两种做法:一是各个通道的校正,分别是改变红色、绿色和蓝色各成分;二是混色操作,基于红、绿、蓝各个成分的同时操作,得到每个通道的输出值。
色彩校正的机理可以简洁而容易地在一个shader中描述。重要的是,美术和程序员使用的普通工具就能有效地控制他们。在这章中,运用Photoshop创建控制资源,然后通过像素shader应用到实时程序中。
在Photoshop中提供了一些基于通道校正的工具。如级别(levels)和曲线(Curves)工具。
其中曲线是仿制了化学中的交叉处理(cross-processing)外观,确切地说,就是在C41化合物中处理E6叫绝所产生的假颜色外观。这样的处理已在印刷、电影和电视领域使用多年。
可以使用下面几行shader代码运用于输出颜色,使用色彩校正纹理映射,可以随意地用曲线工具重新创建任何色彩变化:
float3 InColor = tex2D(inSampler, IN.UV).xyz;
float3 OutColor;
OutColor.r = tex1D(ColorCorrMap, InColor.r).r;
OutColor.g = tex1D(ColorCorrMap, InColor.g).g;
OutColor.b = tex1D(ColorCorrMap, InColor.b).b;
也就是说,使用每个原始的红、绿和蓝像素的灰度值,确定在梯度纹理中我们寻找的相关位置,然后由梯度纹理本身定义对新颜色的重映射,即由复杂曲线调节所定义的新颜色,如下图。
【关键词提炼】
颜色控制(Color Controls)
色彩校正(Color Correction)
基于通道的颜色校正(Channel-Based Color Correction)
灰度变换(Grayscale Conversion)
色彩空间变换(Color-Space Conversions)
图像处理(Image Processing)
二十三、景深 (Depth of Field)
【章节概览】
本章主要介绍如何使用GPU创建实时的景深(Depth of Field)效果。
【核心要点】
物体在距离镜头的一个范围之内能够清晰成像(经过聚焦),在那个范围之外(或近或远)则成像模糊,这种效果就是景深。在相机业和电影业中,景深经常用来指示对场景的注意范围,并且提供场景深度的感觉。在本章中,把这个聚焦范围远的区域称为背景(background),在这个范围前的区域称为前景(foreground),而在范围外的面积称为中景(midground)。
景深效果由透镜的物理性质产生。若要穿过摄像机透镜(或人眼镜的晶体)的光辉聚到胶片(或人的视网膜)上的一个点,光源必须与透镜有着特定的距离。在这个距离上的平面称为焦平面(plane in focus)。不在这个精确距离上的任何东西,投影到胶片上的区域(而不是一个点)称为模糊圈(circle of confusion,CoC)。Coc的直径与透镜尺寸和偏离焦平面的距离成正比。偏离距离小到一定程度,CoC会变得比胶片的分辨率更小,摄影师和摄影师称这个距离为聚焦(in focus),而在这个范围之外的任何东西都是没有对准聚点的(out of focus,模糊的)。如下图。
这章中主要综述了5种近似景深效果的技术。
1、基于光线追踪的景深(Ray-Traced Depth of Field)[Cook et al. 1984]
2、基于累积缓冲的景深(Accumulation-Buffer Depth of Field)[Haeberli and Akeley
1990]
3、分层景深(Layered Depth of Field)[Scofield 1994]
4、前向映射的Z缓冲景深(Forward-Mapped Z-Buffer Depth of Field) [Potmesil and
Chakravarty 1981]
5、反向映射的Z缓冲景深(Reverse-Mapped Z-Buffer Depth of Field)[ Arce and Wloka
2002, Demers 2003]
【关键词提炼】
景深(Depth of Field)
基于光线追踪的景深(Ray-Traced Depth of Field)
基于累积缓冲的景深(Accumulation-Buffer Depth of Field)
分层景深(Layered Depth of Field)
前向映射的Z缓冲景深(Forward-Mapped Z-Buffer Depth of Field)
反向映射的Z缓冲景深(Reverse-Mapped Z-Buffer Depth of Field)
图像处理(Image Processing)
二十六、OpenEXR图像文件格式与HDR(The OpenEXR Image File Format and HDR)
【章节概览】
这章中,大名鼎鼎的工业光魔公司的Florian Kainz、Rod Bogart和Drwe
Hess介绍了OpenEXR标准,这是一种当时新的高动态范围图像(HDRI)格式,在计算机成像的顶级电影中正在快速推广。对于基于图像照明的开发者而言,OpenEXR是关键的工具。
【核心要点】
OpenEXR是由工业光魔( Industrial Light & Magic,ILM )公司开发的高动态范围图像( high-dynamic-range image,HDRI)文件格式。OpenEXR网站是 http://www.openexr.org ,上面有关于此格式的全部细节。
下图是一个例子,说明了需要HDR存在的原因。
如下图是一张显示相当高的动态范围的场景,场景中左边的油灯的火焰比中间小盘子下的阴影大约亮100000倍。
图像曝光的方式导致了一些区域的亮度超过了1.0,在计算机显示屏上,这些区域被裁剪(clipped)掉,并显示为白色或不自然的饱和桔色色调。
我们可以通过把图像变暗来校正白色和橘色区域,但是如果把原始图像存储在低动态范围文件格式中,如JPEG格式,把它变暗就会产生相当难看的图像。如下图。
而如果原始图像存储在高动态范围文件格式中,如OpenEXR,保存明亮的像素值,而不是把他们裁剪到1.0,然后把图像变暗,就可以产生依旧自然的效果。如下图。
文章随后还讲到了OpenEXR的文件结构、数据压缩、使用、线性像素值、创建和使用HDR图像相关的内容。有兴趣的同学可以查看原文,这里就不再赘述了。
【关键词提炼】
高动态范围(High-Dynamic-Range , HDR)
高动态范围图像(High-Dynamic-Range Image,HDRI)
OpenEXR
至此,《GPU Gems 1》全书核心内容提炼总结 ,上下两篇,完。
下次更新见。
With best wishes.