目的
这篇文章的目的:只为了备忘、和便于回顾、复习
只适合本人自己查看
环境
下面代码的运行环境
- Unity : 2019.3.11f1
- Pipeline : URP
这篇文章,在 2021/12 - 2022/2 初,断断续续的时间片抄完的笔记
参考资料具体查看:Reference
下面多数是抄出来的作业,少部分有自己的理解、调整的东西
这篇 PBR 中,很多都是经过各个 optimization 之后的分支代码
PBR 主要渲染方程
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?+ks?4(wo??n)(wi??n)DGF?)Li?(p,wi?)(wi??n)dwi?
- D - 法线分布函数:描述微表面法线 N 和 半角 H 同向性的比重,光滑度越高,也就是粗糙度越低(越光滑),那么N,H 的同向性越大(反射越清晰)
- G - 后面补上,看下面抄的作业中的描述,也时可以,但是还不够详细
- F - 同上
D 项
法线分布函数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?
-
a
a
a - 粗糙度
-
n
n
n - 法线
-
h
h
h - normalize(L + V),是方向与光源方向的半角向量,这里的光源方向指 计算点到光源的方向,不是光源照射的方向
-
n
?
h
n \cdot h
n?h - NdotH - 法线与半角向量的点乘
核心函数
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;
return a2 / d;
}
GGB(desmos)
这次使用的是 desmos 不是 GGM,也是也差不多,可以看到 D_Term 只是一个拟合曲线(只要横向在 0-1 的部分曲线),来达到近视对应 物理仪器采集的数值对应的模型,下面是可以看到 NdotH 0 - 1 的变化:下压 到 上拱
D_Term 完整 Shader
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;
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;
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);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
half3 H = normalize(L + V);
half HdotN = max(dot(H, N), 1e-5);
return D_Function(HdotN, _Roughness);
}
ENDHLSL
}
}
}
G 项
几何函数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?
-
n
n
n - 法线
-
l
l
l - 灯光方向(不是入射方向,即:入射反向,从计算点到光源的方向)
-
k
k
k - NdotL 的过去系数,这里使用拟合曲线:
float k = pow(1 + roughness, 2) * 0.5; ,下面的 GGB 有图形化该曲线样式
G项是由2个几乎一样的子项相乘得来,故我们可以先定义子项的函数
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)代入子项,相乘即可
float G_Function(float NdotL, float NdotV, float roughness)
{
float k = pow(1 + roughness, 2) * 0.5;
return G_subSection(NdotL, k) * G_subSection(NdotV, k);
}
GGB
k 曲线,变量 roughness 在 0~1 的范围,值域为:0.5~2.0
G_Term 完整 Shader
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;
inline float G_subSection(float dot, float k)
{
return dot / lerp(dot, 1, k);
}
float G_Function(float NdotL, float NdotV, float roughness)
{
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);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
half NdotL = max(dot(N, L), 1e-5);
half NdotV = max(dot(N, V), 1e-5);
return G_Function(NdotL, NdotV, _Roughness);
}
ENDHLSL
}
}
}
F 项
菲涅尔函数F(Fresnel equation),即核心方程的F项
菲涅尔反射(Fresnel Reflectance)。光线以不同角度入射会有不同的反射率。相同的入射角度,不同的物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属的F0范围是 0.02 - 0.04 ,大多数金属的F0范围是 0.7 - 1.0 。
如下图(UE4 PBR文档的图片):
下面是 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
故最终,我们的菲涅尔函数如下。
inline half3 F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
half3 F_Function(float HdotL, half3 F)
{
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F);
}
GGB
F_Term 完整 Shader
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;
inline half3 F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
half3 F_Function(float HdotL, half3 F)
{
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
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
{
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
half3 H = normalize(L + V);
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 带入公式
直接光的高光部分:我们把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);
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);
PBR_Test_DGF.hlsl
#ifndef __PBR_TEST_DGF_H__
#define __PBR_TEST_DGF_H__
half4 _Color;
half _Metallic;
half _Roughness;
float D_Function(float NdotH, float roughness)
{
float a2 = roughness * roughness;
float NdotH2 = NdotH * NdotH;
float d = (NdotH2 * (a2 - 1) + 1);
d = d * d;
return a2 / d;
}
inline float G_subSection(float dot, float k)
{
return dot / lerp(dot, 1, k);
}
float G_Function(float NdotL, float NdotV, float roughness)
{
float k = pow(1 + roughness, 2) * 0.5;
return G_subSection(NdotL, k) * G_subSection(NdotV, k);
}
half3 F_Function(float HdotL, half3 F0)
{
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F0);
}
inline half3 F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
#endif
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);
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
half3 H = normalize(L + V);
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);
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);
}
ENDHLSL
}
}
}
应用到具体 PBR 素材上
Vela 提供的模型 - 已备份到百度网盘,但不公开(700MB+)
左边是:SP,右边是 Unity URP 的 Custom PBR Shader(法线是 F0 的模拟效果不太理想,后续可以将 F0 修改为一些一些色阶图来返回 F0 的纯灰度 或是 纯金属的反射颜色才比较好的效果)
天空盒 和 灯光方向、颜色、等参数都不太一样,所以效果差异就更大了
完整 Shader - 只有 PBR + SH(Reflection Probe) + Emissive
阴影还没添加,后续再添加了
PBR__Emissive_Lib.hlsl
#ifndef __PBR_Emissive_LIB_H__
#define __PBR_Emissive_LIB_H__
#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;
float4 normalWS : TEXCOORD1;
float4 tangentWS : TEXCOORD2;
float4 bitangentWS : TEXCOORD3;
};
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
half Direct_D_Function(half NdotH, half roughness)
{
half a2 = Pow4(roughness);
half d = (NdotH * NdotH * (a2 - 1.0) + 1.0);
d = d * d;
return saturate(a2 / d);
}
inline real Direct_G_subSection(half dot, half k)
{
return dot / lerp(dot, 1, k);
}
half Direct_G_Function(half NdotL, half NdotV, half roughness)
{
half k = pow(1 + roughness, 2) * 0.5;
return Direct_G_subSection(NdotL, k) * Direct_G_subSection(NdotV, k);
}
inline half Direct_G_Function_Kalos(half LdotH, half roughness)
{
half k = pow(1 + roughness, 2) * 0.5;
return Direct_G_subSection(LdotH, k);
}
half3 Direct_F_Function(half HdotL, half3 F0)
{
half Fre = exp2((-5.55473 * HdotL - 6.98316) * HdotL);
return lerp(Fre, 1, F0);
}
inline half3 Direct_F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
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);
half mipmapLevel = roughness * 6;
half4 specularColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirWS, mipmapLevel);
#if !defined(UNITY_USE_NATIVE_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)
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);
return lerp(F0, GrazingTSection, fre) * SurReduction;
}
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;
}
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));
Light mainLight = GetMainLight();
half3 L = normalize(mainLight.direction);
float3 positionWS = float3(i.normalWS.w, i.tangentWS.w, i.bitangentWS.w);
half3 V = SafeNormalize(_WorldSpaceCameraPos - positionWS);
half3 H = normalize(L + V);
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);
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;
half Direct_D = Direct_D_Function(HdotN, roughness);
#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
half3 F0 = Direct_F0_Function(albedoTex, metallic);
half3 Direct_F = Direct_F_Function(HdotL, F0);
#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
half3 DirectSpeColor = BRDFSpecSection * mainLight.color * (NdotL * PI * AO);
half3 KS = Direct_F;
half3 KD = (1 - KS) * (1 - metallic);
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;
half3 DirectColor = DirectDiffColor + DirectSpeColor;
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;
half3 IndirectSpeCubeColor = IndirectSpeCube(N, V, roughness, AO);
half3 IndirectSpeCubeFactor = IndirectSpeFactor(roughness, smoothness, BRDFSpecSection, F0, NdotV);
half3 IndirectSpeColor = IndirectSpeCubeColor * IndirectSpeCubeFactor;
half3 IndirectColor = IndirectDiffColor + IndirectSpeColor;
half3 finalCol = DirectColor + IndirectColor;
return half4(finalCol, 1);
}
#endif
URP_PBR_Emissive.shader
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 的某个分支的模型基础上再次简化计算的模型)
References
最后附上近期自己用圆珠笔话的一张画,耗时大概 40 分钟
自从学习 shader 得基础光照着色后,对画画时得细节了解也会增加
(希望自己不要潜得太久,就算不太顺利,也要往前看~,给自己加油!!!)
|