毛星云 网名「浅墨」,微软MVP,《Windows游戏编程之从零开始》作者
本文将总结提炼“GPU精粹三部曲“11本书中的第一本《GPU Gems 1》全书的核心内容,是【GPU精粹与Shader编程】系列文章正篇的第一篇,全文共1万5千余字。
本文内容关键词:
真实感水体渲染(Realistic Water Rendering)
真实感皮肤渲染(Realistic Skin Rendering)
无尽草地的渲染(Rendering Countless Blades of Waving Grass)
水焦散渲染(Rendering Water Caustics)
面部表情模拟(Facial expression simulation)
Perlin噪声(Perlin Noise)
火焰的渲染(Fire Rendering)
衍射的模拟(Simulating Diffraction)
阴影的渲染(Shadow Rendering)
电影级光照(Cinematic Lighting)
阴影贴图抗锯齿(Shadow Map Antialiasing)
全方位阴影贴图(Omnidirectional Shadow Mapping)
GitHub上已更新完整的上下两篇
GitHub上自然是不会像知乎专栏这样有单篇文章1万多字字数限制的,所以全部的3万字已经更新,想提前看3万字完整版的同学请戳GitHub链接:
QianMo/Game-Programmer-Study-Notesgithub.com
系列文章风格说明
为了让每篇文章的干货更足,内容更加详实,本文与后续文章的的写作风格与文章结构,将在系列文章开篇中的规划的写作风格的基础上,做一些微调。
具体是将每篇需要提炼的章节内容分为两大部分:
主核心内容
次核心内容
其中,主核心内容,将选取大家最感兴趣、最有提炼价值的渲染相关的内容,会用更加详细的篇幅进行提炼总结。每章主核心内容将包含五个部分:
【章节概览】
【核心内容提炼】
【核心要点总结】
【本章配套代码汇总表】
【关键词提炼】
而次核心内容,则会用更加精炼的篇幅进行总结。每部分将包含:
【章节概览】
【核心要点】
【本章配套代码汇总表】
【关键词提炼】
目录 · 核心内容导航Highlight
【说明】下文目录中加粗标题为本文将包括的内容,非加粗的标题将在下次的文章中发布。
对《GPU Gems 1》全书核心内容,将进行重点提炼总结的主核心内容有:
一、用物理模型进行高效的水模拟(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)
对《GPU Gems 1》全书核心内容,将进行提炼总结的次核心内容有:
六、水焦散的渲染 (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)
二十一、实时辉光(Real-Time Glow)
二十二、颜色控制(Color Controls)
二十三、景深 (Depth of Field)
二十四、高品质的图像滤波(High-Quality Filtering)
二十五、用纹理贴图进行快速滤波宽度的计算(Fast Filter-Width Estimates with
Texture Maps)
二十六、OpenEXR图像文件格式(The OpenEXR Image File Format)
《GPU Gems 1》其书
《GPU Gems 1》英文原版出版于2004年4月,中文版《GPU精粹1》出版于2006年1月。需要说明的是,书中很多内容放到今天,并不过时,仍然很有研究、学习、运用、实践的价值。尤其是水体渲染,皮肤渲染,次表面散射、阴影渲染、后处理相关的章节。
图 《GPU Gems 1》封面
图 全书内容概览图
《GPU Gems 1》随书配套资源与源代码下载
这一节提供了《GPU Gems 1》随书的配套资源,以及源代码的下载地址。
PS:配套的不少工程中不仅包含完整的源码,也直接包含经过编译后的exe执行文件,可以直接运行后查看效果。
原书全文的Web版本:
NVIDIA官方网站上1~3 Web版的url变更有几次了,上面的链接,是目前最稳定版本。
原书配套源代码、工程与资源下载:
GPU Gems CDhttp.download.nvidia.com
图 配套资源的截图
我维护了的一个名为“GPU-Gems-CD-Content”的GitHub仓库,以备份这些珍贵的资源,也方便直接在GitHub Web端查看大牛们写的代码:
QianMo/GPU-Gems-CD-Contentgithub.com
第一部分 · 主核心内容提炼总结
一、 用物理模型进行高效的水模拟(Effective Water Simulation from Physical Models)
【内容概览】
本章介绍了在GPU中模拟和渲染大型水体的一些方法,并且提出了改进反射的一些有用技巧。
文章由计算简单的正弦函数之和来对水面进行近似模拟开始,逐步扩展到更复杂的函数如Gerstner波,也扩展到像素着色器。主要思路是使用周期波的加和,来创建动态的平铺(tiling)凹凸贴图,从而获得优质的水面细节。
这章也集中解释了水体渲染与模拟系统中常用参数的物理意义,说明了用正弦波之和等方法来近似水面的要点。
图 基于文中水体技术渲染的Uru:Ages Beyond Myst中的场景
【核心内容提炼】
1.1 背景与范围
《GPU Gems 1》出版于2004年,在这几年间,实时渲染技术渐渐从离线渲染领域中分离,自成一派。
而《GPU Gems 1》中收录的这篇文章问世期间,快速傅里叶变换(Fast Fourier
Transform,FFT)库已经能用于顶点和像素着色器中。同时,运行于GPU上的水体模拟的模型也得到了改进。Isidoro等人在2002年提出了在一个顶点着色器中加和4个正弦波以计算水面的高度和方位的思路。另外,Laeuchi在2002年也发表了一个使用3个Gerstner波计算水面高度的着色器。
图 基于快速傅里叶变换的水体渲染
1.2 水体渲染的思路
文中对水体渲染的思路,运行了两个表面模拟:一个用于表面网格的几何波动,另一个是网格上法线图的扰动。这两个模拟本质上是相同的。而水面高度由简单的周期波叠加表示。
正弦函数叠加后得到了一个连续的函数,这个函数描述了水面上所有点的高度和方向。在处理顶点时,基于每个顶点的水平位置对函数取样,使得网格细分形成连续水面。在几何分辨率之下,将该技术继续应用于纹理空间。通过对近似正弦叠加的法线取样,用简单像素着色器渲染到渲染目标纹理(render target texture),从而产生表面的法线图。对每帧渲染法线图,允许有限数量的正弦波组相互独立地运动,这大大提高了渲染的逼真度。
而直接叠加正弦波产生的波浪有太多的“簸荡(roll)”,而真实的波峰比较尖,波谷比较宽。事实证明,正弦函数有一个简单的变体,可以很好地控制这个效果。
1.2.1 波的选择
对于每个波的组成,有如下几个参数需要选择:
波长Wavelength (L):世界空间中波峰到波峰之间的距离。波长L与角频率ω的关系为
ω=2π/L。
振幅Amplitude (A):从水平面到波峰的高度。
速度Speed (S):每秒种波峰移动的距离。为了方便,把速度表示成相位常数 φ=S x
2π/L。
方向Direction (D):垂直于波峰沿波前进方向的水平矢量。
波的状态定义为水平位置(x,y)和时间(t)的函数:
图 单个波函数的参数
而包括所有的波i的总表面是:
为了提供场景动力学的变量,我们将在约束中随机产生这些波的参数,随着时间的变化,我们会不断将某个波淡出,然后再以一组不同的参数将其淡入。且此过程的这些参数是相关联的,必须仔细地产生一套完整的参数组,才能使各个波以可信的方式进行组合。
1.2.2 法线与切线
因为我们的表面有定义明确的函数,所以可以直接计算任意给定点处的曲面方向,而不是依赖于有限差分技术。
副法线(Binormal)B和正切矢量T分别是x和y方向上的偏导数。
对于2D水平面中的任意点(x,y),表面上的三维位置P为:
求副法线(Binormal)B方向,即对上式对x方向求偏导。而求正切矢量T方向,即对上式对y方向求偏导。
而法线N由副法线B和切线T的叉积给出:
1.3 波的几何特征
首先文中将几何波限制为4个,因为添加更多的波并不能增加新的概念,只不过增加更多相同的顶点Shader处理指令和常数而已。
1.3.1 方向波或圆形波的选择
需要对下图所示的方向波或圆形波进行选择。
图 方向波和圆形波
对于两种类型的波,视觉特性和复杂性都是由干涉条纹引起的。
方向波需要的顶点shader处理指令较少,但是究竟选择何种波需要取决于模拟的场景。对于大的水体,方向波往往更好,因为它们是风吹动产生的波较好的模型。对于较小的池塘的水,产生波的原因不是由于风,而是诸如例如瀑布,水中的鱼,圆形波则更好一些。对于方向波,波的方向是在风向的一定范围内任意绘制的;对于圆形波,波中心是在某些限定的范围内任意绘制的。
1.3.2 Gerstner波
正弦波看起来圆滑,用于渲染平静的,田园诗般的池塘很合适。而对于粗犷的海洋,需要形成较尖的浪头和较宽的浪槽,则可以选择Gerstner波。
Gerstner波早在计算机图形学出现之前就已经被研发了出来,用于物理学基础上为海水建模。Gerstner波可以提供一些表面的微妙运动,虽然不是很明显但是却很可信(具体可见[Tessendorf 2001])。
另外,Gerstner波有一种经常被忽略的性质:它将顶点朝着每个浪头顶部移动,从而形成更尖锐的波峰。因为波峰是我们水表面上最锐利的(即最高频率,最主要)特征,所以我们正希望顶点可以集中在此处。
图 Gerstner波
图 基于Gerstner渲染出的水面 @Unreal Engine 4
1.3.3 波长等参数的选择
波长等参数的选择方法:
波长的选择,要点是不要追求波在真实世界中的分布,而是要使用现在的少数几个波达到最大效果。对波长相似的波进行叠加可以突显水面的活力。于是文中选择中等的波长,然后从它的1/2至两倍之间产生任意波长。
波的速度,通过波长,基于公式即可计算得出。
振幅方面,主要是在Shader中指定一个系数,由美术同学对波长指定对应的合适振幅。
波的方向,运动方向与其他参数完全独立,因此可以自由选择。
1.4 波的纹理特征
加和到纹理中的波也像上文说到的顶点一样需要参数化,但是其具有不同的约束条件。首先,在纹理中得到宽频谱更为重要。其次,在纹理中更容易形成不像天然波纹的图案。第三,对给定波长只有某些波方向能保证全部纹理的平铺(tiling)。也就是说,不像在世界空间中仅仅需要注意距离,在纹素(texel)中要注意所有的量。
文中的思路是在2到4个通道中,使用15个频率和方位不同的波进行处理。虽然4个通道听起来有点多,但是它们是进行256 x 256分辨率的渲染目标纹理的处理,而不是处理主帧的帧缓冲。实际上,生成法线贴图的填充率所造成的影响小到可以忽略不计。
1.5 关于深度
首先,把在顶点上的水深度作为一个输入参数,这样,在着色器碰到岸边这样的微妙区域时,便可以自动进行校正。
因为水的高度需要计算,所以顶点位置的z分量就没什么用了。虽然我们可以利用这点来压缩顶点的数据量,但是选择把水深度编码在z分量中,是一个更好的选择。
更确切地说,就是把水体底部的高度放在顶点的z分量中,作为常数带入水的高度表中,这样通过相减,即可得到水深度。而同样,这里假定了一个恒定高度的水位表(constant-height
water table)。
我们也使用水深度来控制水的不透明度、反射强度和几何波振幅。简单来说,即水浅的地方颜色浅,水深的地方颜色深。有了适当的水深度,也就可以去光的传播效果进行更完善的建模。
图 真实感水体渲染效果图 @Unreal Engine 4
【核心要点总结】
文中提出的水体渲染方法,总结起来有三个要点:
1)使用周期波(正弦波、Gerstner波)的加和
2)创建动态的平铺(tiling)贴图
3)使用凹凸环境映射(Bump-Environment Mapping)
【本章配套代码汇总表】
文中并没有贴出相关代码,但原书配套CD提供了完整的源代码和项目工程,具体代码和工程可以查看:
QianMo/GPU-Gems-CD-Contentgithub.com
【关键词提炼】
水的模拟(Water Simulation)
水的渲染(Water Rendering)
正弦函数近似加和(Sum of Sines Approximation)
Gerstner波(Gerstner Waves)
凹凸环境映射(Bump Environment Mapping)
二、Dawn Demo中的皮肤渲染(Skin in the Dawn Demo)
十年技术变迁: NVIDIA Dawn Demo
最初的Dawn Demo由NVIDIA于2002年发布,而十年之后的2012年,NVIDIA新发布了“A New Dawn”技术Demo。
图 A New Dawn Demo截图
以下是一张新老Demo的对比效果图。
图 Dawn Demo (2002年)
图 A New Dawn Demo (2012年)
图 技术指标的对比
【章节概览】
这章详细介绍了NVIDIA出品的Dawn Demo中对精灵人物的着色技术,主要是皮肤的着色技巧。在当时(2002年)NVIDIA创造的此demo的品质,已经成为照片级真实感渲染和实时渲染的代表。
图 Dawn Demo截图
【核心内容提炼】
2.1 关于皮肤着色
基于多种原因,在计算机图形中模拟皮肤十分困难。在当时,即使是在电影中用高端产品模拟出来的仿真角色,通常也经不起近距离的观察。因为,人类可以从中获得大量非语言来表达的信息,如重心的移动,走动的特别习惯,面部的表情,甚至有些人的皮肤泛红等等。
虽然很少有人能理解像“次表面散射(Subsurface Scattering)”、“轮廓照明(Rim
Lighting)”这些词汇,但是当把它们渲染错了的时候,几乎任何人都可以指出来。而且除了着色问题外,有时人们会因为皮肤渲染的问题,说皮肤看起来像是塑料做的。
2.2 皮肤如何对光进行响应
皮肤不像大多数在计算机渲染中建模的表面,因为它是由半透明的表皮、真皮和皮下组织等数层构成的。这可以用次表面散射来模拟。这种现象很普遍,当在太阳面前向上举起手,就能看到穿过皮肤的桔红色的光。
图 次表面散射-穿过皮肤的桔红色的光
皮肤下的散射在所有的角度上显现皮肤形态,使它具有了柔软的、与众不同的特征。
在这之前有一些小组尝试使用多层纹理贴图来模仿皮肤的复杂性,但一般而言,这个方法比较难管理,美术同学很难通过预想,混合出最终符合预期的效果。
相反,文中使用单张彩色贴图,通过着色程序来增加色彩的变化。
图 Dawn头部的前半边的漫反射贴图
另外,皮肤具有一些极细微的变化,会影响其反射特性。这对皮肤外观有微妙的影响,特别是当光线直接与相机位置相反时,皮肤的表现则是存在边缘(Edge)与轮廓光照(Rim Lighting),这时,需要皮肤轮廓边缘的光照,或给皮肤边缘加上光晕。
真正的皮肤具有一些细微的特征,比如汗毛和毛孔能捕捉光线。尽管这些细节用于显式地建模是太不明显了,但我们还是希望得到一个合适、整体更逼真的皮肤渲染外观。在特写时,可以增加凹凸贴图,提供一些额外的细节,特别是一些小的皱纹。但需要注意,我们想要的是柔软的皮肤外观,而不是光闪闪的油腻的塑料。另外,凹凸贴图通常只需静距离特写时才可见。
我们可以通过建模来近似这两个着色属性,建模可以是基于表面法线的简单公式,或者是基于光线或视线矢量的简单公式。
通过认识,我们可以将上述两种渲染特性(次表面散射和边缘光照),建模为基于表面法线和照明或观察向量的简单公式,从而近似出两种着色属性。尤其是沿着Dawn的轮廓边缘,对她身后的光线取样,按照观察向量的索引,让“穿过”Dawn的光与她的基础皮肤色调混合,从而创建次表面散射和边缘光照的着色效果。尤其是背景图中更加明亮的区域。如下图。
图 Dawn的头部前面的切线空间法线贴图(凹凸贴图)
2.3 场景的照明
Dawn Demo中场景的照明使用了基于图像的光照(Image Based Lighting ,
IBL),创建高动态范围(High-Dynamic Range,HDR))的全景,使用环境映射贴图(Environment Maps)进行场景的照明。
图 立方体环境反射贴图
漫反射环境贴图(Diffuse Environment Map)也是一个立方体映射贴图,它使用网格表面的法线作为索引。每个像素存储了相应法线与入射光夹角的余弦加权平均值。
图 漫反射环境贴图(Diffuse Environment Map)
镜面高光环境贴图(Specular Environment Map)同样也是一个立方体映射贴图,使用反射矢量作为索引(类似于立方体映射)。把此镜面高光环境贴图基于粗糙因子进行模糊,目的是模拟对任何表面任何给定点上的法线的改变。
图 镜面高光环境贴图(Specular Environment Map)
存在的问题是,漫反射环境贴图(Diffuse Environment Map)和镜面高光环境贴图(Specular Environment Map)考虑了来自环境的入射光,但不包含由物体引起的阴影。
要解决这个问题,可以生成一个遮挡项,用来近似表达在每个顶点上半球辐射光中,有多大比率场景中其他物体所遮挡。
2.4 实现
Dawn Demo中,毫无悬念地使用顶点着色器和像素着色器进行光照处理,顶点shader的主要功能是将坐标转换到投影空间,并执行那些不能在像素着色器中执行的数学运算。
采用单通道(one-pass)的光照解决方案,不需要另外其他的通道渲染,或alpha混合来创建皮肤表面。
文中提供了完整的顶点Shader和像素Shader的源代码,这里因为篇幅原因不再赘述,具体可以参考原文(PS:上文有贴出Web版的英文全书原文的链接)。
【核心要点总结】
文中采用的皮肤渲染方法,总结起来有三个要点:
1)基于图像的光照(Image Based Lighting , IBL),采用高动态范围(High-Dynamic-Range , HDR)光照环境映射贴图
2)次表面散射(Subsurface Scattering)
3)对皮肤边缘加上光晕,即轮廓照明/边缘光照(Rim Lighting)
【本章配套代码汇总表】
Example 3-1. 从CPU应用程序接收的每个顶点数据示例代码(The Per-Vertex Data
Received from the CPU Application)
Example 3-2. 输出顶点的数据结构示例代码(The Data Structure of the Output
Vertices)
Example 3-3. Dawn脸部的皮肤渲染顶点着色器示例代码(A Sample Vertex Shader for
Dawn's Face)
Example 3-4. Dawn脸部的皮肤渲染片元着色器代码(The Fragment Shader for Dawn's
Face)
【关键词提炼】
皮肤渲染(Skin Rendering)
次表面散射(Subsurface Scattering)
轮廓照明(Rim Lighting)
基于图像的光照(Image Based Lighting ,IBL)
高动态范围(High-Dynamic-Range, HDR)
环境映射贴图(Environment Maps)
三、无尽波动的草地叶片的渲染(Rendering Countless Blades of Waving Grass)
【章节概览】
这章关于巨量自然元素的渲染,特别是对于无尽波动的草地叶片的渲染。作者对Codecreatures demo中首次成形的技术进行了扩展,使其能够高性能的渲染,以更好地适应游戏引擎的需要。
图 Realistic Grass Field @Giovanni Baer
【核心内容提炼】
3.1 概述
首先,需要意识到,对单个草叶的细节建模意义不大,因为那样大片草地需要的多边形数目会太多。
所以,我们必须建立一个符合以下条件的简单而有用的替代方案:
许多草的叶片必须由少数多边形表示。
草地必须从不同的视线看起来显得密集。
而要做到让场景不依赖于摄像机的位置和方向,可以把一些草叶组合起来,表示在一个纹理中,并将多个纹理组合起来,且在结果中单个的多边形不应该引起注意。当观察者四处活动时,通过将草体加入混合操作或者移除混合操作,以在距离范围内增加或删去草体,来保证整个草地的渲染效果具有稳定的视觉质量。
3.2 草的纹理
草的纹理,应该是一些一簇一簇聚集丛生的草,否则,会出现大片的透明区域。
需在透明的alpha通道上画实体草茎。在彩色通道中,用深浅不同的绿色和黄色,来较好地区别各个单独的叶片,也应该模拟不同情况的草叶:长得好的和长得差的、老的和嫩的,甚至区别叶片的前面与后面。
下图是一个草地纹理的示例。
图 草地纹理的示意图
3.3 草体
这一部分将探讨总结如何对多边形进行组合,并用上文提到的草地纹理进行映射,以模拟出茂密的草地,并且不凸显个别多边形。此技术也保证了单个多边形不可见。
因为用户能自由地在场景中游玩,下图所示的结构便不能产生令人信服的效果。
图 线性排布
对于线性排布,如果从垂直于多边形的方向观看场景,就会立刻穿帮,看出草地多边形的结构是线性排布的。另外这种情况下草地会看起来非常稀疏。只有在摄像机自动导航,或者渲染无法到达的远距离草地时,才会考虑这样的排布。
为了保证独立于当前视线的良好视觉质量,我们必须交叉地排布草地多边形。已证明,使用星型结构是非常好的。下图给出了“草体”可能的两种变体。
他们由3个相交的方块构成。我们必须禁用背面剔除来渲染多边形,以保证双面都可见。为了得到合适的照明度,应该让所有顶点的法线方向与多边形的垂直边平行。这保证了位于斜坡上的所有草体都可以得到正确的光照,不会因为地形的亮度而出现差异。
图 草体的交叉排布
如果把这些草地物体彼此相当靠近地设置在一个大的区域里,如下图。在运行期间把它们从后向前进行排序,使用alpha混合,并启用Draw
Call中的z-testing/writing,那么就会得到自然而茂密的草地渲染效果。
图 草地的扩展
3.4 草地的动画
关于草地的动画,基本思想是以三角函数(尤其是正弦和余弦)为基础进行计算,且计算应该考虑到移动的位置和当前时间、风向和强度。
以基本思想为基础,实现起来有几种方法:
1)每草丛草体的动画(Animation per Cluster of Grass Objects)
2)每顶点的动画(Animation per Vertex)
3)每草体的动画(Animation per Grass Object)
三种方法各有优缺点,而文中都给出了具体算法步骤和实现的Shader源码,这里因为篇幅原因,便不展开分析了,具体可以参阅原文。
最终可以实现的渲染效果。
图 Realistic Grass
【核心要点总结】
1)草的纹理,应选取一簇一簇聚集丛生的草。在透明的alpha通道上画实体草茎。在彩色通道中,用深浅不同的绿色和黄色,区别各个单独的叶片。
2)草体的渲染,适合进行交叉排布,从后向前进行排序,使用alpha混合,并启用Draw
Call中的z-testing/writing,便能得到自然而茂密的草地渲染效果。
3)草地的动画,以三角函数(尤其是正弦和余弦)为基础,且应该考虑到移动的位置和当前时间、风向和强度。实现起来有三种方法:
每草丛草体的动画(Animation per Cluster of Grass Objects)
每顶点的动画(Animation per Vertex)
每草体的动画(Animation per Grass Object)
【本章配套代码汇总表】
Example 7-1. 顶点着色器框架(Framework in the Vertex Shader)
Example 7-2. 对每草丛草体的动画的实现Shader代码(Code for Animation per Cluster
of Grass Objects)
Example 7-3. 每顶点动画实现Shader代码(Code for Animation per Vertex)
Example 7-4. 每草体的动画实现Shader代码(Code for Animation per Grass Object)
【关键词提炼】
草地渲染(Grass Rendering)
草地动画(Grass Animation)
草体(Grass Objects)
第二部分 · 次核心内容提炼总结
六、水焦散的渲染 (Rendering Water Caustics)
【章节概览】
这一章介绍了一种从美学角度出发(aesthetics-driven)来实时渲染水中焦散的方法。
图 水的焦散效果
【核心要点】
水的焦散(Water Caustics)的定义:光从弯曲的表面反射或者折射,只聚焦在受光面的某些区域,于是就是产生焦散的现象。
图 折射的计算(入射光线(E)从介质η1进入介质η2,发生折射,产生折射光线(T))
首先,从模拟的观点出发,焦散其实可以通过正向或逆向光线追踪计算。
正向光线追踪中,要追踪从光线射出并穿过场景的光线,累计其在不断地区的贡献。
而逆向光线追踪,则以相反的过程工作,从海底开始,按照与入射相反的顺序逆向根据光线,计算给定点的所有入射光线总和。
而该文章中,前一节介绍了逆向蒙特卡洛光线追踪的一个简化,并大胆假设一些光线对焦散有贡献,并只计算到达海底光线的一个子集,因此,该方法计算消耗非常少,却产生了尽管在物理上不正确但是非常逼真的焦散图样和效果。由于整个效果看起来非常令人信服,尤其是图像质量,使得这个方法非常值得实现。文中用HLSL和OpenGL都进行了实现,并按照惯例,提供了源码。
该算法的伪代码如下:
1.Paint the ocean floor.2.For each vertex in the fine mesh: a.Send a vertical ray. b.Collide the ray with the ocean's mesh. c.Compute the refracted ray using Snell's Law in reverse. d.Use the refracted ray to compute texture coordinates for the "Sun" map. e.Apply texture coordinates to vertices in the finer mesh.3.Render the ocean surface.
【本章配套代码汇总表】
Example 2-1. 关于波函数、波函数的梯度以及线平面截距方程的代码示例,(Code Sample
for the Wave Function, the Gradient of the Wave Function, and the Line-Plane
Intercept Equation)
Example 2-2. 最终渲染通道代码示例,展示了依赖纹理读取操作 (Code Sample for the
Final Render Pass, Showing the Dependent Texture Read Operations)
【关键词提炼】
水焦散渲染(Rendering Water Caustics)
逆向蒙特卡洛光线追踪(backward Monte Carlo ray tracing)
折射(Refraction)
七、 Dawn Demo中的动画(Animation in the "Dawn" Demo)
【章节概览】
这章主要讲到编程人员如何帮助美术同学对混合形状实行控制,从而创建不同的表情。主要是使用顶点Shader通过索引的蒙皮和变形网格对象(morph
target)来使一个高分辨率网格变形,实现角色表情和动画等效果。也讨论了为实现实时动画而考虑的各种折中方案。
图 Dawn Demo的实时屏幕截图
【核心要点】
使用变形目标(Morph Target)是表现复杂网格变形的常用方法。NVIDIA Demo团队使用此技术创建的Zoltar等Demo从每秒插值30个网格开始,然后基于累计误差方案(Accumulated Error Scheme)去除关键帧。使得我们能够缩小文件并减少存储空间,最多可以将三分之二的原始关键帧,同时几乎不会出现可见的失真。在这种类型的网格插值中,任何给定时间中只有两个插值关键帧处于激活状态,而且他们是连续地执行的。另外,变形目标可以并行使用。
原文也中对变形目标(Morph Target)的具体实现进行了论述。
图 表情的混合对象(混合形状)
而蒙皮(Skinning)是一种网格变形方法,对网格中的每个顶点指定一组带有权重的矩阵(权重最大可增加到1.0)。权重指明矩阵应该如何约束顶点。
为一个网格蒙皮做准备,通常要为网格创建一个中间状态,叫做绑定姿势(Bind Pose),这个姿势保持胳膊和腿略微分开,并且尽可能避免弯曲。
图 Dawn的绑定姿势(Bind Pose)
【本章配套代码汇总表】
Example 4-1 以线性或连续样式运用到变形目标的示例代码(Applying morph targets in
a linear or serial fashion sample code)
Example 4-2 单个"multiply-add"用于每个变形目标的示例代码(Single "multiply-add"
instruction for each morph target sample code)
Example 4-3 变形目标的实现示例代码(Morph Target Implementation sample code)
Example 4-4 使用四根骨头蒙皮的示例代码(Application of four-bone skinning sample
code)
【关键词提炼】
面部表情模拟(Facial Expression Simulation)
网格动画(Mesh Animation)
变形目标(Morph Target)
蒙皮(Skinning)
八、 改良的Perlin噪声实现(Implementing Improved Perlin Noise)
【章节概览】
这章的作者是奥斯卡得主Ken Perlin。他提出的噪声算法(Perlin Noise)已在实时和离线计算机图形学中得到多方面运用。这篇文章详细阐述了最新进展,纠正了最初的两个缺陷,也提供了有效及稳定的框架结构,用于在现代可编程硬件上执行噪声运算。
【核心要点】
首先,噪声函数的目的,是在三维空间中提供一种可以有效率地实现、可重复,伪随机的信号。其信号的能带有限(band-limited),大部分能量集中在一个空间频率附近,而视觉上是各向同性(isotropic)的,统计上不随旋转变化。
一般的想法是创建某种信号,类似于一个完全的随机信号(即白噪声),通过低通滤波后,滤除了所有的空间高频率而变得模糊。有如沙丘你缓慢上升的山包和下落的低谷。
图 沙丘与噪声
最初的噪声在1983年实现,1985 (Perlin 1985)发表,思想是使用埃尔米特-样条(Hermite
spline)插值法等方法实现,原文中对此方法的步骤进行了描述。
图 使用埃尔米特-样条(Hermite spline)插值法,从常规3D格点的八个样本中插值
图 通过噪声函数产生的一个切片样条
这篇文章从两个方面对最初的噪声方法的不足进行了改进:
插值的特性以及伪随机斜率场(field of pseudo-random gradients)的特性。
图 四种基于噪声生成的纹理
而另外一个关于噪声的思路是,用体积噪声制造程序式纹理(Procedural texturing using volumetric noise),这样可以不创建显式的纹理图像,来得到自然的材质。这种方法在当年的大片《指环王》中,已经有了广泛应用。
【本章配套代码汇总表】
5-1 假设模型是单位半径球体,实现凹凸模式的示例代码(Assuming the model is a unit-radius sphere, the expressions that implement these bump patterns sample Code)
【关键词提炼】
Perlin噪声(Perlin Noise)
噪声函数(Noise function)
伪随机斜率场(Field of Pseudo-Random Gradients)
体积噪声( volumetric noise)
埃尔米特样条(Hermite spline)
九、Vulcan Demo中的火焰渲染(Fire in the "Vulcan" Demo)
【章节概览】
这章讲述了GeForce FX 5900上市时的Demo“Vulcan”中的火焰渲染技术。其中的技术并非真正的物理模拟,而是对当时的工业标准电影《指环王》的离线技术的跟进。通过文中改进,突破了光栅化大量粒子时操作性能的限制,产生了真实可信的火焰图像。
图 基于本章方法实现的"Vulcan" Demo的截图
【核心要点】
首先文章尝试了两个方案:
完全程序化的火焰( fully procedural flames)和屏幕空间基于变形的二维火焰(screen-space 2D distortion-based flames.),经过试验都未达预期。
于是改采用视频纹理精灵(video-textured sprites ),最终达到预期,并实现出了逼真的火焰,且占用很少的GPU资源。
图 用于创建火焰效果的连续镜头
其中,烟的生成使用粒子系统创建一个烟雾生成器。而所需的光照可以采用不同的技术达到,如光线投射。
图 程序式地产生烟
而火焰和烟的混合,比较常规地使用相加混合(additive blending)。
关于使火焰增加多样性,文中使用了水平和垂直翻转(沿着u和v轴)。而使用任意旋转可以更加具表现力。
图 由自定义纹理坐标生成的变体
【本章配套代码汇总表】
Example 6-1 最终的实现Shader代码(The Final Shader)
【关键词提炼】
火焰渲染(Fire Rndering)
完全程序化的火焰(fully procedural flames)
屏幕空间基于变形的二维火焰(screen-space 2D distortion-based flames)
视频纹理精灵(video-textured sprites )
十、衍射的模拟(Simulating Diffraction)
【章节概览】
这章讲述了简化的Jos衍射光照模型(最初在SIGGRAPH 1999上发表),此模型以光的物理性质为基础,将光当做波来进行建模,从而创建出多彩的干涉条纹。
【核心要点】
什么是衍射(Diffraction)?
小尺度的表面细节引起反射波彼此干扰,这个现象就是衍射。
首先,计算机绘图的大多数表面反射模型都忽略自然光的波动效果。当表面的细节比光的波长(约1um)大许多时,不存在问题。但对于小尺寸的细节,例如一个光盘的表面,波效应就不能忽略了。所以,对于小尺度的表面细节引起反射波彼此干扰的现象,即为衍射。
衍射使这些表面的反射光呈现五彩缤纷的图案,由光盘的精细反射可以看到这一现象。
图 光盘的衍射
衍射的实现,可以在Shader的顶点着色器上,也可以在片元着色器上,且实现可以在任何网格上进行,只需提供一个“切线向量”,和每顶点的法线及位置。而切线向量提供表面上窄条带的局部方向。对于一个光盘,其为轨道的方向,如下图。
图 光盘的切线向量
对应给定衍射波长的颜色,可以使用简单近似的彩虹贴图。贴图从紫到红排列,而且提供彩虹的大部分颜色,用三个理想凹凸函数(峰值分别在蓝、绿和红的区域)简单混合而成。
图 用于shader的彩虹彩色贴图
而最终的衍射颜色是彩色的衍射图案和各项异性高光的简单相加的和。
图 光盘衍射实时的3个快照
图 用纹理映射各项异性主要方向表面的3个快照
【本章配套代码汇总表】
Example 8-1. 衍射的顶点着色器代码(The Diffraction Shader Vertex Program)
【关键词提炼】
衍射模拟(Simulating Diffraction)
各项异性(Anisotropy)
十一、高效的阴影体渲染(Efficient Shadow Volume Rendering)
【章节概览】
这章全面讲述了用于实时阴影渲染中常见两种流派之一的阴影体(Shadow Volumes)技术,又称模板阴影(Stencil Shadows)技术,重点是得到正确的角度的情形,减少几何图形和填充率的消耗。
【核心要点】
当时id software的《Doom 3》就是采用阴影体(Shadow Volumes)技术来对阴影进行的渲染。具体思想是在模板(stencil)缓冲标记阴影的像素,把像素分为阴影或照明两种类型,接着调节负责光照的像素程序,使阴影像素的照明贡献度为0。
阴影体技术可以为点光源、聚光灯和方向光源创建清晰的、逐像素进度的阴影。单个物体可以被多个光源照亮,而且光有任意颜色和衰减度。阴影从三角形网格投射到深度缓冲区上。这意味着被遮挡的物体,可以是带有深度缓冲区的网格、公告板、粒子系统或预先渲染的场景。
较其他运算相比,阴影体可以更好地处理许多制作困难的阴影场景,如一个插在万圣节南瓜灯内部的光源。
图 阴影体技术可以很好胜任的渲染场景
阴影体的缺点是对那些没能正确表达自身形状的网格的阴影表达效果并不理想。如一些带透明区域的公告板,粒子系统,或带alpha粗糙度的纹理网格(如一片树叶)。这些投影体基于他们的真实网格产生阴影,阴影与物体的真实形状并不匹配。而阴影体的另一个缺点是对带裂缝的网格支持不太好。文中也表示,当时阴影体运行的理想场景是顶部俯视。
图 模型上的裂缝会让影子穿过空气漏出来
总之,这篇文章对McGuire等人2003年提出的方法进行了很好的描述、分析与实践。而在这篇文章发出之后的若干年,阴影体技术得到了各种进一步地优化与改进。
【本章配套代码汇总表】
Example 9-1 程序结构伪代码(Program Structure Pseudocode)
Example 9-2 glFrustum风格的无限投影矩阵(An Infinite Projection Matrix in the
Style of glFrustum)
Example 9-3 用于从示例光源中“挤”出 w=0顶点的顶点着色器代码(A Vertex Shader for
Extruding w = 0 Vertices Away from the Example Light)
Example 9-4 (The markShadows Method)
Example 9-5 findBackfaces方法(The findBackfaces Method)
Example 9-6 renderShadowCaps方法(The renderShadowCaps Method)
Example 9-7 renderShadowSides方法(The renderShadowSides Method)
【关键词提炼】
阴影渲染(Shadow Rendering)
阴影体(Shadow Volume)/ 模板阴影(Stencil Shadows)
多通道渲染(Multipass Rendering)
十二、电影级光照(Cinematic Lighting)
【章节概览】
本章中介绍了一个的简化的uberlight(可理解为“全能光照”)实现,此光照shader根据Ronen Barzel(1997,1999)提出的照明模型编写而成。而该模型的超集已由Pixar动画开发,并应用于《玩具总动员》、《怪物公司》、《海底总动员》等一系列的迪士尼电影中。
本章所对该光照模型的尝试,旨在提供一套全面的光照控制参数,以涵盖灯光美术师日常使用的大部分效果。
图 《怪物公司》 中cookies对窗户效果的贡献
【核心要点】
首先,该章中呈现的Shader只模拟光照场景光源的形成和控制,不包括如何模拟表面细节和光反射行为的复杂性。
大体上,用于电影产品的照明模型会进行两种操作,类似于显示在这里的伪代码:
color illuminationModel(){ Computer the surface characteristic For each light { Evaluate the light source (评估光源) Compute the surface response(计算表面响应) }}
首先,通过这些方式计算表面着色信息:运行各种纹理查找(texture lookups),在网格上插值(interpolating values over the mesh),计算程序模式(computing procedural
patterns)等。然后在照明物体的每个光源上循环,计算出它的贡献。我们通过对每个光线计算光的颜色,然后计算表面对照明的响应来进行上述操作。
原文中给出了一个Shader源代码,该Shader用于计算塑料(plastic)材质在只有一个光源贡献的反射模型。,可以很容易将它扩展为更通用的多光源和更多表面的解决方案。
该Shader为美术师提供了各个方面的照明控制:选择(指定物体是否响应接受光照),颜色,形状,阴影和纹理,而阴影选项中包括明暗度、色调、反射、阴影贴图、阴影模糊等参数。
下面两幅图说明了uberlight 的使用效果。照明来自Pixar短片“Geri's Game”中的人物头部。
图 (a)Geri由一个光源照明;(b)改变光的权重,修改反射高光对比度;(c)改变阴影颜色,加强阴影;(d)改变谷仓形状(类似窗户一样的遮挡物),创建更戏剧化的姿态;(e)使用一块模糊的纹理cookie,丰富图像;(f)夸大透射的cookie的对比,创建像外星人一样的效果
(a)常态 (b)黑色电影(noir)的高反差 (c)柔和的光线
【本章配套代码汇总表】
10-1. The Vertex Program for an Uberlight-Like Shader
10-2. The Fragment Program for an Uberlight-Like Shader
【关键词提炼】
电影级光照(Cinematic Lighting)
全能型光照(Uberlight)
照明模型(Lighting Model)
储存于本地的光照数据(Light Cookies)
十三、阴影贴图抗锯齿(Shadow Map Antialiasing)
【章节概览】
这章介绍了如何通过邻近百分比过滤方法(Percentage-Closer Filtering , PCF)有效减少阴影贴图的反走样。
【核心要点】
阴影贴图(Shadow Map,又译作阴影映射)是渲染阴影的常见方法,也是渲染阴影领域的两大流派之一,但是它存在走样的问题。通常使用高分率的阴影贴图和增加阴影贴图的分辨率来反走样,也就是使用Stamminger和Drettakis 2002年提出的“透视阴影贴图( perspective shadow maps)”技术。但是,当光与表面接近于平行的时候,使用“透视阴影贴图”技术和增加阴影贴图分辨率就不起作用了,因为放大的倍数接近于无穷大。
高端渲染软件使用“临近的百分比过滤(Percentage-Closer Filtering , PCF)”技术解决走样问题。最初的PCF算法由Reeves等人1987年提出。其计算的是靠近光源表面的百分比,而不是在阴影中表面的百分比,具体是多次比较阴影贴图的每个像素,求其平均值。
且文中对传统的PCF算法做了改进,不再计算阴影贴图空间中被遮挡的区域,只是简单地在各处使用一个4 x 4个texel(纹素)的样本块。这个块应该大到能够有效地减少走样,但是不能达到要求大量样本和随机取样的程度。如下图。
图 (a)每像素取1个样本 (b)每像素取4个样本 (c)每像素取16个样本
可以看到3幅图中的显示效果区别很明显,图(c)中每像素取16个样本,效果最为出色,达到了反走样的预期。
【本章配套代码汇总表】
PS:原文中没有对代码片段进行编号,这里的编号为附加。
Example 11-1 暴风(Brute Force)算法16采样版本的片元程序实现代码
Example 11-2 阴影贴图反走样的4采样实现版本代码
【关键词提炼】
反走样/抗锯齿(Antialiasing)
邻近百分比过滤(Percentage-Closer Filtering , PCF)
透视阴影贴图( perspective shadow maps)
十四、全方位阴影贴图(Omnidirectional Shadow Mapping)
【章节概览】
在这章中,把阴影贴图的思路扩展到正确处理全方位的(点)光源中,其中包括了实现细节,也涉及到基本硬件能力不足时的低效运行策略。
【核心要点】
首先,这篇文章也谈到了在实时计算机图形学中产生可见阴影的两个流行方法是:
模板阴影(stencil shadows)/ 阴影体(Shadow Volume)
阴影贴图(shadow mapping)
模板阴影(Stencil Shadows,也被称Shadow Volume,阴影体)作在《Doom 3》中有所应用,优点是得到大量的GPU支持、独立于光源的种类、产生的阴影质量很高。但缺点是严重依赖于CPU,只能产生清晰的影子,需要很高的填充率,而且不能与硬件(hardware-tessellated)的表面一起使用。
阴影贴图(Shadow Mapping,也译作阴影映射)由Lance Williams于1978年引入计算机图形学,文章发布当时多数好莱坞电影都在使用这个方法,包括计算机渲染和特效。为了计算阴影,阴影映射在场景几何体上投射特殊的动态创建的纹理。它可以渲染清晰和模糊的影子,以及由不同类型的光源产生的阴影,它还可以与硬件镶嵌的表面以及GPU动画的网格(例如蒙皮网格)一起使用。
该文章主要介绍了全方位阴影贴图(Omnidirectional Shadow Mapping)方法,该方法有两个主要步骤:
创建阴影贴图
进行阴影投射
在创建阶段,对所有把阴影投射到阴影贴图纹理上的物体,渲染它们到光源的距离的平方。而在投射结算,渲染所有接受阴影的物体,并比较所渲染的像素到光源的距离的平方。以下为全方位阴影映射算法的伪代码:
for (iLight = 0; iLight < NumberOfLights; iLight++) { // Fill the shadow map. for (iObject = 0; iObject < NumberOfObjects; iObject++) { RenderObjectToShadowMap(iLight, iObject); } // Lighting and shadow mapping. for (iObject = 0; iObject < NumberOfObjects; iObject++) { LightAndShadeObject (iLight, iObject); }}
图 Omnidirectional Shadow Mapping @Merlin3d
【本章配套代码汇总表】
Example 12-1 全方位阴影映射算法的伪代码(Pseudocode for the Omnidirectional
Shadow-Mapping Algorithm)
Example 12-2 仅渲染深度(Depth-Only Rendering)
Example 12-3 产生一个软阴影(Making a Softer Shadow)
【关键词提炼】
阴影渲染(Shadow Rendering)
阴影贴图(Shadow Mapping)
模板阴影(stencil shadows)/ 阴影体(Shadow volume)
全方位阴影映射(Omnidirectional Shadow Mapping)
The End.