PBR学习个人总结(一)

时间:2023-03-24   阅读次数:

序言

机缘巧合,初次接触Shader是看到同事桌前摆了乐乐女神的《Unity Shader入门精要》这本书,翻了翻发现里面有讲到数学基础矩阵矢量之类的,同事还说很难的,很容易让人劝退呀。

也是有缘,在一次视频中看到闫令琪老师讲的图形学时让我对图形学有了新的认知,原来图形学这么好玩有意思?自此决定了无论多难也要去学学Shader这门学问。

先是B站找到的《Unity Shader从入门到精通视频教程》和《Unity Shader Lab实践》这本书等,认真边学边实践了一遍,因本人是美术出身但有一定的编程基础,认识Shader结构和编写代码时也并不费劲,很多语法级片段都不是复制而是

期间经常跑去了广州图书馆借了好些书,如《3D数学基础图形和游戏开发第2版》认真做了笔记,让我学习比较深刻的内容是矩阵部分。

再有就是学习一些Unity URP节点和ASE的学习,照着网上资料学习了一翻,除了比较直接和了解比较方便之外,你会发现看起来很多节点拼凑的复杂图形,其实在代码来写就一小段而已。

之后突然想着应该要着重学习PBR的问题,于是花了很大时间和精力去学习这个PBR(Physically-Based Rendering)基于物理的渲染,期间过程很痛苦,一下子堆到你面前的是会看懵的各种公式,还好网络上有各位大佬们分享的宝贵经验:

如毛星云大佬的【基于物理的渲染(PBR)白皮书】反复看了N遍,基本理清了PBR的流程,有了大体构架之后目标就明确多了。还有其他大佬们的精彩文章如:《猴子都能看懂的PBR》  《Unity Standard Shader 技术分析》

更多的是一些外文资料,很多代码和公式看多了也就知道大概,看不懂的表述就用在线翻译,基本能知道其中意思。

在沉迷于学习期间,很折磨也很快乐,且收获颇多。

发出这篇文章在于交流,有不到之处希望大佬们加以指正,谢谢!

一、 PBR知识体系架构图

认识PBR前,还是一定要多熟悉这个架构图,对于理清各分支细节有很大的好处,特别是刚接触容易迷惑之时。

pbr_jiagoutu.png

上图是自己绘制的比较简版的架构图,详细的PBR架构图请可以去下载查看

 

         [毛星云:PBR知识体系的架构图]

         清晰了上图的架构之后 ,就可以按各分支详细分解了。

几个分支组合之后的效果如下:

pbr_fin.jpg

二、资源列表

示例模型为网上下载的一个复古电脑,仅1920个三角面的低模,等有时间了再自己做一个高模的示例模型吧。

贴图有5张: Albedo基本色贴图、自发光贴图、金属度贴图、粗糙度贴图、法线贴图、以及AO遮挡贴图。

pbr_mode_dn.png

pbr_textures.png

Properties属性内容

pbr_properties.png

在面板中Option添加了几大分支的开关与选择项,在编写时便于调试使用。

Shader代码片段:

Properties
    {
        _DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
        [Space(10)]_MainTex ("BaseTexture", 2D) = "white" {}
        [NoScaleOffset]_BumpMap("BumpMap",2D)="white"{}
        _BumpScale("BumpScale",Range(0,1))=1
 
        [Space(20)]
        [NoScaleOffset]_MetallicTex("MetallicTex",2D)="white"{}
        _MetallicScale("MetallicScale",Range(0.0,1.0))=0.0
        [NoScaleOffset]_RoughnessTex("RoughnessTex",2D)="white"{}
        _RoughnessScale("RoughnessScale",Range(0,1))=0.5
 
        [Space(20)] [NoScaleOffset]_OcclusionTex("OcclusionTex",2D)="white"{}
        _OcclusionScale("OcclusionScale",Range(0,1))=1
 
        [Space(10)] [NoScaleOffset]_EmssionTex("EmssionTex",2D)="white"{}
        [HDR]_EmissionColor("EmissionColor",Color)=(1,1,1)
 
        [Header(Aniso)]
        [Space(10)]
        [KeywordEnum(None,GTR2,GGX)] _ANISO("AnisoType",float)=0
 
        _Aniso("Aniso",Range(0,1))=0
        _AnisoOffset("AnisoOffset",Range(-10,10))=0
        _AnisoRoat("AnisoRoat",Range(-10,10))=0
        // [Space(10)] _ClearCoat("ClearCoat",Range(0,1))=0
        [Header(Option)]
        [Space(10)]
        [Toggle] _IS_AMBIENT("自发光",float)=1
        [Toggle] _IS_DIFFUSE("漫反射",float)=1
        [Toggle] _IsSpecular("镜面反射",float)=1
        [KeywordEnum(None,GGXTerm,GTR1,GTR2,GND)] _UIDNF("法线分布 D项",float)=0
        [KeywordEnum(None,Unity,Schlick,SG)] _UIF("菲涅尔 F项",float)=0
        [KeywordEnum(None,Filament,GGX,GSF,Beckman)] _UIG("几何遮蔽 G项",float)=0
        [Toggle] _ISIBL("环境光照",float)=1
    }

Pass中需要引入与Shader文件同目录的自定义的文件: #include "Pbr_Function.cginc"


三、漫反射项

3.1 漫反射和镜面反射

先从漫反射和镜面反射开始,当灯光照射一个标准的BRDF材质时可以拆分成两部分:漫反射+镜面反射,如下图可以有一个很直观的了解。

image.png

3.2公式

漫反射项使用的是Unity内置的Disney Diffuse函数。

gs_disneyDiffuse.png

gs_disneyDiffuse1.png

3.3效果展示

image.png

只贴上基础纹理贴图时的效果


shader_20230324_GGXTerm.png  
   左侧为是显示明显的对比,把阀值范围设成-2~2之间


3.4 相关代码

以下为UnityShader中内置的Disney Diffuse代码部分。

half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
    half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
    // Two schlick fresnel term
    half lightScatter   = (1 + (fd90 - 1) * Pow5(1 - NdotL));
    half viewScatter    = (1 + (fd90 - 1) * Pow5(1 - NdotV));
    return lightScatter * viewScatter;
}
inline half Pow5 (half x){
    return x*x * x*x * x;
}

Pow5这个方法,有一维到四维,但为什么不直接用pow函数,而是使用相同的量相乘5次,其在文中也有说到其两个好处:

1.   更好的优化渲染管线

2.   不用担心出现异常





参考资料

[01]【基于物理的渲染(PBR)白皮书】系列

[02] Diffuse irradiance (https://learnopengl.com/PBR/IBL/Diffuse-irradiance)  

[03]Specular IBL (https://learnopengl.com/PBR/IBL/Specular-IBL)

[04]Anisotropic specular BRDF 4.10.1篇 https://google.github.io/filament/Filament.html#materialsystem/anisotropicmodel

其它文章