http://blog.sina.com.cn/s/blog_76d02ce90102xr4x.html
unreal应该算是现在最实用的开源渲染引擎之一了
基于物理的渲染(PBS)用起来真的是为所欲为
这里决定花点时间研究下unreal低层的渲染代码,这里假设读者具有基本的渲染知识,至少知道BRDF是什么
引擎中的着色器代码都在Engine/Shaders中,不同版本之间会有些差别,不过不会差太多就是了
这里使用的版本是4.17
打开Engine/Shaders/Private文件夹,可以看见大量的.usf和.ush文件,这些就是unreal渲染的核心
两者的语法都类似GLSL或HLSL,区别是ush没有入口函数,只能被其他ush或usf来引用,而usf有入口函数,不可被应用。
整个Engine/Shaders/Private中有近300个文件,一行一行解释显然不现实,这篇决定优先分析引擎中material的源码
这部分源码主要在ShadingModels.ush中,会引用其他的BRDF.ush,下面来慢慢分析
首先是下面两个函数:
NormalText Code
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
|
|
//
@param
DiffSpecMask
.r:
diffuse,
.g:specular
e.g.
float2(1,1)
for
both,
float2(1,0)
for
diffuse
only
float3
SurfaceShading(
FGBufferData
GBuffer,
float3
LobeRoughness,
float3
LobeEnergy,
float3
L,
float3
V,
half3
N,
uint2
Random
)
{
switch(
GBuffer.ShadingModelID
)
{
case
SHADINGMODELID_UNLIT:
case
SHADINGMODELID_DEFAULT_LIT:
case
SHADINGMODELID_SUBSURFACE:
case
SHADINGMODELID_PREINTEGRATED_SKIN:
case
SHADINGMODELID_SUBSURFACE_PROFILE:
case
SHADINGMODELID_TWOSIDED_FOLIAGE:
return
StandardShading(
GBuffer.DiffuseColor,
GBuffer.SpecularColor,
LobeRoughness,
LobeEnergy,
L,
V,
N
);
case
SHADINGMODELID_CLEAR_COAT:
return
ClearCoatShading(
GBuffer,
LobeRoughness,
LobeEnergy,
L,
V,
N
);
case
SHADINGMODELID_CLOTH:
return
ClothShading(
GBuffer,
LobeRoughness,
LobeEnergy,
L,
V,
N
);
case
SHADINGMODELID_EYE:
return
EyeShading(
GBuffer,
LobeRoughness,
LobeEnergy,
L,
V,
N
);
default:
return
0;
}
}
float3
SubsurfaceShading(
FGBufferData
GBuffer,
float3
L,
float3
V,
half3
N,
float
Shadow,
uint2
Random
)
{
float3
SubsurfaceColor
=
ExtractSubsurfaceColor(GBuffer);
switch(
GBuffer.ShadingModelID
)
{
case
SHADINGMODELID_SUBSURFACE:
return
SubsurfaceShadingSubsurf
ace(
GBuffer,
L,
V,
N
);
case
SHADINGMODELID_PREINTEGRATED_SKIN:
return
SubsurfaceShadingPreinte
gratedSkin(
GBuffer,
L,
V,
N
);
case
SHADINGMODELID_TWOSIDED_FOLIAGE:
return
SubsurfaceShadingTwoSide
d(
SubsurfaceColor,
L,
V,
N
);
case
SHADINGMODELID_HAIR:
return
HairShading(
GBuffer,
L,
V,
N,
Shadow,
1,
0,
Random
);
case
SHADINGMODELID_EYE:
return
EyeSubsurfaceShading(
GBuffer,
L,
V,
N
);
default:
return
0;
}
}
|
与引擎中的shading model完全一致
从函数中的switch中不难看出用途,这两个函数就定义了不同shading model使用的基础算法和subsurface算法
这两个函数在DeferredLightngCommon.ush中被调用,比如下面
NormalText Code
1
2
|
|
float3
Shading
=
SurfaceShading(
GBuffer,
LobeRoughness,
1,
L,
V,
N,
Random
)
*
NoL;
Shading
+=
SubsurfaceShading(
GBuffer,
L,
V,
N,
1,
Random
);
|
就是直接把结果线性相加....
SurfaceShading
先看SurfaceShading函数,这个函数表示了material除了subsurface之外的全部内容,可以看出,除了Clear Coat,Cloth , Eye有自己独有的着色算法以外,其他的shading model都用的共通的StandardShading,那么就来看看StandardShading里是什么吧
NormalText Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
|
float3
StandardShading(
float3
DiffuseColor,
float3
SpecularColor,
float3
LobeRoughness,
float3
LobeEnergy,
float3
L,
float3
V,
half3
N
)
{
float
NoL
=
dot(N,
L);
float
NoV
=
dot(N,
V);
float
LoV
=
dot(L,
V);
float
InvLenH
=
rsqrt(
2
+
2
*
LoV
);
float
NoH
=
saturate(
(
NoL
+
NoV
)
*
InvLenH
);
float
VoH
=
saturate(
InvLenH
+
InvLenH
*
LoV
);
NoL
=
saturate(NoL);
NoV
=
saturate(abs(NoV)
+
1e-5);
//
Generalized
microfacet
specular
float
D
=
D_GGX(
LobeRoughness[1],
NoH
)
*
LobeEnergy[1];
float
Vis
=
Vis_SmithJointApprox(
LobeRoughness[1],
NoV,
NoL
);
float3
F
=
F_Schlick(
SpecularColor,
VoH
);
float3
Diffuse
=
Diffuse_Lambert(
DiffuseColor
);
//float3
Diffuse
=
Diffuse_Burley(
DiffuseColor,
LobeRoughness[1],
NoV,
NoL,
VoH
);
//float3
Diffuse
=
Diffuse_OrenNayar(
DiffuseColor,
LobeRoughness[1],
NoV,
NoL,
VoH
);
return
Diffuse
*
LobeEnergy[2]
+
(D
*
Vis)
*
F;
}
|
基本遵循了epic在siggraph2013上发表的paper[1]上的算法
其中D是D_GGX,G是Vis_SmithJointApprox,F是F_Schlick。具体代码在BRDF.ush中都有。
再加上Lambert的diffuse构成了基础的shading。
Clear Coat比较复杂,有空可能会单独写一篇
Cloth其实是定义了两种不同的BRDF,在这两者间作插值
NormalText Code
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
|
|
float3
ClothShading(
FGBufferData
GBuffer,
float3
LobeRoughness,
float3
LobeEnergy,
float3
L,
float3
V,
half3
N
)
{
const
float3
FuzzColor
=
saturate(GBuffer.CustomData.rgb);
const
float
Cloth
=
saturate(GBuffer.CustomData.a);
float
NoL
=
dot(N,
L);
float
NoV
=
dot(N,
V);
float
LoV
=
dot(L,
V);
float
InvLenH
=
rsqrt(
2
+
2
*
LoV
);
float
NoH
=
saturate(
(
NoL
+
NoV
)
*
InvLenH
);
float
VoH
=
saturate(
InvLenH
+
InvLenH
*
LoV
);
NoL
=
saturate(NoL);
NoV
=
saturate(abs(NoV)
+
1e-5);
//
Diffuse
float3
Diffuse
=
Diffuse_Lambert(
GBuffer.DiffuseColor
);
float3
Diff
=
Diffuse
*
LobeEnergy[2];
//
Cloth
-
Asperity
Scattering
-
Inverse
Beckmann
Layer
float3
F1
=
F_Schlick(
FuzzColor,
VoH
);
float
D1
=
D_InvGGX(
LobeRoughness[1],
NoH
);
float
V1
=
Vis_Cloth(
NoV,
NoL
);
float3
Spec1
=
D1
*
V1
*
F1;
//
Generalized
microfacet
specular
float3
F2
=
F_Schlick(
GBuffer.SpecularColor,
VoH
);
float
D2
=
D_GGX(
LobeRoughness[1],
NoH
)
*
LobeEnergy[1];
float
V2
=
Vis_SmithJointApprox(
LobeRoughness[1],
NoV,
NoL
);
float3
Spec2
=
D2
*
V2
*
F2;
float3
Spec
=
lerp(Spec2,
Spec1,
Cloth);
return
Diff
+
Spec;
}
|
Spec2和standShading的高光部分一样,Spec1的D和G则略有不同,用GBuffer中的custom.a做插值,得到最终的输出(Diffuse还是Lambert)
Eye的部分更加简单,就是StandShading去掉Diffuse的部分
NormalText Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
|
float3
EyeShading(
FGBufferData
GBuffer,
float3
LobeRoughness,
float3
LobeEnergy,
float3
L,
float3
V,
half3
N
)
{
float
NoL
=
dot(N,
L);
float
NoV
=
dot(N,
V);
float
LoV
=
dot(L,
V);
float
InvLenH
=
rsqrt(
2
+
2
*
LoV
);
float
NoH
=
saturate(
(
NoL
+
NoV
)
*
InvLenH
);
float
VoH
=
saturate(
InvLenH
+
InvLenH
*
LoV
);
NoL
=
saturate(NoL);
NoV
=
saturate(abs(NoV)
+
1e-5);
//
Generalized
microfacet
specular
float
D
=
D_GGX(
LobeRoughness[1],
NoH
)
*
LobeEnergy[1];
float
Vis
=
Vis_SmithJointApprox(
LobeRoughness[1],
NoV,
NoL
);
float3
F
=
F_Schlick(
GBuffer.SpecularColor,
VoH
);
return
D
*
Vis
*
F;
}
|
不过要注意,NoH和VoH的计算方法和StandShading是不同的。
SubsurfaceShading
说完了surfaceShading,下面就来说Subsurface的部分。
首先是最普通的SubsurfaceShading:
NormalText Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
|
float3
SubsurfaceShadingSubsurf
ace(
FGBufferData
GBuffer,
float3
L,
float3
V,
half3
N
)
{
float3
SubsurfaceColor
=
ExtractSubsurfaceColor(GBuffer);
float
Opacity
=
GBuffer.CustomData.a;
float3
H
=
normalize(V
+
L);
//
to
get
an
effect
when
you
see
through
the
material
//
hard
coded
pow
constant
float
InScatter
=
pow(saturate(dot(L,
-V)),
12)
*
lerp(3,
.1f,
Opacity);
//
wrap
around
lighting,
/(PI*2)
to
be
energy
consistent
(hack
do
get
some
view
dependnt
and
light
dependent
effect)
//
Opacity
of
0
gives
no
normal
dependent
lighting,
Opacity
of
1
gives
strong
normal
contribution
float
NormalContribution
=
saturate(dot(N,
H)
*
Opacity
+
1
-
Opacity);
float
BackScatter
=
GBuffer.GBufferAO
*
NormalContribution
/
(PI
*
2);
//
lerp
to
never
exceed
1
(energy
conserving)
return
SubsurfaceColor
*
lerp(BackScatter,
1,
InScatter);
}
|
观察NormalContribution项,显然Opacity=1时,NormalContribution=dot(N,H)。也就是说次表面的深度受到法线和半角的点积影响。
然后是皮肤的次表面
NormalText Code
1
2
3
4
5
6
7
8
|
|
float3
SubsurfaceShadingPreinte
gratedSkin(
FGBufferData
GBuffer,
float3
L,
float3
V,
half3
N
)
{
float3
SubsurfaceColor
=
ExtractSubsurfaceColor(GBuffer);
float
Opacity
=
GBuffer.CustomData.a;
float3
PreintegratedBRDF
=
Texture2DSampleLevel(PreIntegratedBRDF,
PreIntegratedBRDFSampler
,
float2(saturate(dot(N,
L)
*
.5
+
.5),
1
-
Opacity),
0).rgb;
return
PreintegratedBRDF
*
SubsurfaceColor;
}
|
意外的简单,其实就是查找一张LUT的值。
这张图很容易找到,打开unreal4,在content browser的View Options里选择Show Engine Content
这样就可以在Engine Content/EngineMaterials/中找到这张LUT了
然后是twosided的次表面:
NormalText Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
|
float3
SubsurfaceShadingTwoSide
d(
float3
SubsurfaceColor,
float3
L,
float3
V,
half3
N
)
{
//
http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
float
Wrap
=
0.5;
float
NoL
=
saturate(
(
dot(-N,
L)
+
Wrap
)
/
Square(
1
+
Wrap
)
);
//
GGX
scatter
distribution
float
VoL
=
saturate(
dot(V,
-L)
);
float
a
=
0.6;
float
a2
=
a
*
a;
float
d
=
(
VoL
*
a2
-
VoL
)
*
VoL
+
1;
//
2
mad
float
GGX
=
(a2
/
PI)
/
(d
*
d);
//
2
mul,
1
rcp
return
NoL
*
GGX
*
SubsurfaceColor;
}
|
NoL经过了Wrap来保证能量守恒,乘以GGX的NormalDistribution得到最终结果
这里注意GGX没有使用roughness,而是将a=roughness*roughness设成0.6。相当于roughness=0.77
hair的次表面特别复杂,分R、TT、TRT等很多部分,以后单独拿出来讲。eye也一样
Reference
[1] https://de45xmedrsdbp.Resources/files/2013SiggraphPresentation
sNotes-26915738.pdf