PBR 学习总结:实战篇

零、前言

语雀: 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;

完结撒花~

后续看情况再补一下效果图?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇