游戏开发论坛

 找回密码
 立即注册
搜索
查看: 3761|回复: 0

GPU精粹与Shader编程(二):次表面散射与环境光遮蔽

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2019-7-22 14:31:44 | 显示全部楼层 |阅读模式
接上篇:GPU精粹与Shader编程(一):关于基础物理渲染

v2-a9902dfa130b62494120269292a844d3_r.jpg


题图背景来自《神秘海域4》。

系列文章前言

《GPU Gems》1~3、《GPU Pro》1~7以及《GPU Zen》组成的饕餮盛宴,共11本书,合称“GPU精粹三部曲“,是游戏开发、计算机图形学和渲染领域的业界顶尖大牛们一线经验的合辑汇编,是江湖各大门派武林绝学经典招式的精华荟萃,是了解业界各种高阶知识和技法Trick,将自己的游戏开发、图形学与渲染能力提升到下一个高度的捷径。


本文内容概览与关键词

本篇文章将总结提炼“GPU精粹三部曲“11本书中的第一本《GPU Gems 1》全书的核心内容的下半部分,是【GPU精粹与Shader编程】系列文章正篇的第二篇,全文共1万5千余字。

PS:本文是对两周前,本计划发出但因知乎专栏字数限制,最终发布在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-Dynamic-Range,HDR)

先放一张来自《神秘海域4》真实感皮肤渲染的效果图:

image001.jpg
本文的GitHub版本

不少朋友们喜欢看GitHub版本的文章,我也很喜欢。

首先,MarkDown可以很方便地插入快捷导航目录,能进行瞬间跳转到指定子章节。其次,GitHub版本的文章中没有单篇文章的字数限制,少了很多篇幅方面的桎梏。而且因为Git的便利性,版本管理的优势,最新的勘误和修订第一时间会在GitHub的Repo中进行。

【本文的GitHub版本传送门】:

QianMo/Game-Programmer-Study-Notesgithub.com

目录·核心内容导航Highlight

【说明】下文目录中加粗的标题为本文将包括的内容,非加粗的标题已在上次的更新(GPUGems 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-Notesgithub.com

《GPU Gems 1》配套资源与源代码

这一节提供了一些,《GPU Gems 1》书本的相关链接,以及配套源代码的下载。

PS:配套的不少工程中不仅包含完整的源码,也直接包含经过编译后的exe执行文件,可以直接运行后查看效果。

•原书全文的Web版本:

GPU Gemsdeveloper.nvidia.com

•我维护了的一个名为“GPU-Gems-CD-Content”的GitHub仓库,并整理好了《GPU Gems》1~3书本全部的配套工程与源代码,逐章内容的快捷导航,以备份、珍贵、快速查阅这些优质资源:

QianMo/GPU-Gems-Book-Source-Codegithub.com

第一部分·主核心内容提炼总结

四、次表面散射的实时近似(Real-Time Approximations to Subsurface Scattering)

【章节概览】

次表面散射(Subsurface Scattering),简称SSS,或3S,是光射入非金属材质后在内部发生散射,最后射出物体并进入视野中产生的现象,即光从表面进入物体经过内部散射,然后又通过物体表面的其他顶点出射的光线传递过程。

image004.jpg
图次表面散射原理图示

image006.jpg
图真实世界中的次表面散射

要产生使人信服的皮肤和其他半透明材质的渲染效果,次表面散射(Subsurface Scattering)的渲染效果十分重要。

image007.jpg
图有无次表面散射的皮肤渲染对比图(左图:使用次表面散射|右图:无次表面散射)

另外需要提出,在《神秘海域4》中皮肤的渲染效果,很令人惊艳。当然,《神秘海域4》中令人惊艳的,远远不止皮肤的渲染。

image009.jpg
图基于次表面散射的皮肤渲染 《神秘海域4》

本章即描述了次表面散射的几种实时近似方法,关于皮肤的渲染,也关于近似地去模拟透明材质的几种不同方法。

【核心内容提炼】


4.1次表面散射的视觉特性(The Visual Effects of Subsurface Scattering)

要重现出任何视觉效果,经常的做法是考察这种效果的图像,并把可视的外观分解为其组成要素。在观察半透明物体的相片和图像时,能注意到如下几点,即次表面散射(Subsurface

Scattering)的视觉特性:

1、首先,次表面散射往往使照明的整体效果变得柔和。

2、一个区域的光线往往渗透到表面的周围区域,而小的表面细节变得看不清了。

3、光线传入物体越深,就衰减和散射得越严重。

4、对于皮肤来说,在照亮区到阴影区的衔接处,散射往往会引起微弱的倾向于红色的颜色偏移。这是由于光线照亮表皮并进入皮肤,接着被皮下血管和组织散射和吸收,然后从阴影部分离开。且散射在皮肤薄的部位更加明显,比如鼻孔和耳朵周围。

image011.jpg
图次表面散射原理图示

4.2简单的散射近似(Simple Scattering Approximations)

近似散射的比较简单技巧是环绕照明(Warp Lighting)。正常情况下,当表面的法线对于光源方向垂直的时候,Lambert漫反射提供的照明度是0。而环绕光照修改漫反射函数,使得光照环绕在物体的周围,越过那些正常时会变黑变暗的点。这减少了漫反射光照明的对比度,从而减少了环境光和所要求的填充光的量。环绕光照是对Oren-Nayar光照模型的一个粗糙的近似。原模型力图更精确地模拟粗糙的不光滑表面(Nayar and Oren 1995)。

下图和代码片段显示了如何将漫反射光照函数进行改造,使其包含环绕效果。

其中,wrap变量为环绕值,是一个范围为0到1之间的浮点数,用于控制光照环绕物体周围距离。

image013.jpg
图环绕光照函数的图表

  1. float diffuse = max(0, dot(L, N));

  2. float wrap_diffuse = max(0, (dot(L, N) + wrap) / (1 + wrap));
复制代码

为了在片元函数程序中的计算可以更加高效,上述函数可以直接编码到纹理中,用光线矢量和法线的点积为索引。

而在照明度接近0时,可以显示出那种倾向于红的微小颜色漂移,这是模拟皮肤散射的一种廉价方法。而这种偏向于红色的微小颜色漂移,也可以直接加入到此纹理中。

另外也可以在此纹理的alpha通道中加入镜面反射高光光照的功率(power)函数。可以在示例代码Example 16-1中的FX代码展示了如何使用这种技术。对比的图示如下。

image014.jpg
图(a)没有环绕光照的球体(b)有环绕光照明的球体(c)有环绕光照明和颜色漂移的球体

Example 16-1摘录纳入了环绕照明的皮肤Shader效果的代码(Excerpt from the Skin

Shader Effect Incorporating Wrap Lighting)

  1. // 为皮肤着色生成2D查找表(Generate 2D lookup table for skin shading)

  2. float4 GenerateSkinLUT(float2 P : POSITION) : COLOR

  3. {

  4.     float wrap = 0.2;

  5.     float scatterWidth = 0.3;

  6.     float4 scatterColor = float4(0.15, 0.0, 0.0, 1.0);

  7.     float shininess = 40.0;

  8.     float NdotL = P.x * 2 - 1; // remap from [0, 1] to [-1, 1]

  9.     float NdotH = P.y * 2 - 1;

  10.     float NdotL_wrap = (NdotL + wrap) / (1 + wrap); // wrap lighting

  11.     float diffuse = max(NdotL_wrap, 0.0);

  12.     // 在从明到暗的转换中添加颜色色调(add color tint at transition from light to
  13.     dark)

  14.     float scatter = smoothstep(0.0, scatterWidth, NdotL_wrap) *

  15.     smoothstep(scatterWidth * 2.0, scatterWidth,

  16.          NdotL_wrap);

  17.     float specular = pow(NdotH, shininess);

  18.     if (NdotL_wrap <= 0) specular = 0;

  19.     float4 C;

  20.     C.rgb = diffuse + scatter * scatterColor;

  21.     C.a = specular;

  22.     return C;

  23. }

  24. // 使用查找表着色皮肤(Shade skin using lookup table)

  25. half3 ShadeSkin(sampler2D skinLUT,

  26.     half3 N,

  27.     half3 L,

  28.     half3 H,

  29.     half3 diffuseColor,

  30.     half3 specularColor) : COLOR

  31. {

  32.     half2 s;

  33.     s.x = dot(N, L);

  34.     s.y = dot(N, H);

  35.     half4 light = tex2D(skinLUT, s * 0.5 + 0.5);

  36.     return diffuseColor * light.rgb + specularColor * light.a;

  37. }
复制代码

4.3使用深度贴图模拟吸收(Simulating Absorption Using Depth Maps)

吸收(Absorption)是模拟半透明材质的最重要特性之一。光线在物质中传播得越远,它被散射和吸收得就越厉害。为了模拟这种效果,我们需要测量光在物质中传播的距离。而估算这个距离可以使用深度贴图(Depth Maps)技术[Hery 2002],此技术非常类似于阴影贴图(Shadow Mapping),而且可用于实时渲染。

image015.jpg
图使用深度贴图计算光在物体中的传播的距离

深度贴图(Depth Maps)技术的思路是:

在第一个通道(first pass)中,我们从光源的视点处渲染场景,存储从光源到某个纹理的距离。然后使用标准的投射纹理贴图(standard projective texture mapping),将该图像投射回场景。在渲染通道(rendering pass)中,给定一个需要着色的点,我们可以查询这个纹理,来获得从光线进入表面的点(di)到光源间距离,通过从光线到光线离开表面的距离(do)里减去这个值,我们便可以获得光线转过物体内部距离长度的一个估计值(S)。如上图。

原文中详细分析了此方法的实现过程,也附带了完整的Shader源码,具体细节可以查看原文,这里因为篇幅原因就不展开了。

image016.jpg
图使用深度贴图去近似散射,物体上薄的部位传输更多的光

也有一些更高端的模型试图更精确地模拟介质内散射的累积效应。

一种模型是单次散射近似(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]的规范化的坐标。

另外,为了模拟吸收和散射与波长的相关的事实,可以对每个彩色通道分为地改变滤波权重。

image017.jpg
图(a)原始模型(b)应用了纹理空间漫反射照明的模型,光照变得柔和

image018.jpg
图基于纹理空间漫反射照明的效果

同样,原文中详细分析了此方法的实现过程,也附带了完整的Shader源码,具体细节可以查看原文,这里因为篇幅原因就不展开了。

再贴几张基于次表面散射的皮肤渲染效果图,结束这一节。

image020.jpg
图基于次表面散射的皮肤渲染

image022.jpg
图基于次表面散射的皮肤渲染 《神秘海域4》

image024.jpg
图基于次表面散射的皮肤渲染 《神秘海域4》

【核心要点总结】

文中提出的次表面散射的实时近似方法,总结起来有三个要点:

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,是一种用于计算场景中每个点对环境光照的曝光程度的一种着色渲染技术。

本章讲到了如何使用有效的实时环境光遮蔽技术,对物体遮蔽信息及环境进行预处理,综合这些因素给物体创建逼真的光照和阴影。

image026.jpg

image027.jpg
图有无环境光遮蔽的对比1图有无环境光遮蔽的对比

【核心内容提炼】

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矢量的未遮挡入射光的圆锥体的平均值得到。

image028.jpg
图可到达度和平均方向的计算

下面贴出的伪代码显示了我们的基本方法。在每个三角形的中心,我们产生一组以表面法线为中心的半球形光线,跟踪每道光线进入场景,记录哪些光线与模型相交,标志不能从环境接收的光线,以及不被遮挡的光线。接着我们计算不被遮挡的光线的平均方向,这给出了入射光平均方向的近似值。(当然,我们计算的方向实际上可能会被遮挡,但我们选择忽略不计这个问题。)

Example 17-1计算环境光遮蔽量的基本算法伪代码(Basic Algorithm for Computing Ambient Occlusion Quantities)

  1. For each triangle {

  2.     Compute center of triangle

  3.     Generate set of rays over the hemisphere there

  4.     Vector avgUnoccluded = Vector(0, 0, 0);

  5.     int numUnoccluded = 0;

  6.     For each ray {

  7.         If (ray doesn't intersect anything) {

  8.             avgUnoccluded += ray.direction;

  9.             ++numUnoccluded;

  10.         }

  11.     }

  12.     avgUnoccluded = normalize(avgUnoccluded);

  13.     accessibility = numUnoccluded / numRays;

  14. }
复制代码

生成这些光线的简单方法是使用拒绝采样法(rejection sampling):检测在x,y和z为-1到1区间的3D立方体中随机生成的光线,并拒绝不在单位半球中与法线相关的光线。

能通过这次检测的光线方向可视分布理想的光线方向。列表17-2的伪代码表示出了此方法的实现思路。

当然,也可以用更复杂的蒙特卡洛(Monte Carlo)采样法来得到更好的样本方向的分布。

Example 17-2使用拒绝采样法计算随机方向的算法伪代码(Algorithm for Computing

Random Directions with Rejection Sampling)

  1. while (true) {

  2.     x = RandomFloat(-1, 1); // random float between -1 and 1

  3.     y = RandomFloat(-1, 1);

  4.     z = RandomFloat(-1, 1);

  5.     if (x * x + y * y + z * z > 1) continue; // ignore ones outside unit

  6.     // sphere

  7.     if (dot(Vector(x, y, z), N) < 0) continue; // ignore "down" dirs

  8.     return normalize(Vector(x, y, z)); // success!

  9. }
复制代码

另外,用图形硬件代替光线追踪软件,有可能加速遮挡信息的计算。

5.3使用环境光遮蔽贴图进行渲染(Rendering with Ambient Occlusion Maps)

使用环境光遮蔽贴图进行着色的基本思想是:

可以直接在着色点处使用之前已计算好的,有多少光线能到达表面的,优质的近似值信息。

影响这个数值的两个因素是:

(1)在此点上方半球的哪个部分不被点和环境贴图之间的几何体遮挡。

(2)沿着这些方向的入射光是什么。

下图显示了两种不同的情况。在左图中,只能看到着色点上面的一小部分分享,由方向矢量B和围绕它的方向圆锥体所表示,该点的可到达度非常低。而在右图中,沿着更大范围的方向有更多的光线到达给定点。

image029.jpg
图不同量的可见度的近似(左图:由于附近的几何体的遮挡比较严重,这点得到的照度较小;右图:沿着更宽方向的圆锥体,更大量的光能到达这点,照度较左图更大)

在预处理中计算的可访问性值告诉我们哪一部分半球可以看到环境贴图,而可见方向的平均值给出一个近似方向,围绕它计算入射光。虽然这个方向可能指向一个实际被遮挡的方向(例如,如果半球的两个独立区域未被遮挡,但其余的部分被遮挡,平均方向可能在这两者之间),但在实践中其通常运行良好。

image030.jpg
图使用可到达度信息和环境贴图渲染的光照场景

image031.jpg
图有无使用环境光遮蔽贴图进行渲染的对比图

另外需要注意,实时环境光遮蔽的常用廉价方案是预先计算网格表面几个位置的平均可见性值,存储于贴图中,然后将这些值在运行时与图形硬件提供的未遮挡光照相乘。

【核心要点总结】

给定一个任意的着色模型,环境光遮蔽算法需要知道模型上每点的两个信息:

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)

作者:毛星云
专栏地址:https://zhuanlan.zhihu.com/p/36499291

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-4-26 16:56

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表