这篇文章的目的:只为了备忘、和便于回顾、复习
只适合本人自己查看
下面代码的运行环境
这篇文章,在 2021/12 - 2022/2 初,断断续续的时间片抄完的笔记
参考资料具体查看:Reference
下面多数是抄出来的作业,少部分有自己的理解、调整的东西
这篇 PBR 中,很多都是经过各个 optimization 之后的分支代码
L o ( p , w o ) = ∫ Ω ( k d c π + k s D G F 4 ( w o ⋅ n ) ( w i ⋅ n ) ) L i ( p , w i ) ( w i ⋅ n ) d w i L_o(p,w_o)=\int_{\Omega}(k_d \frac{c}{\pi} + k_s \frac {D G F}{4(w_o \cdot n)(w_i \cdot n)}) L_i(p,w_i)(w_i \cdot n) dw_i Lo(p,wo)=∫Ω(kdπc+ks4(wo⋅n)(wi⋅n)DGF)Li(p,wi)(wi⋅n)dwi
法线分布函数GGX(Normal Distribution Function),即核心方程的D项
D - 法线分布函数:描述微表面法线 N 和 半角 H 同向性的比重,光滑度越高,也就是粗糙度越低(越光滑),那么N,H 的同向性越大(反射越清晰)
D = a 2 π ( ( n ⋅ h ) 2 ( a 2 − 1 ) + 1 ) 2 D = \frac {a^2} {\pi ((n \cdot h)^2 (a^2-1)+1)^2} D=π((n⋅h)2(a2−1)+1)2a2
核心函数
// #define PI 3.1415926
// D 项 法线微表面分布函数
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;// * PI;
return a2 / d;
}
这次使用的是 desmos 不是 GGM,也是也差不多,可以看到 D_Term 只是一个拟合曲线(只要横向在 0-1 的部分曲线),来达到近视对应 物理仪器采集的数值对应的模型,下面是可以看到 NdotH
0 - 1 的变化:下压 到 上拱
// jave.lin 2022/01/18
// PBR GGX D Term
Shader "Test/URP_PBR_D_Term"
{
Properties
{
_Roughness ("Roughness", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 positionOS : POSITION;
half3 normalOS : NORMAL;
};
struct v2f
{
float4 positionCS : SV_POSITION;
half3 normalWS : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};
half _Roughness;
// #define PI 3.1415926
// D 项 法线微表面分布函数
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;// * PI;
return a2 / d;
}
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
return o;
}
half4 frag (v2f i) : SV_Target
{
half3 N = normalize(i.normalWS);
// return half4(N, 1);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
// return half4(L, 1);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
// return half4(V, 1);
half3 H = normalize(L + V);
// return half4(H, 1);
half HdotN = max(dot(H, N), 1e-5);
return D_Function(HdotN, _Roughness);
}
ENDHLSL
}
}
}
几何函数G,(Geometry function),即核心方程的G项,它是描述入射射线(即光照方向)和出射射线(视线方向)被自己的微观几何形状遮挡的比重。
G = n ⋅ l l e r p ( n ⋅ l , 1 , k ) × n ⋅ v l e r p ( n ⋅ v , 1 , k ) G = \frac {n \cdot l} {lerp(n \cdot l, 1, k)} \times \frac {n \cdot v} {lerp(n \cdot v, 1, k)} G=lerp(n⋅l,1,k)n⋅l×lerp(n⋅v,1,k)n⋅v
float k = pow(1 + roughness, 2) * 0.5;
,下面的 GGB 有图形化该曲线样式G项是由2个几乎一样的子项相乘得来,故我们可以先定义子项的函数
// G项子项
inline float G_subSection(float dot, float k)
{
return dot / lerp(dot, 1, k);
}
再去定义真正的G项,把 d o t ( N , L ) dot(N, L) dot(N,L)和 d o t ( N , V ) dot(N, V) dot(N,V)代入子项,相乘即可
// G项
float G_Function(float NdotL, float NdotV, float roughness)
{
// method1-k:
// // float k = pow(1 + roughness, 2) / 8.0;
// // method2-k:
// const float d = 1.0 / 8.0;
// float k = pow(1 + roughness, 2) * d;
// method3-k:
float k = pow(1 + roughness, 2) * 0.5;
return G_subSection(NdotL, k) * G_subSection(NdotV, k);
}
k 曲线,变量 roughness 在 0~1
的范围,值域为:0.5~2.0
// jave.lin 2022/01/19
// PBR G Term
Shader "Test/URP_PBR_G_Term"
{
Properties
{
_Roughness ("Roughness", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 positionOS : POSITION;
half3 normalOS : NORMAL;
};
struct v2f
{
float4 positionCS : SV_POSITION;
half3 normalWS : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};
half _Roughness;
// G项子项
inline float G_subSection(float dot, float k)
{
return dot / lerp(dot, 1, k);
}
// G项
float G_Function(float NdotL, float NdotV, float roughness)
{
// method1-k:
// // float k = pow(1 + roughness, 2) / 8.0;
// // method2-k:
// const float d = 1.0 / 8.0;
// float k = pow(1 + roughness, 2) * d;
// method3-k:
float k = pow(1 + roughness, 2) * 0.5;
return G_subSection(NdotL, k) * G_subSection(NdotV, k);
}
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
return o;
}
half4 frag (v2f i) : SV_Target
{
half3 N = normalize(i.normalWS);
// return half4(N, 1);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
// return half4(L, 1);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
// return half4(V, 1);
// half3 H = normalize(L + V);
// return half4(H, 1);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
return G_Function(NdotL, NdotV, _Roughness);
}
ENDHLSL
}
}
}
菲涅尔函数F(Fresnel equation),即核心方程的F项
菲涅尔反射(Fresnel Reflectance)。光线以不同角度入射会有不同的反射率。相同的入射角度,不同的物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属的F0范围是 0.02 - 0.04
,大多数金属的F0范围是 0.7 - 1.0
。
下面是 Schlick 的模型的公式对应的代码:
F = l e r p ( ( 1 − ( n ⋅ v ) ) 5 , 1 , F 0 ) F = lerp((1-(n \cdot v))^5, 1, F_0) F=lerp((1−(n⋅v))5,1,F0)
但是由于我们需要的法线方向并不是模型本身的宏观法线n,而是经过D项筛选通过的微观法线H,故需把N改成H,再将 Schlick 调整为如下数学公式(为何我觉得上面的公司更为直观,-_-,大概是因为更代码化)
F S c h l i c k ( h , v , F 0 ) = F 0 + ( 1 − F 0 ) ( 1 − ( h ⋅ v ) ) 5 F_{Schlick} (h, v, F_0) = F_0 + (1 - F_0)(1 - (h \cdot v))^5 FSchlick(h,v,F0)=F0+(1−F0)(1−(h⋅v))5
后来unity对它进行了优化(Optimizing GGX Shaders with dot(L,H)),视线方向V换成了L,如下所示,这是我们所使用的函数
F ( l , h ) = F 0 + ( 1 − F 0 ) ( 1 − l ⋅ h ) 5 F(l, h) = F_0 + (1 - F_0) (1 - l \cdot h)^5 F(l,h)=F0+(1−F0)(1−l⋅h)5
其中的5次方计算量比较大,把它变成自然对数函数进行计算可以节省计算量,后续文章里所有的5次方计算都可以换算成对数计算
x y = e y l n x x^y = e^{y \space ln \space x} xy=ey ln x
故最终,我们的菲涅尔函数如下。
// method1:
// F项
inline half3 F0_Function(half3 albedo, half metallic)
{
// jave.lin :
// 非金属一般都是纯灰度:0.02 ~ 0.04
// 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
// 后续其他版本的 pbr 可以调整为更加效果的
return lerp(0.04, albedo, metallic);
}
half3 F_Function(float HdotL, half3 F)
{
// jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F);
}
// // method2:
// // F项
// half3 F_Function(float HdotL, half3 F)
// {
// half Fre = pow(1 - HdotL, 5);
// return lerp(Fre, 1, F);
// }
// jave.lin 2022/01/19
// PBR F Term
Shader "Test/URP_PBR_F_Term"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_Metallic ("_Metallic", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 positionOS : POSITION;
half3 normalOS : NORMAL;
};
struct v2f
{
float4 positionCS : SV_POSITION;
half3 normalWS : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};
half4 _Color;
half _Metallic;
// method1:
// F项
inline half3 F0_Function(half3 albedo, half metallic)
{
// jave.lin :
// 非金属一般都是纯灰度:0.02 ~ 0.04
// 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
// 后续其他版本的 pbr 可以调整为更加效果的
return lerp(0.04, albedo, metallic);
}
half3 F_Function(float HdotL, half3 F)
{
// jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F);
}
// // method2:
// // F项
// half3 F_Function(float HdotL, half3 F)
// {
// half Fre = pow(1 - HdotL, 5);
// return lerp(Fre, 1, F);
// }
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
return o;
}
half4 frag (v2f i) : SV_Target
{
// half3 N = normalize(i.normalWS);
// return half4(N, 1);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
// return half4(L, 1);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
// return half4(V, 1);
half3 H = normalize(L + V);
// return half4(H, 1);
// half3 F = lerp(0.04, _Color.rgb, _Metallic);
half HdotL = max(dot(H, L), 1e-5);
half3 F = F0_Function(_Color.rgb, _Metallic);
return half4(F_Function(HdotL, F), 1);
}
ENDHLSL
}
}
}
直接光的高光部分:我们把D,G,F代入公式,先计算出高光部分。注意这里经过半球积分后会乘PI,不要丢了;注意这里并未再次乘KS,因为KS=F,已经计算过了一次不需要重复计算。
halfHdotN = max(dot(H, N), 1e-5);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
half HdotL = max(dot(H, L), 1e-5);
// ======= Direct START ======
half3 F = F0_Function(_Color.rgb, _Metallic);
half Direct_D = D_Function(HdotN, _Roughness);
half Direct_G = G_Function(NdotL, NdotV, _Roughness);
half3 Direct_F = F_Function(HdotL, F);
half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
half3 DirectSpeColor = BRDFSpecSection * mainLight.color * NdotL * PI;
return half4(DirectSpeColor, 1);
// ======= Direct END ======
#ifndef __PBR_TEST_DGF_H__
#define __PBR_TEST_DGF_H__
// jave.lin : 2022/01/19
half4 _Color;
half _Metallic;
half _Roughness; // 后续改到 ubo / cbuffer 中
// D 项 法线微表面分布函数
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;// * PI;
return a2 / d;
}
// G项子项
inline float G_subSection(float dot, float k)
{
return dot / lerp(dot, 1, k);
}
// G项
float G_Function(float NdotL, float NdotV, float roughness)
{
// method1-k:
// // float k = pow(1 + roughness, 2) / 8.0;
// // method2-k:
// const float d = 1.0 / 8.0;
// float k = pow(1 + roughness, 2) * d;
// method3-k:
float k = pow(1 + roughness, 2) * 0.5;
return G_subSection(NdotL, k) * G_subSection(NdotV, k);
}
// method1:
// F项
half3 F_Function(float HdotL, half3 F0)
{
// jave.lin : (1-x)^5 转为性能更高的来模拟:2^((-5.55473 * x- 6.98316) * x)
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F0);
}
// // method2:
// // F项
// half F_Function(float HdotL, half F0)
// {
// half Fre = pow(1 - HdotL, 5);
// return lerp(Fre, 1, F0);
// }
inline half3 F0_Function(half3 albedo, half metallic)
{
// jave.lin :
// 非金属一般都是纯灰度:0.02 ~ 0.04
// 金属一般都是纯颜色:0.7 ~ 1.0 之间的,这里我们简单的使用一个 lerp 来模拟
// 后续其他版本的 pbr 可以调整为更加效果的
return lerp(0.04, albedo, metallic);
}
#endif
// jave.lin 2022/01/19
// PBR DGF Term
Shader "Test/URP_PBR_DGF_Term"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_Metallic ("Metallic", Range(0, 1)) = 0.5
_Roughness ("Roughness", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Includes/PBR_Test_DGF.hlsl"
struct appdata
{
float4 positionOS : POSITION;
half3 normalOS : NORMAL;
};
struct v2f
{
float4 positionCS : SV_POSITION;
half3 normalWS : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.positionWS = TransformObjectToWorld(v.positionOS.xyz);
o.normalWS = TransformObjectToWorldNormal(v.normalOS.xyz);
return o;
}
half4 frag (v2f i) : SV_Target
{
half3 N = normalize(i.normalWS);
// return half4(N, 1);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
// return half4(L, 1);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
// return half4(V, 1);
half3 H = normalize(L + V);
// return half4(H, 1);
half HdotN = max(dot(H, N), 1e-5);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
half HdotL = max(dot(H, L), 1e-5);
// ======= Direct START ======
half3 F = F0_Function(_Color.rgb, _Metallic);
half Direct_D = D_Function(HdotN, _Roughness);
half Direct_G = G_Function(NdotL, NdotV, _Roughness);
half3 Direct_F = F_Function(HdotL, F);
half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
half3 DirectSpeColor = BRDFSpecSection * mainLight.color * NdotL * PI;
return half4(DirectSpeColor, 1);
// ======= Direct END ======
}
ENDHLSL
}
}
}
Vela 提供的模型 - 已备份到百度网盘,但不公开(700MB+)
在 substance 官方也可以直接下载到:Vela Template
左边是:SP,右边是 Unity URP 的 Custom PBR Shader(法线是 F0 的模拟效果不太理想,后续可以将 F0 修改为一些一些色阶图来返回 F0 的纯灰度 或是 纯金属的反射颜色才比较好的效果)
天空盒 和 灯光方向、颜色、等参数都不太一样,所以效果差异就更大了
阴影还没添加,后续再添加了
#ifndef __PBR_Emissive_LIB_H__
#define __PBR_Emissive_LIB_H__
// #ifdef HAS_HALF
// #undef HAS_HALF
// #define HAS_HALF 1
// #endif
// jave.lin : 2022/02/06
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct a2v
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
half3 normalOS : NORMAL;
half4 tangentOS : TANGENT;
float2 lightmapUV : TEXCOORD1;
};
struct v2f
{
float4 positionCS : SV_POSITION;
float4 uv : TEXCOORD0; // xy: base map uv, zw: lightmap uv
float4 normalWS : TEXCOORD1; // w: posWS.x
float4 tangentWS : TEXCOORD2; // w: posWS.y
float4 bitangentWS : TEXCOORD3; // w: posWS.z
};
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_MaskMap); SAMPLER(sampler_MaskMap);
TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);
TEXTURE2D(_EmissiveMap); SAMPLER(sampler_EmissiveMap);
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Metallic;
half _AO;
half _Roughness;
half _NormalScale;
half _Emissive;
half _EmissiveTwinkleFrequence;
CBUFFER_END
// ========== Direct START =============
// 直接光照部分
// ========== 直接光照 D项 START =============
// 直接光照 D项 法线微表面分布函数
half Direct_D_Function(half NdotH, half roughness)
{
half a2 = Pow4(roughness);
half d = (NdotH * NdotH * (a2 - 1.0) + 1.0);
d = d * d;// *PI;
return saturate(a2 / d);
}
// ========== 直接光照 D项 END =============
// ========== 直接光照 G项 START =============
// 直接光照 G项子项
inline real Direct_G_subSection(half dot, half k)
{
return dot / lerp(dot, 1, k);
}
// 直接光照 G项
half Direct_G_Function(half NdotL, half NdotV, half roughness)
{
// method1-k:
// // half k = pow(1 + roughness, 2) / 8.0;
// // method2-k:
// const half d = 1.0 / 8.0;
// half k = pow(1 + roughness, 2) * d;
// method3-k:
half k = pow(1 + roughness, 2) * 0.5;
return Direct_G_subSection(NdotL, k) * Direct_G_subSection(NdotV, k);
}
// 模拟 G项:Kelemen-Szirmay-Kalos Geometry Factor
// http://renderwonk.com/publications/s2010-shading-course/hoffman/s2010_physically_based_shading_hoffman_b.pdf
// 搜索:Kelemen-Szirmay-Kalos Geometry Factor
inline half Direct_G_Function_Kalos(half LdotH, half roughness)
{
half k = pow(1 + roughness, 2) * 0.5;
return Direct_G_subSection(LdotH, k);
}
// ========== 直接光照 G项 END =============
// ========== 直接光照 F项 START =============
// method1:
// 直接光照 F项
half3 Direct_F_Function(half HdotL, half3 F0)
{
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F0);
}
// // method2:
// // 直接光照 F项
// half3 Direct_F_Function(half HdotL, half3 F0)
// {
// half Fre = pow(1 - HdotL, 5);
// return lerp(Fre, 1, F0);
// }
// ========== 直接光照 F项 END =============
// ========== 直接光照 F0 START =============
inline half3 Direct_F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
// ========== 直接光照 F0 END =============
// ========== Direct END =============
// ========== Indirect START =============
real3 SH_IndirectionDiff(real3 normalWS)
{
real4 SHCoefficients[7];
SHCoefficients[0] = unity_SHAr;
SHCoefficients[1] = unity_SHAg;
SHCoefficients[2] = unity_SHAb;
SHCoefficients[3] = unity_SHBr;
SHCoefficients[4] = unity_SHBg;
SHCoefficients[5] = unity_SHBb;
SHCoefficients[6] = unity_SHC;
real3 color = SampleSH9(SHCoefficients, normalWS);
return max(0, color);
}
half3 Indirect_F_Function(half NdotV, half3 F0, half roughness)
{
half fre = exp2((-5.55473 * NdotV - 6.98316) * NdotV);
return F0 + fre * saturate(1 - roughness - F0);
}
half3 IndirectSpeCube(half3 normalWS, half3 viewWS, float roughness, half AO)
{
half3 reflectDirWS = reflect(-viewWS, normalWS);
roughness = roughness * (1.7 - 0.7 * roughness); // unity 内部不是线性 调整下 拟合曲线求近似,可以再 GGB 可视化曲线
half mipmapLevel = roughness * 6; // 把粗糙度 remap 到 0~6 的 7个阶段,然后进行 texture lod 采样
half4 specularColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirWS, mipmapLevel); // 根据不同的 mipmap level 等级进行采样
#if !defined(UNITY_USE_NATIVE_HDR)
// 用 DecodeHDREnvironment 将解码 HDR 颜色值。
// 可以看到采样出的 RGBM 是一个 4 通道的值
// 最后的一个 M 存的是一个参数
// 解码时将前三个通道表示的颜色乘上 x*(M^y)
// x y 都是有环境贴图定义的系数
// 存储在 unity_SpecCube0_HDR 这个结构中
return DecodeHDREnvironment(specularColor, unity_SpecCube0_HDR) * AO;
#else
return specularColor.rgb * AO;
#endif
}
half3 IndirectSpeFactor(half roughness, half smoothness, half3 BRDFspe, half3 F0, half NdotV)
{
#ifdef UNITY_COLORSPACE_GAMMA
half SurReduction = 1 - 0.28 * roughness * roughness;
#else
half SurReduction = 1 / (roughness * roughness + 1);
#endif
#if defined(SHADER_API_GLES) // Lighting.hlsl 261 行
half Reflectivity = BRDFspe.x;
#else
half Reflectivity = max(max(BRDFspe.x, BRDFspe.y), BRDFspe.z);
#endif
half GrazingTSection = saturate(Reflectivity + smoothness);
half fre = Pow4(1 - NdotV); // Lighting.hlsl 第 501 行
// half fre = exp2((-5.55473 * NdotV - 6.98316) * NdotV); // Lighting.hlsl 第 501 行,他是 4 次方,我们是 5 次方
return lerp(F0, GrazingTSection, fre) * SurReduction;
}
// ========== Indirect END =============
// ========== vert START ===========
v2f vert(a2v i)
{
v2f o = (v2f)0;
float3 positionWS = TransformObjectToWorld(i.positionOS.xyz);
o.positionCS = TransformWorldToHClip(positionWS);
o.uv.xy = TRANSFORM_TEX(i.uv, _BaseMap);
o.uv.zw = i.lightmapUV;
o.normalWS.xyz = normalize(TransformObjectToWorldNormal(i.normalOS.xyz));
o.tangentWS.xyz = normalize(TransformObjectToWorldDir(i.tangentOS.xyz));
o.bitangentWS.xyz = cross(o.normalWS.xyz, o.tangentWS.xyz) * i.tangentOS.w * unity_WorldTransformParams.w;
o.normalWS.w = positionWS.x;
o.tangentWS.w = positionWS.y;
o.bitangentWS.w = positionWS.z;
return o;
}
// ========== vert END ===========
// ========== frag START ===========
half4 frag(v2f i) : SV_Target
{
half4 normalTex = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv.xy);
half3 normalTS = UnpackNormalScale(normalTex, _NormalScale);
normalTS.z = sqrt(1 - saturate(dot(normalTS.xy, normalTS.xy)));
float3x3 T2W = { i.tangentWS.xyz, i.bitangentWS.xyz, i.normalWS.xyz };
T2W = transpose(T2W);
half3 N = NormalizeNormalPerPixel(mul(T2W, normalTS));
// return half4(N, 1);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
// return half4(L, 1);
float3 positionWS = float3(i.normalWS.w, i.tangentWS.w, i.bitangentWS.w);
half3 V = SafeNormalize(_WorldSpaceCameraPos - positionWS);
// return half4(V, 1);
half3 H = normalize(L + V);
// return half4(H, 1);
half HdotN = max(dot(H, N), 1e-5);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
half HdotL = max(dot(H, L), 1e-5);
// ======= Direct START ======
half3 albedoTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv.xy).rgb * _BaseColor.rgb;
half3 emissiveTex = SAMPLE_TEXTURE2D(_EmissiveMap, sampler_EmissiveMap, i.uv.xy).rgb;
half3 maskTex = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, i.uv.xy).rgb;
half metallic = maskTex.r * _Metallic;
half AO = lerp(1, maskTex.g, _AO);
half smoothness = maskTex.b;
half roughness = (1 - smoothness) * _Roughness;
// direct specular
half Direct_D = Direct_D_Function(HdotN, roughness);
//return Direct_D;
// jave.lin : 使用 Kelemen-Szirmay-Kalos Geometry Factor 优化(可以减少一个 sub G func)
// 参考:http://renderwonk.com/publications/s2010-shading-course/hoffman/s2010_physically_based_shading_hoffman_b.pdf - 搜索:Kelemen-Szirmay-Kalos Geometry Factor
#if defined(_KALOS_G_FACTOR_ON)
half Direct_G = Direct_G_Function_Kalos(HdotL, roughness);
#else
half Direct_G = Direct_G_Function(NdotL, NdotV, roughness);
#endif
//return Direct_G;
half3 F0 = Direct_F0_Function(albedoTex, metallic);
//return half4(F0, 1);
half3 Direct_F = Direct_F_Function(HdotL, F0);
//return half4(Direct_F, 1);
#if defined(_KALOS_G_FACTOR_ON)
half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * HdotL);
#else
half3 BRDFSpecSection = (Direct_D * Direct_G) * Direct_F / (4 * NdotL * NdotV);
#endif
//return half4(BRDFSpecSection, 1);
half3 DirectSpeColor = BRDFSpecSection * mainLight.color * (NdotL * PI * AO);
//return half4(DirectSpeColor, 1);
// direct diffuse
half3 KS = Direct_F;
half3 KD = (1 - KS) * (1 - metallic);
// return half4(KD, 1);
half3 emissiveColor = emissiveTex * pow(2, _Emissive);
#if defined(_EMISSIVE_TWINKLE_ON)
emissiveColor *= sin(_Time.y * _EmissiveTwinkleFrequence * 10) * 0.5 + 0.5;
#endif
half3 DirectDiffColor = KD * albedoTex * mainLight.color * NdotL + emissiveColor;
// return half4(DirectDiffColor, 1);
// direct lights
half3 DirectColor = DirectDiffColor + DirectSpeColor;
// return half4(DirectColor, 1);
// ======= Direct END ======
// ======= Indirect START ======
// indirect diffuse
half3 shColor = SH_IndirectionDiff(N) * AO;
half3 Indirect_KS = Indirect_F_Function(NdotV, F0, roughness);
half3 Indirect_KD = (1 - Indirect_KS) * (1 - metallic);
half3 IndirectDiffColor = shColor * Indirect_KD * albedoTex;
// return half4(IndirectDiffColor, 1); // jave.lin : 添加一个 反射探针 即可看到效果:reflection probe
// indirect specular
// 反射探针的间接光
half3 IndirectSpeCubeColor = IndirectSpeCube(N, V, roughness, AO);
// return half4(IndirectSpeCubeColor, 1);
half3 IndirectSpeCubeFactor = IndirectSpeFactor(roughness, smoothness, BRDFSpecSection, F0, NdotV);
//return half4(IndirectSpeCubeFactor, 1);
half3 IndirectSpeColor = IndirectSpeCubeColor * IndirectSpeCubeFactor;
// return half4(IndirectSpeColor, 1);
half3 IndirectColor = IndirectDiffColor + IndirectSpeColor;
// return half4(IndirectColor, 1);
// ======= Indirect END ======
half3 finalCol = DirectColor + IndirectColor;
return half4(finalCol, 1);
}
// ========== frag END ===========
#endif
// jave.lin 2022/02/06
// URP PBR Emissive
Shader "Test/URP_PBR_Emissive"
{
Properties
{
_BaseColor ("Color", Color) = (1, 1, 1, 1)
_BaseMap ("Albedo", 2D) = "white" {}
[NoScaleOffset] _MaskMap ("Mask: Metalic(R), AO(G), Smoothness(B)", 2D) = "black" {}
[NoScaleOffset] [Normal] _NormalMap ("Normal (RGB)", 2D) = "bump" {}
[NoScaleOffset] _EmissiveMap ("Emissive (RGB)", 2D) = "black" {}
_Metallic ("Metallic", Range(0, 1)) = 1
_AO ("AO", Range(0, 1)) = 1
_Roughness ("Roughness", Range(0, 1)) = 1
_NormalScale("NormalScale", Range(0, 1)) = 1
_Emissive ("Emissive", Range(0, 100)) = 1
[Toggle(_EMISSIVE_TWINKLE_ON)] _Emissve_Twinkle("Emissive Twinkle", Int) = 1
_EmissiveTwinkleFrequence ("Emissive Twinkle Frequence", Range(0, 1)) = 1
[Toggle(_KALOS_G_FACTOR_ON)] _Kalos_G_Factor ("Optimize with Kalos G Factor", Int) = 1
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalRenderPipeline"
}
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _ _EMISSIVE_TWINKLE_ON
#pragma shader_feature _ _KALOS_G_FACTOR_ON
#include "Includes/PBR__Emissive_Lib.hlsl"
ENDHLSL
}
}
}
jave.lin : 另外说明一下
PBR 也是经验模型,是基于物理观察后的数据做的数据模型来模拟的
所以比一般非物理观测模型的更加逼真一些
因此才叫:PBR(基于物理(观察后)渲染(数学模型))
上面我们提到的公式,都是经过再优化(在原来的 PBR 的某个分支的模型基础上再次简化计算的模型)
最后附上近期自己用圆珠笔话的一张画,耗时大概 40 分钟
自从学习 shader 得基础光照着色后,对画画时得细节了解也会增加
(希望自己不要潜得太久,就算不太顺利,也要往前看~,给自己加油!!!)