GAMES202 Physically-Based Rending(PBR)

本文最后更新于:4 个月前

Physically-Based Rending(PBR)

Microfacet BRDF

BRDF:Bidirectional Reflectance Distribution Function双向反射分布函数

image-20230707140434264 \[ f_r\left(\omega_i \rightarrow \omega_r\right)=\frac{\mathrm{d} L_r\left(\omega_r\right)}{\mathrm{d} E_i\left(\omega_i\right)}=\frac{\mathrm{d} L_r\left(\omega_r\right)}{L_i\left(\omega_i\right) \cos \theta_i \mathrm{~d} \omega_i} \quad\left[\frac{1}{\mathrm{sr}}\right] \] 本质上,BRDF是指某一个点的irradiance在某个方向的radiance贡献,所以它是radiance / irradiance。

image-20230629134340036

将表面表示成法线各向异性的粗糙平面,表面每个点上都有无数条各个方向的法线,可以理解为在半球上分布,集中于半球的中心法线。按照上图公式计算入射与出射:

F:从一个角度看过去,有多少的能量会被反射。因为能量的传播是与入射方向和法线的角度有关的

G:Shadowing-Masking Term。微表面的互相遮挡

D:微表面的法线分布。给定一个入射方向和一个出射方向,可以计算出半程向量h,将h带入半球法线分布中解得在这个点上有多少比例的法线是h方向,作为法线的贡献。因为我们假设只有h方向作为法线才可以实现给定的入射和出射方向的反射。

Fresnel

image-20230629140402847

image-20230630143106900

F项(Fresnel项)的解释。入射方向与法线越重合,反射的能量就越多。重合时称为grazing angle。 \[ \begin{aligned} R(\theta) & =R_0+\left(1-R_0\right)(1-\cos \theta)^5 \\ R_0 & =\left(\frac{n_1-n_2}{n_1+n_2}\right)^2 \end{aligned} \] 近似计算公式:第一个式子将R限制在R0~1之间,当角度取90时R最大为1.第二个式子是R0的定义,这几个n与材质相关。

Normal Distribution Function(NDF)

image-20230630160111880

微表面法线模型,描述了法线分布,当大部分法线都向上时认为是glossy的。

Beckman分布

\[ D(h)=\frac{e^{-\frac{\tan ^2 \theta_h}{\alpha^2}}}{\pi \alpha^2 \cos ^4 \theta_h} \]

α:表面粗糙度

θ:半程向量h与法线n的夹角

本质上就是正态分布结合粗糙度的分布,是各向同性的。

image-20230630162725082

为了保证在projected solid angle上积分为1,分母上加了一些系数。如上图,projected solid angle就是半球法线在平面上的投影角度。

image-20230630162437203

Beckman分布的正态分布系数考虑的是坡度空间的tanθ,这样做的好处是保证微表面永远向上,不会出现向下的。

GGX(or Trowbridge-Reitz)

\[ D_{G G X}(\mathbf{m})=\frac{\alpha^2}{\pi\left((\mathbf{n} \cdot \mathbf{m})^2\left(\alpha^2-1\right)+1\right)^2} \]

m:半程向量h

n:宏观表面的法线n

image-20230630163606139

GGX在角度较大的时候收敛较慢,可以形成较好的过渡光晕效果。

image-20230630164044553

Shadowing-Masking Term

image-20230630165125995

image-20230630165245741

微表面的自遮挡系数,从光线出发,光线被遮挡叫shadowing,从人眼出发,看不到的现象叫masking。

image-20230630170610637

对于左边的球体来说,球体边缘的dot(n,i)无限接近于0,导致函数的计算结果无限大,因此G项就是为了解决这个问题的。

Smith Shadowing-Masking Term

image-20230630171322002

红线是Beckman,绿线是GGX,右图是对应的Smith Shadowing-Masking Term,可以发现垂直入射时几乎为1,在grazing angle上剧烈减少。

m为半程向量。

Energy loss

White Furnace Test

白炉测试(The White Furnace Test)的思路为验证入射总能量为1的光线的反射光线分布是否归一化的测试。可以将白炉测试理解为一束辉度(irrandiance)为1的光线从上往下照到菲涅尔反射项为1的材质表面,并测定反射光线总能量是否为1的过程。若反射光线总能量为1,则通过白炉测试,否则,便不通过白炉测试。白炉测试不考虑颜色。

image-20230630172145546

对上述BRDF表达采用白炉测试的结果如下:

image-20230630172323463

可以发现,粗糙度越高的物体,能量损失越大。这是因为越粗糙的表面,光线在表面的沟壑上弹射的概率越大,如果只考虑一次bounce,那么会损失在粗糙表面多次弹射的光线的能量。

The Kulla-Conty Approximation

如何补回这些损失的能量呢?一方面可以进行muti-bounce,只要bounce够多,就可以无限次近似。但是这样做太费了,更不可能应用到实时。因此可以通过一些公式近似去找补回来这些能量。基本思路就是假设我们四面八方任意一个角度入射的irradiance为1,那么我们从渲染方程得到的任意方向的出射irradiance E(U0)理论上也应该是1。位置、出射方向已知,那么我们就需要通过一个式子计算出损失的1 - E(μ0)。换句话说,我们要计算出另外一种BRDF表达函数,这个通过把这个函数带入渲染方程,可以计算得到损失的能量。

回顾渲染方程: \[ E_o\left(x, \omega_o\right)=L_e\left(x, \omega_o\right)+\int_{\Omega} f\left(x, \omega_o, \omega_i\right) L_i\left(x, \omega_i\right)\left(\omega_i \cdot n\right) d \omega_i \] 这里我们忽略位置x项,假设BRDF是各向同性的,即BRDF可以改写为f(θo,θi,φ)。

不考虑自发光项。如上文我们假设入射irradiance为1,即Li项可以去掉。由于wi与n都是归一化的,那么dot(wi,n)就是单位立体角的cosθ项。对单位立体角的积分dwi可以拆成sinθdθdφ,那么渲染方程可以写为以下形式: \[ E_o\left(\theta_o\right)=\int_0^{2 \pi} \int_0^\pi f\left(\theta_o, \theta_i, \varphi\right) \cos \theta \sin \theta d \theta d \varphi \] 将cosθ塞进去: \[ E_o\left(\theta_o\right)=\int_0^{2 \pi} \int_0^\pi f\left(\theta_o, \theta_i, \varphi\right) \sin \theta d \sin \theta d \varphi \] 令sinθ = μ \[ E_o\left(μ_o\right)=\int_0^{2 \pi} \int_0^1 f\left(μ_o, μ_i, \varphi\right) μ d μ d \varphi \] 由于BRDF的可逆性,因此除了考虑o方向,还要考虑i方向(也就是要考虑入射丢失的和出射丢失的),同时要乘一个归一化的量c(可以是常量或函数)得出一个brdf也就是: \[ c\left(1-E\left(\mu_o\right)\right)\left(1-E\left(\mu_i\right)\right) \] 通过这个brdf积分后得到的结果要等于消失的能量1 - E(μ0)。最终得到的需要补充的能量中BRDF项直接给出结果: \[ f_{\mathrm{ms}}\left(\mu_o, \mu_{\mathrm{i}}\right)=\frac{\left(1-E\left(\mu_0\right)\right)\left(1-E\left(\mu_i\right)\right)}{\pi\left(1-E_{\text {avg }}\right)} \] 其中: \[ E_{\mathrm{avg}}{(\mu_o)}=2 \int_0^1 E(\mu_i) \mu_i d \mu_i \] 上式就是我们最终要求得的BRDF表达,这个通过把这个函数带入渲染方程,可以计算得到损失的能量。证明如下: \[ \begin{aligned} E_{\mathrm{ms}}\left(\mu_0\right) & =\int_0^{2 \pi} \int_0^1 f_{\mathrm{ms}}\left(\mu_o, \mu_i, \phi\right) \mu_i d \mu_i d \phi \\ & =2 \pi \int_0^1 \frac{\left(1-E\left(\mu_0\right)\right)\left(1-E\left(\mu_i\right)\right)}{\pi\left(1-E_{\text {avg }}\right)} \mu_i d \mu_i \\ & =2 \frac{1-E\left(\mu_0\right)}{1-E_{\text {avg }}} \int_0^1\left(1-E\left(\mu_i\right)\right) \mu_i d \mu_i \\ & =\frac{1-E\left(\mu_0\right)}{1-E_{\text {avg }}}\left(1-E_{\text {avg }}\right) \\ & =1-E\left(\mu_0\right) \end{aligned} \] 在最终通过渲染方程计算shading point的时候,不单单要计算原始的BRDF,也要把新的补充BRDF加上。

对于Eavg,输入参数为观察角度μ,同时E与BRDF有关,可用roughness代替。因此这项不用实际计算,可以打个表:

image-20230701150946391

Result

image-20230701151405109

如上图,加上补充的能量之后效果就正常了。

BRDF With Color

  • 有颜色就意味着,物体发生了能量吸收的情况,也就是有额外能量损失,也就是单次反射的积分结果不是1
  • 因此我们可以先考虑没有颜色的情况,然后再乘以一个小于1的颜色系数

同理,为了简化计算,我们也定义了一个平均Fresnel项,不考虑入射方向,直接带入出射角度就可以算得Fresnel值: \[ F_{a v g}{(\mu_o)}=\frac{\int_0^1 F(\mu_i) \mu_i \mathrm{d} \mu_i}{\int_0^1 \mu_i \mathrm{d} \mu_i}=2 \int_0^1 F(\mu_i) \mu_i \mathrm{d} \mu_i \] 对于有颜色的物体,Fresnel项最大也要小于1

我们可以发现平均Fresnel项的形式与上文的Eavg完全一样,这也就是为什么叫avg,实际上也是只给定出射角度,一束光在这个角度到底能贡献多少。

在一次弹射时,出射的能量为Favg * Eavg

在二次弹射时,出射的能量为Favg * (1 - Eavg) * Favg * Eavg

在N次弹射时,出射的能量为FavgN * (1 - EavgN * Favg * Eavg

1 - Fresnel项表示光被吸收的比率,1 - E项表示光在微表面内弹射本次bounce没有在对应的角度弹射出去的能量。

几何级数积分可得: \[ \frac{F_{\mathrm{avg}} E_{\mathrm{avg}}}{1-F_{\mathrm{avg}}\left(1-E_{\mathrm{avg}}\right)} \] 将这个级数计算出来的结果乘以假设不带颜色计算的结果,得到的就是考虑颜色的BRDF。

Linearly Transformed cosines(LTC)

LTC模型主要解决不考虑shadow的多边形光源在某一点的shading,主要使用GGX法线分布,且光源是uniform的。

image-20230702222432083

lobe:波瓣。BRDF是四维的(入射出射各有两个角度的球面坐标输入)。一个lobe表示人眼方向固定,出射方向在该点的GGX分布下的分布,是二维函数表示的三维分布。lobe的形状是根据反射的强度得到的,也就是BRDF的取值。对于普通的DDX分布来说,最高点与宏观表面法线重合,向两边正态分布,那么如上图,半程向量h与宏观法线n重合时lobe取值最大,然后向两侧逐渐减小,最终呈花瓣状。

BRDF是与出射角度有关的。对于面光源,理论上需要BRDF在光源lobe重合的角度上进行积分,实际上需要采样很多很多的点才能计算出对shading point的贡献。我们注意到lobe的分布与余弦分布很像,都是中间高两边低,那么可以采用一种变换将lobe转换为cos分布。

参考《GAMES202:高质量实时渲染》4 实时高质量着色:Microfacet Model、LTC(Linearly Transformed Cosines)、非真实感渲染(NPR) - 知乎 (zhihu.com)

Disney Principled BRDF

微表面模型有一定的局限性,首先它很难描述多层材质,例如木桌面上刷了一层漆,有一部分光线穿过漆面打到木质上,然后再反射等一系列过程。其次它不好用,它引用的一些影响效果的PBR参数都是基于物理的,对于艺术家来说很不友好。例如金属的折射率在物理上是一个a+bi的复数。Disney Principled BRDF不保证物理准确,但是很好用。

设计原则:

  • 使用直观的参数,而不是物理参数来控制模型呈现的效果;

  • 模型使用的参数尽可能的少;

  • 参数的合理范围应该是从 0 到 1;

  • 在必要时,即使参数的取值超出了 [0,1] 范围,也能得到有意义的结果;

  • 所有的参数组合应该尽可能的稳健和合理;

image-20230702235002607

  • subsurface:次表面反射,为了在BRDF中给你一种比diffuse还要平的效果.可以看出当subsurface为1时与0相比像是被压扁了一样.
  • metallic:金属性,顾名思义看起来像金属的程度.

  • specular:控制有多少镜面反射的内容,0为完全没有镜面反射内容,diffuse,1则表示全是镜面反射内容.
  • specular tint:镜面反射出的颜色无色(为0),还是偏向于自己物体本身的颜色(1).
  • roughness:粗糙度,为0表示全是镜面反射,为1表示没有镜面反射.
  • anisotropic:各向异性程度,可以理解为当为1的时候带来一种像是被刷过一样的效果.
  • sheen:可以理解为,在物体表面法线方向上长了绒毛,这让你在grazing angle(外圈)处看起来有一种雾化的感觉.
  • sheen tint:可以理解为绒毛造成的雾化效果颜色是无色,还是偏向物体本身的颜色.
  • clearcoat:可以理解为透明层的明显程度,0时表示没有透明层,1则表示有一层透明层(涂了一层清漆).
  • clearcoat gloss:透明层的光泽层度,为0就像被磨砂了一样,为1则表示完全光滑.

Disney's principle BRDF的优点:

1.容易理解和使用各参数(属性)

2.参数的混合组合使得可以在一个模型上显示出很多不同的材质.

3.开源

Disney's principle BRDF的缺点:

1.并不是完全基于物理的

2.巨大的参数空间使得拥有强大的表示能力,但是会造成冗余现象.

Non-Photorealistic Rendering(NPR)

NPR(非真实感渲染)目的是通过一些简单快速的处理,得到一些风格化的渲染结果,通常使用一些轻量级的解决方法,在着色器中进行一些简单而巧妙的处理。NPR通常在真实渲染的结果基础上进行。

描边

什么是边?

image-20230703002121334

对于一个物体的各类边缘,定义如下:

boundary:物体边界B

Crease:折痕C

Materal Edge:材质边界M

Silhouette Edge:物体轮廓S,分布在外侧,由多个面共享

几何方法

对于S边,可以发现一个有趣的现象:S边的两个面,一个是可见的,一个是不可见的,S边处于交界处。换句话说,在S边附近分布的点,它的法线是和视线近似垂直的。即grazing angle位置。这种基于法线的描边有一个很明显的缺点,无法控制描边的粗细。

image-20230703004007275

将物体背面的三角形轮廓扩大,然后全渲染成黑的,这是目前主流的描边方法,就是很耗。

image-20230703004152867

image-20230703004210068

同时,有着各种各样的优化方法,例如棱角剔除、vertex外扩等。

图像方法

image-20230703004720446

可以使用不同的卷积核对图像进行filter,算出图像的边缘。图像锐化就是这样做之后再加回原图像。

image-20230703004839696

同样,也可以用normal、depth进行判断。

色块

image-20230703004959315

阈值化:当一个变量处于某个范围内时,直接将其设定为范围内的一个定值。

image-20230703005215068

根据法线对diffuse shading阈值化

image-20230703005352345

对diffuse、specular分别进行自由阈值化的结果

素描

image-20230703005547969

素描效果:越暗的地方素描线条越多

image-20230703005919163

可以定义一系列多级mipmap纹理,根据shading point的明暗去不同的纹理上查询。相邻的shading point在纹理上的采样点也应该相邻,保证线条连续。

由于素描是纯画在屏幕上的,并没有远近的效果,例如远处物体线条就会变小等。那么如果只用一级纹理,由于远处的物体UV变小,视觉上分辨率就会降低,会变暗,线条变小。我们采用上图中的mipmap,这种mipmap并没有改变线条的密度大小,保证了远近的物体线条密度都差不多。

image-20230703010537128

Lumen在屏幕空间中使用GGX模拟specular分布的实际用法

GGX的输入参数是半程向量,输出法线的反射贡献。最容易想到的做法就是对于BRDF来说,我们已知半程向量,那么可以直接带入计算法线贡献即可,这样做对于diffuse材质很好,但是对于specular来说,对于整个半球进行大量采样是不划算的,因为只有一小块lobe有贡献,因此引入重要性采样。

先贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#if COMPUTE_ROUGH_SPECULAR

float DiffuseLerp = GetDiffuseLerp(GBufferData.Roughness);

uint NumSpecularSamples = NUM_SPECULAR_SAMPLES;

// Prevent NaNs from ImportanceSampleVisibleGGX
GBufferData.Roughness = max(GBufferData.Roughness, .01f);

if (DiffuseLerp < 1.0f || GBufferData.ShadingModelID == SHADINGMODELID_CLEAR_COAT)
{
// For clear coat materials, screen probes are always providing the bottom layer reflections
GBufferData.WorldNormal = GetClearCoatBottomNormal(GBufferData, GBufferData.WorldNormal);

//@todo - derive mip from cone angle from roughness

// Approximation made to move out of inner loop
float RayPDFForMip = 1.0f;
float SolidAngleSample = 1.0 / (NumSpecularSamples * RayPDFForMip);
float CosConeHalfAngle = 1.0 - SolidAngleSample / (2.0 * PI);
float NumTexels = sqrt(1.0f - CosConeHalfAngle) * ScreenProbeGatherOctahedronResolution;
float MipLevel = clamp(log2(NumTexels), 0, ScreenProbeGatherMaxMip);
FSphericalGaussian HemisphereSG = Hemisphere_ToSphericalGaussian(GBufferData.WorldNormal);
FSphericalGaussian VisibleSG = BentNormalAO_ToSphericalGaussian(UnitBentNormal, AO);

for (uint TracingRayIndex = 0; TracingRayIndex < NumSpecularSamples; TracingRayIndex++)
{
float4 E = ComputeIndirectLightingSampleE(DispatchThreadId.xy, TracingRayIndex, NumSpecularSamples);

E = BiasBSDFImportantSample(E);

FBxDFSample BxDFSample = SampleBxDFWrapper(SHADING_TERM_SPECULAR, GBufferData, V, E);
float3 InterpolatedRadiance = InterpolateFromScreenProbes(BxDFSample.L, MipLevel, StochasticScreenProbeSample);

float DirectionVisibility = 1.0f;

#if SCREEN_SPACE_BENT_NORMAL
float LVisibility = saturate(Evaluate(VisibleSG, BxDFSample.L) / Evaluate(HemisphereSG, BxDFSample.L));
DirectionVisibility *= LVisibility;
#endif

SpecularLighting += TonemapLighting(InterpolatedRadiance * BxDFSample.Weight * DirectionVisibility);
}

SpecularLighting = InverseTonemapLighting(SpecularLighting / (float)NumSpecularSamples);
}

//@todo - replace with SH fit of GGX
SpecularLighting = lerp(SpecularLighting, DiffuseLighting / PI, DiffuseLerp);
#else
//SpecularLighting = DiffuseLighting;
#endif



FBxDFSample SampleBxDFWrapper(const uint TermMask, FGBufferData GBuffer, float3 V, float4 E)
{
return SampleDefaultLitBxDF(TermMask, GBuffer, V, E);
//return SampleBxDF(TermMask, GBuffer, V, E);
}

FBxDFSample SampleDefaultLitBxDF(uint TermMask, float3 WorldNormal, float Roughness, float3 V, float4 E)
{
TermMask &= SHADING_TERM_DIFFUSE | SHADING_TERM_SPECULAR;

float2 DiskE = UniformSampleDiskConcentric(E.xy);

float3 N = WorldNormal;
float3x3 TangentBasis = GetTangentBasis(N);

// TODO: stocastically choose SHADING_TERM_D or SHADING_TERM_DIFFUSE if TermMask == (SHADING_TERM_D | SHADING_TERM_SPECULAR)

FBxDFSample BxDFSample = (FBxDFSample)0;
if (TermMask == SHADING_TERM_DIFFUSE)
{
float TangentZ = sqrt(1 - length2(DiskE));

BxDFSample.L = mul(float3(DiskE, TangentZ), TangentBasis);
BxDFSample.PDF = TangentZ * rcp(PI);
BxDFSample.Weight = 1.0;
BxDFSample.Term = SHADING_TERM_DIFFUSE;
}
else if (TermMask == SHADING_TERM_SPECULAR)
{
float a2 = Pow4(Roughness);

float3 TangentV = mul(TangentBasis, V);

#if 1
// PDF = G_SmithV * VoH * D / NoV / (4 * VoH)
// PDF = G_SmithV * D / (4 * NoV);
float4 TangentH = ImportanceSampleVisibleGGX(DiskE, a2, TangentV);
#else
// PDF = D * NoH / (4 * VoH),
float4 TangentH = ImportanceSampleGGX(E.xy, a2);
#endif

float HPDF = TangentH.w;

float3 H = mul(TangentH.xyz, TangentBasis);

float VoH = saturate(dot(V, H));

BxDFSample.L = 2 * dot(V, H) * H - V;
BxDFSample.PDF = RayPDFToReflectionRayPDF(VoH, HPDF);

// float D = D_GGX(a2, NoH);
// float G_SmithL = 2 * NoL / (NoL + sqrt(NoL * (NoL - NoL * a2) + a2));
// float Vis = Vis_Smith(a2, NoV, NoL);
// float3 F = F_Schlick(GBuffer.SpecularColor, VoH);

// Correct integration applies DGF below but for speed we apply EnvBRDF later when compositing
// BxDFSample.Weight = F * ( NoL * Vis * (4 * VoH / NoH) ); // for ImportanceSampleGGX
// BxDFSample.Weight = F * G_SmithL; // for ImportanceSampleVisibleGGX
BxDFSample.Weight = 1.0;

BxDFSample.Term = SHADING_TERM_SPECULAR;
}

return BxDFSample;
}

// [ Heitz 2018, "Sampling the GGX Distribution of Visible Normals" ]
// http://jcgt.org/published/0007/04/01/
// PDF = G_SmithV * VoH * D / NoV / (4 * VoH)
// PDF = G_SmithV * D / (4 * NoV)
float4 ImportanceSampleVisibleGGX( float2 DiskE, float a2, float3 V )
{
// NOTE: See below for anisotropic version that avoids this sqrt
float a = sqrt(a2);

// stretch
float3 Vh = normalize( float3( a * V.xy, V.z ) );

// Stable tangent basis based on V
// Tangent0 is orthogonal to N
float LenSq = Vh.x * Vh.x + Vh.y * Vh.y;
float3 Tangent0 = LenSq > 0 ? float3(-Vh.y, Vh.x, 0) * rsqrt(LenSq) : float3(1, 0, 0);
float3 Tangent1 = cross(Vh, Tangent0);

float2 p = DiskE;
float s = 0.5 + 0.5 * Vh.z;
p.y = (1 - s) * sqrt( 1 - p.x * p.x ) + s * p.y;

float3 H;
H = p.x * Tangent0;
H += p.y * Tangent1;
H += sqrt( saturate( 1 - dot( p, p ) ) ) * Vh;

// unstretch
H = normalize( float3( a * H.xy, max(0.0, H.z) ) );

return float4(H, VisibleGGXPDF(V, H, a2));
}

上述代码是Lumen Finalgather Screenprobe中对于清漆材质的specular处理过程。首先我们要清楚我们已知什么:相机位置(视角)和shading point。

  1. 在shading point上以DispatchThreadId为中心进行给定数量的indirect方向采样,得到向量E

  2. 对E进行y方向的偏置,使得y更加密集分布在0.5附近内,然后将相应参数传入SampleBxDFWrapper进行计算

  3. 在SampleDefaultLitBxDF函数中,以E为中心进行半球面圆盘采样,得到新的方向DiskE

  4. 将新的DiskE、相机位置、specular参数a2带入ImportanceSampleVisibleGGX函数进行计算

  5. ImportanceSampleVisibleGGX是2.18年一篇论文《Sampling the GGX Distribution of Visible Normals》的实现,实际上也是一种GGX分布。返回半程向量H和PDF

  6. BxDFSample.L = 2 * dot(V, H) * H - V就是通过H和V将半程向量H转换为入射方向
  7. 从入射方向收集radiance
  8. ……

由上可知,在重要性采样中,==最开始的入射方向E其实就是随机数种子==,将种子带入BRDF采样函数中才能得到最终的入射方向和PDF