5.1.1 PBR
Physically Based Rendering
字面意思:基于物理的渲染
什么叫基于物理?
答:
- 基于物理的材质(Material)
- 基于物理的光照(Lighting)
- 基于物理适配的摄像机(Camera)
5.1.2 材质(光照模型)基于物理
基于物理的渲染依然是对现实世界的近似
需要满足三个条件
- 基于微平面(MicroFacet)的表面模型
- 能量守恒
- 应用基于物理的BRDF
微平面
将物体表面建模成无数微观尺度上有随机朝向的理想镜面反射
的小平面的理论。
左粗糙,右光滑。
能量守恒
概念:出射光线的能量,永远不能大于 入射光线的能量
在光线强度一定的情况下,随着粗糙度的上升镜面反射区域的面积会增加,作为平衡,镜面反射区域的平均亮度则会下降。(此消彼长)
如何实现能量守恒
使用反射率方程
简单概括:
入射光的强度 * 反射比例 * 入射光的衰减 = 反射光的强度
反射光的强度 + 自发光强度 = 最终出射光的强度
入射光的衰减:半角向量和法线的点积
5.1.3 BRDF
双向反射分布函数(Bidirectional Reflectance Distribution Function)
BTDF 双向透射分布函数(Bidirectional Transmitted Distribution Function)
BSDF = BRDF + BTD F双向散步分布函数
BRDF通常写作以下两种形式:
只要我们知道入射光的方向和出射光的方向,通过BRDF函数就可以得到反射比例。
BRDF是如何计算的
一般把反射拆分成漫反射和高光反射(因为纯镜面反射很极端,一般考虑到高光反射中)
漫反射部分基本采用兰伯特光照模型:当光线与模型平面越垂直,漫反射的强度越高,光线与模型平面越平行,漫反射的强度越低。
由于dA不变,当照射的区域变大时,光照范围变大,由于能量守恒,导致单位面积的光强度减小。
除了与光照的夹角有关,还与光照的距离有关。(式中前半部分为距离相关,后半部分为角度相关)
BRDF中的高光反射
Phong模型,视角方向与反射方向夹角越小,则所得高光强度越大;
Blinn-Phong模型,半角向量方向与法线方向的夹角越小,则所得高光强度越大;
上述两个模型都是经验模型,并不是真正的PBR。
基于物理的高光反射
对 求反射率的部分进行扩展:
各部分组成含义:
DGF分别对应:
D: 法线分布函数
G: 几何遮蔽函数
F: 菲涅尔方程
5.1.4 DGF
法线分布函数(Normal Distribution Function, NDF)
这是一个描述法线对光衰减起作用的函数
函数表达式一般描述如下:
GGX与Blin-Phong的效果区别如下:
实验证明GGX也是更符合现实中的光照效果的。
几何遮蔽函数
我们使用微平面理论,也就是说每个面单独计算互不干扰,但是实际上,物体的表面的凹凸存在相互遮蔽的情况。
图中展示了两种情况,即几何遮蔽和几何阴影。
因此,几何函数从统计学上近似的求得了微平面间相互遮蔽的比率。(这种相互遮蔽会损耗光线的能量,除了被吸收,还有被自身遮蔽带来的能量损耗。)
下图为几何遮蔽函数开启后对表面颜色的影响
菲涅尔方程
被反射的光线对比光线被折射的部分所占的比率
举例:当垂直观察的时候,反射光很少,折射光很多,此时二者比率较小,所以得到的比值较小最终观察到的光线亮度就较小。
最终效果(掠射角更亮):
上图现象解释。蓝色光线部分与平面夹角较小,所以菲涅尔效应更强,红色部分夹角大,菲涅尔效应弱,反射光弱。
总结:
迪士尼原则的BRDF
- 应使用直观的参数,而不是物理类的晦涩参数;
- 参数应当尽可能的少;
- 参数在其合理范围内应该为0到1;
- 允许参数在有意义时超出正常的合理范围;
- 所有参数组合应当尽可能健壮和合理;
利用十一个不同的参数来逼近真实的金属和非金属以及不同粗糙度的材质光照结果。
本来以为很难,看了之后发现基础理论部分还是可以看懂的,后面实践一下,争取做出一个自己的PBRShader,并在此基础上改进出风格化的效果。
作业
中间为自己手搓的,最下面一行是unity自带Standard,最上面一行是直接光BRDF结果。
PBR造轮子
造轮子原文,配合食用,否则后半部分的公式有些看不懂了。
Shader "Custom/TestPBR" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" { }
_Tint ("Tint", Color) = (1, 1, 1, 1)
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.5
_LUT ("LUT", 2D) = "white" { }
}
SubShader {
Tags { "RenderType" = "Opaque" }
LOD 100
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#include "UnityStandardBRDF.cginc"
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
float4 _Tint;
float _Metallic;
float _Smoothness;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _LUT;
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal);
o.normal = normalize(o.normal);
return o;
}
float3 fresnelSchlickRoughness(float cosTheta, float3 F0, float roughness) {
return F0 + (max(float3(1.0 - roughness, 1.0 - roughness, 1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
fixed4 frag(v2f i) : SV_Target {
// 插值后再次归一化,否则高光部分是像素形状
i.normal = normalize(i.normal);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
float3 lightColor = _LightColor0.rgb;
// 半角向量
float3 halfVector = normalize(viewDir + lightDir);
// 粗糙度的一次方 二次方 四次方
float perceptualRoughness = 1 - _Smoothness;
float roughness = perceptualRoughness * perceptualRoughness;
float squareRoughness = roughness * roughness;
float nl = max(saturate(dot(i.normal, lightDir)), 0.000001);
float nv = max(saturate(dot(i.normal, viewDir)), 0.000001);
float vh = max(saturate(dot(viewDir, halfVector)), 0.000001);
float lh = max(saturate(dot(lightDir, halfVector)), 0.000001);
float nh = max(saturate(dot(i.normal, halfVector)), 0.000001);
// BRDF第一部分
// 1. 直接光漫反射:兰伯特光照模型
float3 Albedo = _Tint * tex2D(_MainTex, i.uv);
// 2. 直接光镜面反射 DGF
float lerpSquareRoughness = pow(lerp(0.002, 1, roughness), 2);
// 2.1 D:GGXTR公式
float D = lerpSquareRoughness / (pow((pow(nh, 2) * (lerpSquareRoughness - 1) + 1), 2) * UNITY_PI);
// 2.2 G: 几何遮蔽函数
// 直接光和IBL的k值计算方式不同,直接光的最小k值为1/8,避免完全不吸收光的情况发生
float kInDirectLight = pow(squareRoughness + 1, 2) / 8;
float kInIBL = pow(squareRoughness, 2) / 8;
float Gleft = nl / lerp(nl, 1, kInDirectLight);
float Gright = nv / lerp(nv, 1, kInDirectLight);
float G = Gleft * Gright;
// 2.3 F: 菲涅尔方程
// 菲涅尔方程只在金属上生效,为了使同一个材质能够同时表现金属和非金属,
// 所以将材料的金属度参数整合到平面的基础反射率F0计算中
// 在常数和表面颜色之间根据金属度进行插值
float3 F0 = lerp(unity_ColorSpaceDielectricSpec.rgb, Albedo, _Metallic);
// 实际结果可以说是完全没有菲涅尔效果,但是结果正确,所以就这样
float3 F = F0 + (1 - F0) * exp2((-5.55473 * vh - 6.98316) * vh);
// 1-F是为了能量守恒, 1-_Metallic是因为金属会更多的吸收折射光线导致漫反射消失
float kd = (1 - F) * (1 - _Metallic);
float3 diffuseColor = kd * Albedo * lightColor * nl;
// 使用乘法比除以4要高效的多
float3 SpecularResult = (D * G * F * 0.25) / (nv * nl);
// 镜面反射的系数就是F,此处不再乘一遍
// 可以发现最后除了pi,是因为unity在漫反射部分乜有除以pi为了保证正确的比例关系,所以对镜面反射部分乘以pi
float3 specColor = SpecularResult * lightColor * nl * UNITY_PI;
// 直接光的漫反射和镜面反射
float3 DirectLightResult = diffuseColor + specColor;
// BRDF第二部分 基于IBL的BRDF
// 3. IBL漫反射,在每一帧中进行几分计算不现实,Unity已经帮我们预处理好了cubemap相关光照信息可以直接使用
half3 ambient_contrib = ShadeSH9(float4(i.normal, 1));
float3 ambient = 0.03 * Albedo;
float3 iblDiffuse = max(half3(0, 0, 0), ambient.rgb + ambient_contrib);
// 4.1 IBL镜面反射
// 采样用的粗糙度和原本的粗糙度不是线性关系,所以需要进行特殊处理
float mip_roughness = perceptualRoughness * (1.7 - 0.7 * perceptualRoughness);
// 计算视角关于法线的反射方向用于后续采样
float3 reflectVec = reflect(-viewDir, i.normal);
// 获得实际采样的mip层级 UNITY_SPECCUBE_LOD_STEPS默认值为6
half mip = mip_roughness * UNITY_SPECCUBE_LOD_STEPS;
// 根据粗糙度等级进行三线性插值
half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectVec, mip);
float3 iblSpecular = DecodeHDR(rgbm, unity_SpecCube0_HDR);
// Unity官方间接光照的镜面反射实现(不采样_LUT)
float surfaceReduction = 1.0 / (roughness * roughness + 1.0);
float oneMinusReflectivity = 1 - max(max(SpecularResult.r, SpecularResult.g), SpecularResult.b);
float grazingTerm = saturate(_Smoothness + (1 - oneMinusReflectivity));
float3 iblSpecularResult = iblSpecular * surfaceReduction * FresnelLerp(F0, grazingTerm, nv);
// 间接光照的漫反射计算
float3 fLast = fresnelSchlickRoughness(max(nv, 0.0), F0, roughness);
float kdLast = (1 - fLast) * (1 - _Metallic);
float3 iblDiffuseResult = iblDiffuse * kdLast * Albedo;
// 间接光照的镜面发射计算,(采样LUT)
// 采样LUT的方式开销更大
float2 envBRDF = tex2D(_LUT, float2(lerp(0, 0.99, nv), lerp(0, 0.99, roughness))).rg;
iblSpecularResult = iblSpecular*(fLast * envBRDF.r + envBRDF.g);
float3 indirectResult = iblDiffuseResult + iblSpecularResult;
float4 result = float4(DirectLightResult + indirectResult, 1);
return result;
}
ENDCG
}
}
FallBack "Diffuse"
}