콘텐츠로 건너뛰기

# 综合研究基于物理的渲染算法Unity3D[Translation]

Unity 5, 虚幻引擎 4, 寒霜引擎, 甚至是 ThreeJS, 更多的游戏引擎在使用它。

## 再回到 BRDF…

‘能量守恒 (Energy Conservation)’是说从地表反射的总光量，比地表收到的总量少。

#### Vertex Program

Vertex 程序跟 Unity 里的编写 Shade r相关自学书上生成的类似。

#### Fragment Program

fragment 程序里，在算法上要定义之后可以使用的变量Set:

#### Roughness

```.wp-block-code {
border: 0;
}

.wp-block-code > span {
display: block;
overflow: auto;
}

.shcb-language {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
position: absolute;
width: 1px;
word-wrap: normal;
word-break: normal;
}

.hljs {
box-sizing: border-box;
}

.hljs.shcb-code-table {
display: table;
width: 100%;
}

.hljs.shcb-code-table > .shcb-loc {
color: inherit;
display: table-row;
width: 100%;
}

.hljs.shcb-code-table .shcb-loc > span {
display: table-cell;
}

.wp-block-code code.hljs:not(.shcb-wrap-lines) {
white-space: pre;
}

.wp-block-code code.hljs.shcb-wrap-lines {
white-space: pre-wrap;
}

.hljs.shcb-line-numbers {
border-spacing: 0;
counter-reset: line;
}

.hljs.shcb-line-numbers > .shcb-loc {
counter-increment: line;
}

.hljs.shcb-line-numbers .shcb-loc > span {
}

.hljs.shcb-line-numbers .shcb-loc::before {
border-right: 1px solid #ddd;
content: counter(line);
display: table-cell;
text-align: right;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
white-space: nowrap;
width: 1%;
}
```float roughness = 1- (_Glossiness * _Glossiness);   // 1 - smoothness*smoothness
roughness = roughness * roughness;```Code language: JavaScript (javascript)```

#### Metallic

``````float3 diffuseColor = _Color.rgb * (1-_Metallic) ;
float3 specColor = lerp(_SpecularColor.rgb, _Color.rgb, _Metallic * 0.5);```Code language: C# (cs)```

#### 构成 PBR SHADER Pt.1 :法线分布函数 (Specular Function)法线分布函数是什么?

NDF统计说明表面的微面正常分布。根据用途 NDF的作用是调整反射 (明暗)亮度的权重函数。重点是要以几何学特征想NDF。

``````float3 SpecularDistribution = specColor;

//the algorithm implementations will go here

return float4(float3(1,1,1) * SpecularDistribution.rgb,1);```Code language: JavaScript (javascript)```

#### Blinn-Phong NDF

``````float BlinnPhongNormalDistribution(float NdotH, float specularpower, float speculargloss){
float Distribution = pow(NdotH,speculargloss) * specularpower;
Distribution *= (2+specularpower) / (2*3.1415926535);
return Distribution;
}```Code language: JavaScript (javascript)```

Phong specularity的 Blinn 近似是由 Phong Specular Model的优化做成的。Blinn(图形研究人员名字), 他决定比起计算每个frame的光反射，做法线Vector和半Vector的内积会更快。算法上 Blinn比 Phong更柔和，产出的结果更多样化。Blinn-Phong 虽然不算是正确的物理算法，但是可以生成符合特定游戏表现意图的高光。

``SpecularDistribution *=  BlinnPhongNormalDistribution(NdotH, _Glossiness,  max(1,_Glossiness * 40));`Code language: C# (cs)`

Phong NDF

``````float PhongNormalDistribution(float RdotV, float specularpower, float speculargloss){
float Distribution = pow(RdotV,speculargloss) * specularpower;
Distribution *= (2+specularpower) / (2*3.1415926535);
return Distribution;
}```Code language: JavaScript (javascript)```

Phong 算法是非物理算法，但会生成比上方 Blinn 近似更精确的结果。下面是实现案例。

``SpecularDistribution *=  PhongNormalDistribution(RdotV, _Glossiness, max(1,_Glossiness * 40));`Code language: C# (cs)`

Beckman NDF

Beckman Normal Distribution 功能是更高级的功能，是考虑粗糙度值的。

KS B 0161里表面粗糙度是用以下三个方法规定的。

``````float BeckmannNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness*roughness;
float NdotHSqr = NdotH*NdotH;
return max(0.000001,(1.0 / (3.1415926535*roughnessSqr*NdotHSqr*NdotHSqr))
* exp((NdotHSqr-1)/(roughnessSqr*NdotHSqr)));
}```Code language: JavaScript (javascript)```

``SpecularDistribution *=  BeckmannNormalDistribution(roughness, NdotH);`Code language: C# (cs)`

Gaussian NDF

Gaussian Normal Distribution 模型跟其他模型比用的频率不多。因为设到更高的柔和值的时候，会生成比想要的更柔和的倾斜面高光。在艺术观点看的时候，这是可取的，但是对实际物理特征还需论争。

``````float GaussianNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness*roughness;
float thetaH = acos(NdotH);
return exp(-thetaH*thetaH/roughnessSqr);
}```Code language: JavaScript (javascript)```

``SpecularDistribution *=  GaussianNormalDistribution(roughness, NdotH);`Code language: C# (cs)`

GGX NDF

GGX不是现在普遍适用的，但的确是受欢迎的算法中的一个。现代应用软件大部分是依赖于 BRDF 部分功能。GGX是 Bruce Walter和 Kenneth Torrance开发的。这篇论文里使用的函数都是普遍使用的函数中的一部分。

``````float GGXNormalDistribution(float roughness, float NdotH)
{
float roughnessSqr = roughness*roughness;
float NdotHSqr = NdotH*NdotH;
float TanNdotHSqr = (1-NdotHSqr)/NdotHSqr;
return (1.0/3.1415926535) * sqr(roughness/(NdotHSqr * (roughnessSqr + TanNdotHSqr)));
}```Code language: C# (cs)```
``SpecularDistribution *=  GGXNormalDistribution(roughness, NdotH);`Code language: C# (cs)`

GGX 算法的反射高光非常坚固，但是通过圆球表面后还是维持柔和的分布。这是很好的案例，来说明为什么 GGX 算法通过金属表面复制反射的扭曲是好的。

Trowbridge-Reitz NDF

Trowbridge-Reitz接近法和GGX研发自同一个论文，得出的结果与GGX算法非常相似。比较显著的差异是物体极端边缘位置高光比GGX更加平滑。GGX比锐角有着更加粗糙的衰减。

``````float TrowbridgeReitzNormalDistribution(float NdotH, float roughness){
float roughnessSqr = roughness*roughness;
float Distribution = NdotH*NdotH * (roughnessSqr-1.0) + 1.0;
return roughnessSqr / (3.1415926535 * Distribution*Distribution);
}

SpecularDistribution *=  TrowbridgeReitzNormalDistribution(NdotH, roughness);```Code language: C# (cs)```

Ward Anisotropic NDF

``````float WardAnisotropicNormalDistribution(float anisotropic, float NdotL, float NdotV, float NdotH, float HdotX, float HdotY)
{
float aspect = sqrt(1.0h-anisotropic * 0.9h);
float X = max(.001, sqr(1.0-_Glossiness)/aspect) * 5;
float Y = max(.001, sqr(1.0-_Glossiness)*aspect) * 5;
float exponent = -(sqr(HdotX/X) + sqr(HdotY/Y)) / sqr(NdotH);
float Distribution = 1.0 / (4.0 * 3.14159265 * X * Y * sqrt(NdotL * NdotV));
Distribution *= exp(exponent);
return Distribution;
}

SpecularDistribution *=  WardAnisotropicNormalDistribution(_Anisotropic,NdotL, NdotV, NdotH, dot(halfDirection, i.tangentDir), dot(halfDirection,  i.bitangentDir));```Code language: JavaScript (javascript)```

Ward接近方式的非各同向性与Trowbridge-Reitz方法非常不同。Specular 高光在表面越柔和就会更加柔和、消失的也更加迅速。和Trowbridge-Reitz方法一样，Ward Algorithm需要tangent和Bit tangent数据，但使用的不光是一般内积和光的内积，还有正常内积及我们观点里的内积。

microfacet BRDF方程式的核心部分与活性表面（根据从L到V的光能反射表面区域覆盖的面积）和微表面的总面积之间的比例有关。

``````float GeometricShadow = 1;
//the algorithm implementations will go here
return float4(float3(1,1,1) * GeometricShadow,1);```Code language: JavaScript (javascript)```

Implicit GSF

``````float ImplicitGeometricShadowingFunction (float NdotL, float NdotV)
{
float Gs =  (NdotL*NdotV);
return Gs;
}

### Ashikhmin-Shirley GSF

``````float AshikhminShirleyGSF (float NdotL, float NdotV, float LdotH)
{
float Gs = NdotL*NdotV/(LdotH*max(NdotL,NdotV));
return  (Gs);
}

GeometricShadow *= AshikhminShirleyGSF (NdotL, NdotV, LdotH);```Code language: JavaScript (javascript)```

Ashikhmin-Premoze GSF

Ashikhmin-Premoze GSF与Ashikhmin-Shirley方式不同，设计成要与各同向性的NDF一起使用。和Ashikhmin-Shirley同样的是，这个是一个非常微妙的GSF。

``````float AshikhminPremozeGeometricShadowingFunction (float NdotL, float NdotV)
{
float Gs = NdotL*NdotV/(NdotL+NdotV - NdotL*NdotV);
return  (Gs);
}

Duer GSF

``````float DuerGeometricShadowingFunction (float3 lightDirection,float3 viewDirection,
float3 normalDirection,float NdotL, float NdotV)
{
float3 LpV = lightDirection + viewDirection;
float Gs = dot(LpV,LpV) * pow(dot(LpV,normalDirection),-4);
return  (Gs);
}

Duer提出了以下GFS函数，以解决在下面使用的Ward GSF函数里面发现的specularity和相关问题。Duer GSF虽然是表现与上面Ashikhmin-Shirley类似的结果，但是更适合各同向性的BRDF以及有着非常些微异方性的BRDF。

Neumann GSF

Neumann – Neumann GSF是适合异方性正规分布的GSF的另一个示例。视野方向或者光线的方向越大，就会生成更多几何阴影。

``````float NeumannGeometricShadowingFunction (float NdotL, float NdotV)
{
float Gs = (NdotL*NdotV)/max(NdotL, NdotV);
return  (Gs);
}

Kelemen GSF

Kelemen GSF可以提供合适的能量节约GSF。和之前的大部分Model不同，虽然几何阴影的比例不是固定的，但他根据视野角度而变化。这是Cook-Torrance几何阴影函数的极端近似。

Modified-Kelemen GSF

``````float ModifiedKelemenGeometricShadowingFunction (float NdotV, float NdotL, float roughness)
{
float c = 0.797884560802865;    // c = sqrt(2 / Pi)
float k = roughness * roughness * c;
float gH = NdotV  * k +(1-k);
return (gH * gH * NdotL);
}

Cook-Torrance GSF

``````float CookTorrenceGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH)
{
float Gs = min(1.0, min(2*NdotH*NdotV / VdotH, 2*NdotH*NdotL / VdotH));
return  (Gs);
}

Ward GSF

``````float WardGeometricShadowingFunction (float NdotL, float NdotV, float VdotH, float NdotH)
{
float Gs = pow( NdotL * NdotV, 0.5);
return  (Gs);
}

Ward GSF是强化的默示性GSF。Ward使用此方法强化正规分布函数。它可以特别有效的从各个角度来强调表面异方性Band。

Smith为基础的GSF比其他的GSF更加准确，更加被广泛认可，同时考虑到正规分布的粗糙度和形状。该函数的DSF需要处理两个片段才能进行计算。

Walter et all. GSF

Walter et al. NDF是GGX GSF的一般形态，为了能与之共同使用制作了这个函数。
Walter et al. GSF感觉是“虽然对锐角附近或者粗糙表面之外的BSDF(双向散射分布函数)的形成影响很小，但是需要保存能量”。考虑到这一点，我们使用“Roughness”作为GSF的动力，并创建了一个GSF来尊重这一原则。

``````float BeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
float roughnessSqr = roughness*roughness;
float NdotLSqr = NdotL*NdotL;
float NdotVSqr = NdotV*NdotV;

float calulationL = (NdotL)/(roughnessSqr * sqrt(1- NdotLSqr));
float calulationV = (NdotV)/(roughnessSqr * sqrt(1- NdotVSqr));

float SmithL = calulationL < 1.6 ? (((3.535 * calulationL) + (2.181 * calulationL * calulationL))/(1 + (2.276 * calulationL) + (2.577 * calulationL * calulationL))) : 1.0;
float SmithV = calulationV < 1.6 ? (((3.535 * calulationV) + (2.181 * calulationV * calulationV))/(1 + (2.276 * calulationV) + (2.577 * calulationV * calulationV))) : 1.0;

float Gs =  (SmithL * SmithV);
return Gs;
}

Walter et al.最初是为与Beckman NDF一起使用而制作的，推测是适合与Phong NDF一起使用的GSF。

``````float BeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
float roughnessSqr = roughness*roughness;
float NdotLSqr = NdotL*NdotL;
float NdotVSqr = NdotV*NdotV;

float calulationL = (NdotL)/(roughnessSqr * sqrt(1- NdotLSqr));
float calulationV = (NdotV)/(roughnessSqr * sqrt(1- NdotVSqr));

float SmithL = calulationL < 1.6 ? (((3.535 * calulationL) + (2.181 * calulationL * calulationL))/(1 + (2.276 * calulationL) + (2.577 * calulationL * calulationL))) : 1.0;
float SmithV = calulationV < 1.6 ? (((3.535 * calulationV) + (2.181 * calulationV * calulationV))/(1 + (2.276 * calulationV) + (2.577 * calulationV * calulationV))) : 1.0;

float Gs =  (SmithL * SmithV);
return Gs;
}

Schlick做了几个在其他Smith GSF里面也可以使用的Smith GSF的近似值。这是Smith GSF的Schlick近似值。

``````float SchlickGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
float roughnessSqr = roughness*roughness;
float SmithL = (NdotL)/(NdotL * (1-roughnessSqr) + roughnessSqr);
float SmithV = (NdotV)/(NdotV * (1-roughnessSqr) + roughnessSqr);
return (SmithL * SmithV);
}

``````float SchlickBeckmanGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
float roughnessSqr = roughness*roughness;
float k = roughnessSqr * 0.797884560802865;

float SmithL = (NdotL)/ (NdotL * (1- k) + k);
float SmithV = (NdotV)/ (NdotV * (1- k) + k);

float Gs =  (SmithL * SmithV);
return Gs;
}

Schlick-GGX GSF

GGX的 Schlick Approximation只是把我们的光照度值除以2。

``````float SchlickGGXGeometricShadowingFunction (float NdotL, float NdotV, float roughness)
{
float k = roughness / 2;

float SmithL = (NdotL)/ (NdotL * (1- k) + k);
float SmithV = (NdotV)/ (NdotV * (1- k) + k);

float Gs =  (SmithL * SmithV);
return Gs;
}

``schlick = x + (1-x) * pow(1-dotProduct,5);`Code language: C# (cs)`

``mix(x,1,pow(1-dotProduct,5)); `Code language: C# (cs)`

``````float MixFunction(float i, float j, float x)
{
return  j * x + i * (1.0 - x);
}

float SchlickFresnel(float i)
{
float x = clamp(1.0-i, 0.0, 1.0);
float x2 = x*x;
return x2*x2*x;
}

//normal incidence reflection calculation
float F0 (float NdotL, float NdotV, float LdotH, float roughness)
{
float FresnelLight = SchlickFresnel(NdotL);
float FresnelView = SchlickFresnel(NdotV);
float FresnelDiffuse90 = 0.5 + 2.0 * LdotH*LdotH * roughness;
return  MixFunction(1, FresnelDiffuse90, FresnelLight) * MixFunction(1, FresnelDiffuse90, FresnelView);
}```Code language: JavaScript (javascript)```

Schlick Fresnel

``````float3 SchlickFresnelFunction(float3 SpecularColor,float LdotH)
{
return SpecularColor + (1 - SpecularColor)* SchlickFresnel(LdotH);
}

FresnelFunction *=  SchlickFresnelFunction(specColor, LdotH);```Code language: JavaScript (javascript)```

``````_Ior("Ior",  Range(1,4)) = 1.5

float SchlickIORFresnelFunction(float ior ,float LdotH)
{
float f0 = pow(ior-1,2)/pow(ior+1, 2);
return f0 + (1-f0) * SchlickFresnel(LdotH);
}

FresnelFunction *=  SchlickIORFresnelFunction(_Ior, LdotH);```Code language: JavaScript (javascript)```

Spherical-Gaussian Fresnel

Spherical-Gaussian Fresnel函数能算出与Schlick ‘s Approximation非常相似的结果。

``````float SphericalGaussianFresnelFunction(float LdotH,float SpecularColor)
{
float power = ((-5.55473 * LdotH) - 6.98316) * LdotH;
return SpecularColor + (1 - SpecularColor) * pow(2,power);
}

FresnelFunction *= SphericalGaussianFresnelFunction(LdotH, specColor);```Code language: JavaScript (javascript)```

``````UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection,
float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos)
{
//Unity light Setup ::
UnityLight light;
light.color = lightColor;
light.dir = lightDirection;
light.ndotl = max(0.0h,dot( normalDirection, lightDirection));
UnityGIInput d;
d.light = light;
d.worldPos = worldPos;
d.worldViewDir = viewDirection;
d.atten = attenuation;
d.ambient = 0.0h;
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.boxMin[0] = unity_SpecCube0_BoxMin;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.probeHDR[0] = unity_SpecCube0_HDR;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
d.probeHDR[1] = unity_SpecCube1_HDR;
Unity_GlossyEnvironmentData ugls_en_data;
ugls_en_data.roughness = roughness;
ugls_en_data.reflUVW = viewReflectDirection;
UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data );
return gi;
}```Code language: C# (cs)```
``````float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * (  NdotL * NdotV));

float3 lightingModel = (diffuseColor + specularity);
lightingModel *= NdotL;
float4 finalDiffuse = float4(lightingModel * attenColor,1);
return finalDiffuse;```Code language: JavaScript (javascript)```

Getting Unity Lighting Information Involved

먼저, Reflection Probe 을 장면에 추가하고 구워냅니다. 그런 다음이 함수를 셰이더에 추가해야합니다.

``````UnityGI GetUnityGI(float3 lightColor, float3 lightDirection, float3 normalDirection,float3 viewDirection,
float3 viewReflectDirection, float attenuation, float roughness, float3 worldPos)
{
//Unity light Setup ::
UnityLight light;
light.color = lightColor;
light.dir = lightDirection;
light.ndotl = max(0.0h,dot( normalDirection, lightDirection));
UnityGIInput d;
d.light = light;
d.worldPos = worldPos;
d.worldViewDir = viewDirection;
d.atten = attenuation;
d.ambient = 0.0h;
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.boxMin[0] = unity_SpecCube0_BoxMin;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.probeHDR[0] = unity_SpecCube0_HDR;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
d.probeHDR[1] = unity_SpecCube1_HDR;
Unity_GlossyEnvironmentData ugls_en_data;
ugls_en_data.roughness = roughness;
ugls_en_data.reflUVW = viewReflectDirection;
UnityGI gi = UnityGlobalIllumination(d, 1.0h, normalDirection, ugls_en_data );
return gi;
}```Code language: JavaScript (javascript)```
``````float3 specularity = (SpecularDistribution * FresnelFunction * GeometricShadow) / (4 * (  NdotL * NdotV));

float3 lightingModel = (diffuseColor + specularity);
lightingModel *= NdotL;
float4 finalDiffuse = float4(lightingModel * attenColor,1);
return finalDiffuse;```Code language: JavaScript (javascript)```

fragment程序内部使用这个函数，可以生成适合环境取样的数据。

태그: