GIT地址:

https://github.com/lingzerg/LingzergDemo ​ github.com

PBR中一个比较典型的实现-BRDF, 就是表达一束光照射在一个表面(微面元)上之后反射出去的结果

BRDF有三个参数构成了整个运算的基础

F0, baseColor , 而金属度决定更靠近那个值

F0就是反射率, 当我们90度直视一个表面的时候, 看到的光子回弹的比例

unity中无法设置这个F0, 少了一个控制的维度, 可能需要我们手动添加, 后面我们会在代码中添加这个

PBR方程/BRDF函数

BRDF可以看成两个函数

kd, ks

DGF的公式我们先看一眼:

PBR那个公式L 则是整个半球的辐照度积分

DOT – 点积

```Shader "Unlit/MyBRDF"
{
Properties
{
_Color ("Base Color", Color) = (1,1,1,1)
[Gamma] _Metallic ("Metallic", Range(0,1)) = 0.5
_Roughness ("Roughness", Range(0,1)) = 0.5
_BaseF0 ("BaseF0",Range(0,1)) = 0.04
}
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityStandardBRDF.cginc"

#define PI 3.14159274f
struct VertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;

};
struct Interpolators
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
fixed3 normal : TEXCOORD2;

};
fixed4 _Color;
fixed _Metallic,_Roughness,_BaseF0;
Interpolators vert (VertexInput v)
{
Interpolators i;
i.vertex = UnityObjectToClipPos(v.vertex);
i.worldPos = mul(unity_ObjectToWorld, v.vertex);

i.normal = UnityObjectToWorldNormal(v.normal);
i.normal = normalize(i.normal);
return i;
}
fixed4 frag (Interpolators i) : SV_Target
{

return 0;
}
ENDCG
}
}
}```

F的公式:

```fixed3 fresnelSchlick(float cosTheta, fixed3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}```

```fixed3 F0 = _BaseF0;
F0 = lerp(F0, _Color.rgb, _Metallic);
fixed3 F = fresnelSchlick(VdotH, F0);```

```fixed4 frag (Interpolators i) : SV_Target
{
fixed4 FinalColor = 0;
float3 lightColor = _LightColor0.rgb;
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

float3 normal = normalize(i.normal);
float VdotH = max(saturate(dot(viewDir, halfVector)), 0.000001);
float NdotL = max(saturate(dot(normal, lightDir)), 0.000001);
fixed3 F0 = _BaseF0;
F0 = lerp(F0, _Color.rgb, _Metallic);
fixed3 F = fresnelSchlick(VdotH, F0);

fixed kd = (1-F)*(1-_Metallic);
float3 diffuse = _Color/PI * kd;
FinalColor.rgb = diffuse * lightColor * NdotL;
FinalColor.a;
return FinalColor;
}```

`return FinalColor * PI;`

```float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); float3 halfVector = normalize(lightDir + viewDir);   而  就是粗糙度, 但是我们要让输入的粗糙度先做一个平方操作, 这样的目的是为了让滑条和粗糙度的映射关系等于下面这个曲线:  这样美术在拉动粗糙度的滑动条时, 得到的值会偏小, 更容易控制高光   然后为了方便,我们单独建一个方法, 在下面调用,   方法的内容就是公式的内容:   fixed DistributionGGX(fixed3 NdotH, fixed a) {
fixed a2 = a*a;
fixed denom = (NdotH*NdotH * (a2-1)+1);
denom = PI * denom * denom;
return a2/denom;
}   接着我们输出一下D看下效果:   fixed D = DistributionGGX(NdotH,roughness);
return D;  嗯 稍微有点大不过不要紧, 我们后面会增加几何遮蔽项, 以及配平的除数   几何遮蔽 – G   先放公式:            也就是说几何遮蔽等于要调用两次  函数,   一次计算灯光的遮蔽情况, 一次计算视角的遮蔽情况,最后乘到一起   几何遮蔽描述的是微面元中两个物理情况:  就是说在微面元上, 不仅要光能照到, 并且眼睛也要能看到   所以我们要求两次G, 然后把他们乘起来, 几何遮蔽会减弱我们看到的灯光效果   所以老规矩, 我们先创建一个函数:   fixed SchlickGGX(float cosTheta, fixed k) {
return cosTheta/(cosTheta* (1-k)+k);
}   然后我们先计算    因为是直接光, 所以我们直接用直接光的公式:      fixed k_dir = pow((squareRoughness+1),2)/8;   接着我们计算两次G项 并乘到一起, 然后输出到颜色看下效果:   fixed ggx1 = SchlickGGX(NdotL,k_dir);
fixed ggx2 = SchlickGGX(NdotV,k_dir);
fixed G = ggx1 * ggx2;
return G;   结果如下:  现在我们已经有了FDG, 我们先输出一下FDG的乘积看下效果:   return fixed4(F*G*D,1);  这时候会发现高光变小了, 这是因为G项的遮蔽作用   然后我们把最后的配平参数写上:   FDG /= 4*NdotV*NdotL;   这个配平参数的推导, 推荐大家看这篇文章中镜面反射如何推导的部分:  TC130：彻底看懂PBR/BRDF方程 ​ zhuanlan.zhihu.com   实在过于繁琐, 我这里就不展开讲解了   如果你此时输出FDG, 会发现FDG非常小, 这依旧是由于unity那个缩放的PI 导致的 TC130：彻底看懂PBR/BRDF方程 如果你此时输出FDG, 会发现FDG非常小, 这依旧是由于unity那个缩放的PI 导致的   我们直接把漫反射和高光项加到一起, 然后乘以外部的乘数   FinalColor.rgb = diffuse;
FinalColor.rgb += +FDG;

FinalColor.rgb *=  lightColor * NdotL * PI;
FinalColor.a = 1;
return FinalColor;   最后输出看下结果:  然后我们可以看到, 如果在F0 = 0.04的情况下 和unity的standard材质高光还是不同, 我实际测试, 如果把F0换成:  那幺unity的F0 显然不等于0.04了   我实测大概是0.15左右, 除了π 这里也是个让人哭笑不得的地方   而最后和unity的standard还是有一些出入   主要是我是严格按公式里的方式算的, 比如ue的F用的是一个近似算法:   float3 F = F0 + (1 - F0) * exp2((-5.55473 * vh - 6.98316) * vh);   unity肯定也会有一定的改动, 所以略有出入并不要紧,并且我觉得我实现的效果更接近公式的效果,毕竟unity是一个需要结果乘π的引擎…   我们也可以通过拉动参数调整   我也推荐你试试去掉结尾的π, 然后把光强拉到3.14试试   我在shader中加一个开关方便你测试  到这里就全部结束了, 我写的几度崩溃, 因为太长了…   相信看到这里的你也是个猛士   谢谢你的阅读, 并且如果你有任何建议或者吐槽欢迎留言   对于理论我主要参考的文章是这篇:  https://learnopengl.com/PBR/Theory ​ learnopengl.com  中文还有一篇很不错的分析 也推荐大家看下:   Download as PDF   ```