环境
下面代码的运行环境
- Unity : 2022.3.3f1
- Pipeline : URP
---------------------------------------------------------------------------------------------------------------------------------
常规做法:兰伯特(半兰伯特)+ 反射(phong和Blinn-Phong)
halfLambert = dot(L,N)*0.5+0.5;
phong=dot( r , v );其中:r=reflect( -L , v );
Blinn-Phong = dot(N,H);其中H=(L+V)*0.5 ,近似省性能,H=normalize(L + V);
PBR的公式:
从美术角度去理解,大致分为两个,直接光照模型(直接漫反射和高光) + 间接光照模型(间接漫反射和高光) + 环境阴影;
PBR遵循三大原则:①能量守恒;②基于物理的BRDF;③微表面模型 ;
实际上做效果的时候,可以根据项目情况适当修改,以视觉效果为准“只要看起来是对的那他就是对的”;
BRDF = K*(F * G * D) / (4 * dot(N, V) * dot(N, L))
- F:菲涅尔项(Fresnel term),描述了材质在不同入射角下的反射比例。可以根据菲涅尔方程计算得到。
- G:几何项(Geometry term),考虑了观察者、光源和材质微表面之间的遮挡关系。可以使用Schlick's approximation或其他几何函数进行计算。
- D:法线分布项(Normal distribution term),描述了材质微表面的法线分布情况。常用的法线分布函数有Beckmann distribution、GGX distribution等。
- N:表面法线向量。
- V:视线方向向量。
- L:光照方向向量。
写代码的时候列出所需参数,代入公式,Debug输出值观察所需颜色结果;
我这里加入了阴影自定义颜色 和细节Mask(Diffuse,normal,Spec),参考资料见References,感谢前辈们的分享经验;
代码如下(注释):
Shader "URP/PBR_2.0"
{
Properties
{
_Color("基础颜色", Color) = (1,1,1,1)
[MainTexture]_MainTex ("主贴图", 2D) = "white" {}
_Spc("基础 Metalness(R) Smoothness(A)", 2D) = "black" {}
_Metallic("金属度",Range(0,1))=1
_Roughness ("粗糙度", Range(0, 1)) = 1
[Normal]_NormalTex("法线贴图", 2D) = "bump" {}
_NormalScale("法线强度", Float) = 1.0
_AO("基础 AO", 2D)= "white" {}
_layer1Tex ("Layer1 Albedo (RGB) Smoothness (A)", 2D) = "white" {}
_layer1Metal ("Layer1 Metalness", Range(0,1)) = 0
_layer1Norm("Layer 1 Normal", 2D) = "bump" {}
_layer1Breakup ("Layer1 Breakup (R)", 2D) = "white" {}
_layer1BreakupAmnt ("Layer1 Breakup Amount", Range(0,1)) = 0.5
_layer1Tiling("Layer1 Tiling", float) = 10
_Power ("Layer1 Blend Amount", float ) = 1
_Shift("Layer1 Blend Height", float) = 1
[Normal]_DetailNormal ("Detail Normal", 2D) = "bump" {}
_DetailInt ("DetailNormal Intensity", Range(0,1)) = 0.4
_DetailTiling("DetailNormal Tiling", float) = 2
[Toggle(_KALOS_G_FACTOR_ON)] _Kalos_G_Factor ("Optimize with Kalos G Factor", Int) = 1
_ShadowMainColor("ShadowMainColor", color) = (0,0,0,1)
[Toggle(_AdditionalLights)] _AddLights ("AddLights", Float) = 1
}
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline"}
LOD 100
Pass
{
Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
// URP 软阴影
#pragma multi_compile_fragment _ _SHADOWS_SOFT
// URP 主光阴影、联机阴影、屏幕空间阴影//
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
//奇技淫巧全在里面
#include "PBRMath.hlsl"
//光照贴图
#pragma multi_compile _ LIGHTMAP_ON
CBUFFER_START(UnityPerMaterial)
float4 _Color,_ShadowMainColor;
float _NormalScale,_Power, _DetailInt, _DetailTiling, _layer1Tiling, _layer1Metal, _layer1BreakupAmnt, _Shift,_Metallic,_Roughness;
float4 _MainTex_ST,_NormalTex_ST,_Spc_ST,_AO_ST,_DetailBump_ST,_layer1Tex_ST,_layer1Norm_ST,_layer1Breakup_ST;
CBUFFER_END
TEXTURE2D(_MainTex); TEXTURE2D(_Spc); TEXTURE2D(_AO); TEXTURE2D(_DetailNormal); TEXTURE2D(_NormalTex);
SAMPLER(sampler_MainTex); SAMPLER(sampler_Spc); SAMPLER(sampler_AO); SAMPLER(sampler_DetailNormal); SAMPLER(sampler_NormalTex);
TEXTURE2D(_layer1Tex); TEXTURE2D(_layer1Norm); TEXTURE2D(_layer1Breakup);
SAMPLER(sampler_layer1Tex); SAMPLER(sampler_layer1Norm); SAMPLER(sampler_layer1Breakup);
struct VertexInput
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float4 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uvLM : TEXCOORD1;
};
struct VertexOutput
{
float4 position : SV_POSITION;
float4 uv : TEXCOORD0; //xy是_MainTex的uv,zw是_NormlTex的UV
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 tangentWS : TEXCOORD3;
float3 bitangentWS : TEXCOORD4;
float2 uvLM : TEXCOORD5;
float3 vertexSH : TEXCOORD6;
half fogCoord : TEXCOORD7; //单个fog可以写half
float3 worldPos : TEXCOORD8;
//float4 shadowCoord : TEXCOORD8;
};
VertexOutput vert(VertexInput v)
{
VertexOutput o;
VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS.xyz);
o.position = positionInputs.positionCS;
o.positionWS = positionInputs.positionWS;
//o.shadowCoord = GetShadowCoord(positionInputs);
//o.shadowCoord = TransformWorldToShadowCoord(o.positionWS);
VertexNormalInputs normalInputs = GetVertexNormalInputs(v.normalOS.xyz,v.tangentOS);
o.normalWS = normalInputs.normalWS;
o.tangentWS = normalInputs.tangentWS;
o.bitangentWS = normalInputs.bitangentWS;
o.worldPos = TransformObjectToWorld(v.positionOS.xyz);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv, _NormalTex);
o.fogCoord = ComputeFogFactor(o.position.z);
OUTPUT_LIGHTMAP_UV(v.uvLM, unity_LightmapST, o.uvLM);
return o;
}
float4 frag(VertexOutput i): SV_Target
{
//阴影
float4 SHADOW_COORDS = TransformWorldToShadowCoord(i.worldPos.xyz);
Light lightDirectional = GetMainLight(SHADOW_COORDS);
half shadow = lightDirectional.shadowAttenuation;
//贴图采样
float4 MainTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv.xy);
half4 spec = SAMPLE_TEXTURE2D(_Spc,sampler_Spc,i.uv.xy);
half4 ao = SAMPLE_TEXTURE2D(_AO,sampler_AO,i.uv.xy);
half4 layer1 = SAMPLE_TEXTURE2D(_layer1Tex,sampler_layer1Tex,i.uv.xy* _layer1Tiling);
half3 layer1norm = UnpackNormal(SAMPLE_TEXTURE2D(_layer1Norm,sampler_layer1Norm,i.uv.xy* _layer1Tiling));
half layer1Breakup = SAMPLE_TEXTURE2D (_layer1Breakup,sampler_layer1Breakup, i.uv.xy* _layer1Tiling).r;
half3 detnorm = UnpackNormal(SAMPLE_TEXTURE2D (_DetailNormal,sampler_DetailNormal,i.uv.xy * _DetailTiling));
//主法线处理
float4 normalTXS = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, i.uv.zw);
float3 normalTS = UnpackNormalScale(normalTXS,_NormalScale);
//-------法线混合//
half3 modNormal = normalTS + half3(layer1norm.r * 0.6, layer1norm.g * 0.6, 0);
//-------TBN矩阵换算等参数准备//
real3x3 TBN = real3x3(i.tangentWS, i.bitangentWS, i.normalWS);
half3 normalWS = TransformTangentToWorld(modNormal,TBN);
detnorm = normalize(mul(detnorm,TBN));
normalTS = normalize(mul(normalTS,TBN));
layer1norm = normalize(mul(layer1norm,TBN));
//准备混合遮罩///万能的遮罩,后面给反射这些也要混合使用它!!!
half3 layer1direction = half3(0,1,0);
half blend = dot(normalWS, layer1direction);
half blend2 = (blend * _Power + _Shift) * lerp(1, layer1Breakup, _layer1BreakupAmnt);
blend2 = saturate(pow(blend2, 3));
//合并法线:合并法线操作之前必须在世界空间(即纹理解包+TBN之后才能使用)
half3 blendedNormal = lerp(normalTS, layer1norm, blend2);
blendedNormal = blendedNormal + (detnorm * half3(_DetailInt,_DetailInt,0)); //恭喜你获得了这个关键的法线!!!
//向量准备
half3 L = normalize(lightDirectional.direction);
half3 V = SafeNormalize(_WorldSpaceCameraPos - i.positionWS);
half3 H = normalize(L + V);
half3 N = blendedNormal.rgb;
//向量点积
half HdotL = max(dot(H, L), 1e-5); //1e-5代表10的负5次方,也就是0.00001
//half NdotL = max(dot(N, L), 1e-5);
half NdotL = dot(N, L)*0.5+0.5;
NdotL = lerp(NdotL,max(dot(N, L), 1e-5),0.75); //这个是我个人喜好!
half NdotV = max(dot(N, V), 1e-5);
half HdotN = max(dot(H, N), 1e-5);
//合并反射、AO等贴图(使用上面同样的那个Mask)
half3 blendedAlbedo = lerp(MainTex.rgb, layer1.rgb, blend2);
half blendedSmoothness = lerp(spec.a, layer1.a, blend2);
half3 blendedMetallic = lerp(spec.r, _layer1Metal, blend2);
//PBR参数准备
half metallic = blendedMetallic.r * _Metallic;
half smoothness = blendedSmoothness;
half roughness = (1 - smoothness) * _Roughness;
half3 F0 = Direct_F0_Function(blendedAlbedo, metallic); //这里面定义的方法使用了奇技淫巧,去包含文件去看
half3 Direct_F = Direct_F_Function(HdotL, F0);
//----// 直线光漫反射
half3 KS = Direct_F;
half3 KD = (1 - KS) * (1 - metallic);
half3 DirectDiffColor = KD * blendedAlbedo * lightDirectional.color * NdotL;
// 镜面反射
half Direct_D = Direct_D_Function(HdotN, roughness);
//----// BRDF
#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
#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 * lightDirectional.color * (NdotL * PI * ao.rgb);
// 第一部分(直线光照结果)
half3 DirectColor = DirectDiffColor + DirectSpeColor;
// 第二部分:间接漫反射
half3 shColor = SH_IndirectionDiff(N) * ao.r;
half3 Indirect_KS = Indirect_F_Function(NdotV, F0, roughness);
half3 Indirect_KD = (1 - Indirect_KS) * (1 - metallic);
half3 IndirectDiffColor = shColor * Indirect_KD * blendedAlbedo;
// return half4(IndirectDiffColor, 1); // jave.lin : 添加一个 反射探针 即可看到效果:reflection probe
// 间接反射
//----// 反射探针的间接光
half3 IndirectSpeCubeColor = IndirectSpeCube(N, V, roughness, ao.r);
half3 IndirectSpeCubeFactor = IndirectSpeFactor(roughness, smoothness, BRDFSpecSection, F0, NdotV);
half3 IndirectSpeColor = IndirectSpeCubeColor * IndirectSpeCubeFactor;
half3 IndirectColor = IndirectDiffColor + IndirectSpeColor;
//half3 ambient = _GlossyEnvironmentColor.rgb;
//1,2部分光照结果(总)
half3 finalCol = DirectColor + IndirectColor;
finalCol.rgb = MixFog(finalCol.rgb,i.fogCoord);
//光照烘焙颜色
half3 bakedGI = SAMPLE_GI(i.uvLM, i.vertexSH, normalWS);
//float shadow = MainLightRealtimeShadow(i.shadowCoord); // shadow
finalCol.rgb = lerp(_ShadowMainColor.rgb * finalCol.rgb, finalCol.rgb, shadow);
finalCol.rgb =finalCol.rgb+ bakedGI.rgb*finalCol.rgb;
clip(_Color.a-0.5);
return float4(finalCol.rgb,1) ;
}
ENDHLSL
}
UsePass "Universal Render Pipeline/Lit/ShadowCaster"
UsePass "Universal Render Pipeline/Lit/DepthOnly" //深度绘制Pass防止消失
}
FallBack "Packages/com.unity.render-pipelines.universal/FallbackError"
}
//奇技淫巧
#ifndef PBRMath
#define PBRMath
// 直接光照 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);
}
// 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);
// }
inline half3 Direct_F0_Function(half3 albedo, half metallic)
{
return lerp(0.04, albedo, metallic);
}
// 直接光照 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
inline half Direct_G_Function_Kalos(half LdotH, half roughness)
{
half k = pow(1 + roughness, 2) * 0.5;
return Direct_G_subSection(LdotH, k);
}
//间接漫反射函数
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;
}
#endif
References:
感谢两位前辈的经验分享:
Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)_unity urp pbr_Jave.Lin的博客-CSDN博客
Unity Shader 学习笔记(4)URP渲染管线带阴影PBR-Shader模板 -- 新增可自定义阴影颜色_unity urp设置不接收阴影_嘿皮土豆的博客-CSDN博客