零、前言
语雀: PBR 学习总结:实战篇
上一篇介绍了PBR的理论知识,这篇使用Unity进行实战,先来看下效果:

一、工作流
在PBR中,为了在不同的光照环境下保持材质表现的一致性和真实感,主要分为了两种工作流:
- 金属/粗糙度(Metallic/Roughness)
- 镜面反射/光泽度(Specular/Glossiness)
1. 金属 / 粗糙度工作流 (Metallic/Roughness)
这是目前游戏引擎中最常用的工作流
贴图:
- Base Color(基础色): 包含非金属的漫反射颜色和金属的镜面反射颜色(需要配合metallic将diffuse和spcular分离)
- Metallic(金属度): 一张黑白图(通常建议只用纯黑或纯白)。白色(1.0)表示金属,黑色(0.0)表示非金属(绝缘体)
- Roughness(粗糙度): 描述表面的平整程度。1.0 为完全粗糙(漫反射散射),0.0 为完全平滑(镜面反射)。
2. 镜面反射/光泽度工作流 (Specular/Glossiness)
更传统的 PBR 方案,常见于影视渲染和较早的游戏渲染管线中
贴图:
- Diffuse(漫反射):仅包含非金属的漫反射颜色
- Specular(镜面反射):一张 RGB 彩色贴图。控制材质表面的反射颜色和强 度
- Glossiness(光泽度): 与 Roughness 相反。1.0 表示非常平滑,0.0 表示非常粗糙
本文中使用金属、粗糙度的工作流,来制作
二、项目设置
1. 贴图设置
- Albedo、BaseMap、DiffuseMap、SpecularMap需要勾选sRGB
- Roughness、Metallic、AO等是线性空间计算的,不要勾选sRGB
- Normal在Unity中选择Normal Map
2. 后处理设置
- ToneMapping – ACES
- ColorGrading 调下色(处理ACES后过暗)
Bloom(太闪不好看)- 暗角
- DoF
3. 摄像机设置
- 调整FOV :20~30
- 剔除
三、代码
记得加上ShadowCaster等Pass
面板参数
如下
_Albedo ("Albedo", 2D) = "white" {}
_BaseColor("Base Color", Color) = (1.0, 1.0, 1.0, 1.0)
_MetallicTex ("Metallic Tex", 2D) = "white" {}
_Metallic ("Metallic", Range(0,1)) = 0.0
_RoughnessTex ("Roughness Tex", 2D) = "white" {}
_Roughness ("Roughness", Range(0,1)) = 0.5
_NormalTex ("Normal Tex", 2D) = "bump" {}
_NormalScale ("Normal Scale",Range(0, 1)) = 0.5
_OcculsionTex ("Occlusion Tex", 2D) = "white" {}
_OcclusionStrength ("Occlusion Strength", Range(0,1)) = 1.0
_EmissionTex ("Emission Tex", 2D) = "white" {}
_EmissionIntensity ("Emission Intensity", Range(0,10)) = 1.0
向量、贴图
// 向量准备
half3 normalWS = normalize(input.normalWS);
half3 tangentWS = normalize(input.tangentWS);
half3 bitangentWS = cross(normalWS, tangentWS);
half3x3 TBN = half3x3(tangentWS, bitangentWS, normalWS);
float3 positionWS = input.positionWS;
half4 shadowMask = half4 (1.0, 1.0, 1.0, 1.0);
half3 viewDir = GetWorldSpaceNormalizeViewDir(positionWS);
// 主光源
Light mainLight = GetMainLight(input.shadowCoord, positionWS, shadowMask);
float shadow = mainLight.shadowAttenuation;
float3 lightDir = mainLight.direction;
float3 lightColor = mainLight.color;
// 贴图采样
float4 baseMap = SAMPLE_TEXTURE2D(_Albedo, sampler_Albedo, input.uv) * _BaseColor;
float metallic = _Metallic * SAMPLE_TEXTURE2D(_MetallicMap, sampler_MetallicMap, input.uv).r;
float roughness = _Roughness * SAMPLE_TEXTURE2D(_RoughnessMap, sampler_RoughnessMap, input.uv).r;
float4 normalMap = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv);
float3 normalMapTS = UnpackNormalScale(normalMap,_NormalScale);
float3 normalMapWS = normalize(mul(normalMapTS, TBN));
float occlusionMap = SAMPLE_TEXTURE2D(_OcculsionMap, sampler_OcculsionMap, input.uv).r;
float3 Emission = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.uv).rgb * _EmissionIntensity;
float occlusion = lerp(1.0, occlusionMap, _OcclusionStrength);
BRDF参数
在金属粗糙度工作流中,从BaseMap/Albedo中获取DiffuseColor和SpcularColor
float3 DiffuseColor = lerp(baseMap * (1 - 0.04), 0, metallic);
float3 SpecualrColor = lerp(0.04, baseMap, metallic);
直接光
漫反射
float3 Diffuse_Lambert( float3 DiffuseColor )
{
return DiffuseColor * (1 / PI);
}
镜面反射
D项
// GGX / Trowbridge-Reitz
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
float D_GGX( float a2, float NoH )
{
float d = ( NoH * a2 - NoH ) * NoH + 1; // 2 mad
return a2 / ( PI*d*d ); // 4 mul, 1 rcp
}
F项
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float3 F_Schlick( float3 SpecularColor, float VoH )
{
float Fc = Pow5( 1 - VoH ); // 1 sub, 3 mul
//return Fc + (1 - Fc) * SpecularColor; // 1 add, 3 mad
// Anything less than 2% is physically impossible and is instead considered to be shadowing
return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}
V项
// Appoximation of joint Smith term for GGX
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
float Vis_SmithJointApprox( float a2, float NoV, float NoL )
{
float a = sqrt(max(a2, 1e-7));
float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
return 0.5 * rcp( Vis_SmithV + Vis_SmithL + 1e-5 );
}
V项就是将 公式中的分母和G项放在一起计算
整合
float D = D_GGX_UE(a2, NoH);
float Vis = Vis_SmithJointApprox(a2, NoV, NoL);
float3 F = F_Schlick_UE(SpecularColor, VoH);
float3 DiffuseTerm = Diffuse_Lambert(DiffuseColor) * Radiance;
float3 SpecularTerm = D * Vis * F * Radiance;
float3 kS = F; // 镜面反射系数
float3 kD = (1.0 - kS) * (1.0 - metallic); // 剩下的才是漫反射
float3 DirectLighting = kD * DiffuseTerm + SpecularTerm;
多光源和主光计算逻辑一样,for循环处理每一盏灯
间接光
漫反射
球谐函数,模拟环境光漫反射
float3 RadianceSH = SampleSH(N);
镜面反射
a. 预过滤环境贴图
采样CubeMap的MipMap
float mipLevel = perceptualRoughness * (1.7 - 0.7 * perceptualRoughness) * UNITY_SPECCUBE_LOD_STEPS;
float4 encodeSpecularRadiance = unity_SpecCube0.SampleLevel(samplerunity_SpecCube0, reflect(-v, normal), mipLevel);
float3 specularRadiance = DecodeHDREnvironment(encodeSpecularRadiance, unity_SpecCube0_HDR);
//Unity也给我们写好了方法
float3 specularRadiance = GlossyEnvironmentReflection(R, positionWS, Roughness, Occlusion);
b. 环境BRDF
流派1:LUT
//LUT采样
float2 env_brdf = tex2D(_BRDFLut,float2(lerp(0,0.99,NV),lerp(0,0.99,Roughness))).rg;
流派2:解析拟合
half3 EnvBRDFApprox( half3 SpecularColor, half Roughness, half NoV )
{
// [ Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II" ]
// Adaptation to fit our G term.
const half4 c0 = { -1, -0.0275, -0.572, 0.022 };
const half4 c1 = { 1, 0.0425, 1.04, -0.04 };
half4 r = Roughness * c0 + c1;
half a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
half2 AB = half2( -1.04, 1.04 ) * a004 + r.zw;
// Anything less than 2% is physically impossible and is instead considered to be shadowing
// Note: this is needed for the 'specular' show flag to work, since it uses a SpecularColor of 0
AB.y *= saturate( 50.0 * SpecularColor.g );
return SpecularColor * AB.x + AB.y;
}
整合
//漫反射:SH
float3 DiffuseTerm = DiffuseColor * RadianceSH * Occlusion;
//镜面反射:IBL
//第一项
float3 Env_IBL = GlossyEnvironmentReflection(R, positionWS, Roughness, Occlusion);
//第二项
float3 Env_Brdf = EnvBRDFApprox(SpecularColor, Roughness, NoV);
float3 SpecularTerm = Env_IBL * Env_Brdf * Occlusion;
float3 kS = Env_Brdf;
float3 kD = (1.0 - kS) * (1.0 - metallic);
float3 IndirectLighting = kD * DiffuseTerm + SpecularTerm;
完结撒花~


-哔哩哔哩.jpg)
