基于物理渲染

3.5k words

Desc:

记录阅读 build in 中关于BRDF的笔记

法线分布项

Image text

多次表面反射


微表面BRDF不考虑从微表面多次反射(“反弹”)的光。 这种简化会导致一些能量损失和过度暗化,特别是对于粗糙金属

Image text
Image text

薄膜干涉

常见的是用MatCap方式实现
波动光学BRDF模型

ShaderToy
ShaderToy
如何在UE5中做出带薄膜干涉的油污地面 - 落月满江树的文章

反射项:

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

// 法线分布函数(各向同性)
inline float GGXTerm (float NdotH, float roughness)
{
float a2 = roughness * roughness;
float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
return a2 /( (d * d + 1e-7f)*PI ); // This function is not intended to be running on Mobile,
// therefore epsilon is smaller than what can be represented by half
}

/**
* @brief GGX Anisotropic Distribution
* @param alpha_t Roughness in tangent direction
* @param alpha_b Roughness in bitangent direction
* @param ToH Dot product between tangent vector and halfvector
* @param BoH Dot product between bitangent vector and halfvector
* @param NoH Dot product between normal vector and halfvector
*
* \f$ D(h;\alpha_t,\alpha_b) = \frac{1}{\pi\alpha_t\alpha_b}\frac{1}{ \big( (n\cdot h)^2 + \frac{(t \cdot h)^2}{\alpha_t^2} + \frac{(b\cdot h)^2}{\alpha_b^2} \big)^2 } \f$
*/
float ggx_anisotropic_ndf(float alpha_t, float alpha_b,
float ToH, float BoH, float NoH)
{
vec3 v = vec3(alpha_b*ToH,alpha_t*BoH, alpha_t*alpha_b*NoH);
float v2 = dot(v,v);
float a2 = alpha_t*alpha_b;
float w2 = a2/v2;
return( a2*w2*w2*m_i_pi );
}

// 菲涅尔方程
inline half3 FresnelTerm (half3 F0, half cosA)
{
half t = Pow5 (1 - cosA); // ala Schlick interpoliation
return F0 + (1-F0) * t;
}


// Ref: http://jcgt.org/published/0003/02/03/paper.pdf
inline float SmithJointGGXVisibilityTerm (float NdotL, float NdotV, float roughness)
{
#if 0
// Original formulation:
// lambda_v = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
// lambda_l = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
// G = 1 / (1 + lambda_v + lambda_l);

// Reorder code to be more optimal
half a = roughness;
half a2 = a * a;

half lambdaV = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
half lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);

// Simplify visibility term: (2.0f * NdotL * NdotV) / ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
return 0.5f / (lambdaV + lambdaL + 1e-5f); // This function is not intended to be running on Mobile,
// therefore epsilon is smaller than can be represented by half
#else
// Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
float a = roughness;
float lambdaV = NdotL * (NdotV * (1 - a) + a);
float lambdaL = NdotV * (NdotL * (1 - a) + a);

#if defined(SHADER_API_SWITCH)
return 0.5f / (lambdaV + lambdaL + 1e-4f); // work-around against hlslcc rounding error
#else
return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif

#endif
}

float CookTorranceBRDF(Surface surface,BRDF brdf,Light light){
float3 HalfDir=normalize(surface.viewDirection+light.direction);
float NdotH=saturate(dot(HalfDir,surface.normal));
float LdotH=saturate(dot(light.direction,HalfDir));
float NdotV=saturate(dot(surface.normal,surface.viewDirection));
float NdotL=saturate(dot(surface.normal,light.direction));
float D=GGXTerm(NdotH,brdf.roughness);
float F=FresnelTerm(brdf.specular,LdotH);
float V = SmithJointGGXVisibilityTerm (LdotH, NdotV, brdf.roughness);
float specularTerm=V*D*PI;
specularTerm=max(0,specularTerm*LdotH);
return specularTerm*F;//+brdf.diffuse;
//return (D*F*G)/(4*dot(surface.normal,light.direction)*dot(surface.normal,surface.viewDirection));//;
}

总颜色计算

1
2
3
4
5
6
7

float3 GetCookTorranceLighting(Surface surface,BRDF brdf,Light light){
float3 incomingLight=IncomingLight(surface,light);
float brdfLight=CookTorranceBRDF(surface,brdf,light);
return incomingLight*brdfLight+incomingLight*surface.color;
}

效果图

Image text

参考链接

基于物理着色:BRDF
SIGGRAPH
D、F、G 公式
球面高斯近似
基于物理的着色模型
基于物理的着色模型
SIGGRAPH 2013
光照模型 PBR
球面高斯近似
shadertoy