Unity-PBR学习

image-20211110142932731

PBR

首先什么是PBR?PBR全名:Physically Based Rendering基于物理的渲染,是指基于物理原理微平面理论建模的着色/光照模型。

PBR基础理念概括

1.微平面理论。在微观的尺度上任何平面可以被称为微平面的细小镜面进行描述。根据粗糙程度的不同,微平面的排位也不尽相同。在实际的工作流程,这种物体表面的不规则形用粗糙度贴图高光度贴图表示。

img

2.能量守恒。出射光线的能量永远不会超过入射光线的能量。随着粗糙度的上升镜面反射区域的面积会增加,作为平衡镜面反射区域的平均亮度也会下降。

3.菲涅尔反射。光线以不同的角度入射会有不同的反射率。相同的入射角度不同物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属的F0范围是0.02~0.04,大多数金属的F0范围是0.7~1.0。

4.线性空间。光照计算必须在线性空间完成,Shader中输入的gamma空间的贴图比如漫反射贴图需要转成线性空间,而描述物体表面属性的贴图如粗糙度,高光贴图,金属贴图等必须保证是线性空间。

5.金属与非金属的差别。非金属具有单色/灰色镜面反射颜色。而金属具有彩色的镜面反射颜色。即非金属的F0是一个float。而金属的F0是一个float3。

金属:漫反射率=0,漫反射颜色=黑,高光反射率=reflctivity,高光颜色=自身颜色

非金属:漫反射率=1-reflctivity,漫反射颜色=自身颜色,高光反射率=0.04,高光颜色=灰黑

img

Unity URP PBR

首先我们需要看看Unity URP PBR源码长啥样的,才会对后面的修改光照模型心中有数。首先查看Lighting文件的BRDFData结构体,这个结构体包含了BRDF计算的核心参数

image-20211109141932901

接下来就是初始化BRDFData数据,这里用宏定义是高光度流程还是金属度流程,我们这里只讨论金属度流程

image-20211109142142387

由于非金属的光照特性是:高光反射率在4%左右,所以在Unity中定义了kDielectricSpec来存储了一个经验数值,计算好漫反射率和高光反射率,再计算漫反射和高光颜色,最后传入InitializeBRDFDataDirect中做进一步处理。

#define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
half OneMinusReflectivityMetallic(half metallic)
{
    // We'll need oneMinusReflectivity, so
    //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
    // store (1-dielectricSpec) in kDielectricSpec.a, then
    //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
    //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = kDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}

整体框架如下:

preview

BRDF

BRDF是渲染方程的一种,是用来模拟光的视觉效果的光照模型,其实我们知道兰伯特 + BlinnPhong其实就是漫反射+高光。BRDF可以简易概括为两部分的实现直接光照和间接光照,同时两部分又可以包含漫反射和高光两部分,所以可以分为:

  1. 直射光漫反射
  2. 间接光漫反射
  3. 直接光镜面反射
  4. 间接光江面反射

BRDF就是把他所需要的数据扔进这个光照渲染方程里面,计算出PBR的最终颜色算法

Unity的BRDF

Unity的BRDF分为三级:

  1. brdf1:基于迪士尼的brdf
  2. brdf2:brdf1的优化版
  3. brdf3:预烘焙NHRoughness

效果往下逐级递减,性能往下逐级上升。

Unity会根据你选择的项目性能进行BRDF的区分

image-20211110105238575

知乎大神Lute Li理解的brdf算法简单易懂

preview

BRDF1

1.漫反射项使用了DisneyDiffuse模型,与原版不同,Unity的DisneyDiffuse计算中没有除以π,Unity给出的解释是除以π会暗好多,为了保证物理平衡后面计算Specular的时候会乘以一个π。

image-20211110105941628

2.GGX模型,GGX由三部分组成,D法线分布,G几何遮蔽和几何阴影,F菲涅尔,在Unity中G被V代替,即Visibility可见性。

D:GGX法线分布

image-20211110111004373

V:SmithJointGGX

image-20211110110929518

F:用Schlick的近似经验模型

image-20211110111159370

BRDF2

1.漫反射项使用了Lambert模型,在BRDF2函数中 ,没有对漫反射做特殊处理,只是在最后各项数据合并的时候才对光照漫反射部分进行了Lambert计算

image-20211110111636680

2.镜面反射项,Unity对V,F函数进行结合拟合操作。

D依然为GGX分布函数,V为修改后的Kelemen可见项,F拟合为 1/L*H

image-20211110112624188

Unity在siggraph2015年中的分享内容,最终高光项

image-20211110113234811

BRDF3

TODO…..

金克丝实践

根据知乎大佬的文章进行实践,整体实现主要结合ShaderGraph和CustomFunction实现,在ShaderGraph中准备好的BRDF所需要的数据,再传进BRDF中做进一步的光照计算。

image-20211110142821750

void lolm_brdf2_float(in float3 MainColor,in float3 MaskColor,in float3 MatcapColor,in float3 OcclusionColor,in float3 EmissionColor,in float4 SpecularColor,in UnityTexture2D SSS_Lut_Tex,in float3 WorldNormal,in float3 WorldViewDir, in float3 WorldPos,out float3 FinalColor)
{
    #ifdef SHADERGRAPH_PREVIEW
        FinalColor = float3(1,1,1);

    #else
        #if SHADOWS_SCREEN
            half4 clipPos = TransformWorldToHClip(WorldPos);
            half4 shadowCoord = ComputeScreenPos(clipPos);
        #else
            half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
        #endif


    //获取主光源信息
    Light light = GetMainLight(shadowCoord);

    // 基础数据准备
    float halfDir = normalize(light.direction + WorldViewDir);

    float nl = saturate(dot(WorldNormal,light.direction));
    float nh = saturate(dot(WorldNormal,halfDir));
    float nv = saturate(dot(WorldNormal,WorldViewDir));
    float lh = saturate(dot(light.direction,halfDir));

    //获取光源根据法线的反射方向
    // float worldLightReflectDir = normalize(reflect(-light.direction,WorldNormal));
    // float lrDotv = dot(worldLightReflectDir,WorldViewDir);

    float halfLambert = nl*0.5f+0.5f;

    // 直接光的漫反射项
    //计算漫反射率
    float oneMinusReflectivity = 0.96f *(1 - MaskColor.r);
    float3 diffuseColor = MainColor * oneMinusReflectivity;
    //float3 diffuseColor = MainColor;

    // 直接光的高光反射项
    // 计算高光反射率
    float reflectivity = 1.0f - oneMinusReflectivity;
    // 计算高光颜色
    float3 _selectSpecColor = lerp(float3(0.04f,0.04f,0.04f),MainColor,MaskColor.r);
    // 首先计算D项
    float roughness = 1 - MaskColor.g;
    float a = roughness * roughness;
    float a2 = a*a;

    float D = nh * nh * (a2 - 1.0f) + 1.00001f; 

    // 使用的是BRDF2,VF是用拟合做处理
    float SpecularTerm =  a2 / (max(0.1f, lh*lh) * (roughness + 0.5f) * (D * D) * 4 * 3.14f);
    //SpecularTerm = clamp(SpecularTerm,0.0f,1.0f);
    //SpecularTerm = 1- SpecularTerm;

    //自选高光Pow,控制高光区域
    // float _specPow = 0.5;
    //高光强度
    // float _specStrength = 3;

    // half specularPow = lerp(2,4,MaskColor.b);
    // half phong = pow(saturate(lrDotv),specularPow);
    // float3 directSpecular = _selectSpecColor * phong * _specStrength;

    // 模拟次表面散射
    float3 sss_Color = tex2D(SSS_Lut_Tex,float2(halfLambert, MaskColor.b));

    half surfaceReduction = (0.6-0.08*roughness);
    surfaceReduction = 1.0 - a*roughness*surfaceReduction;

    //half oneMinusReflectivity = (1-MaskColor.r)*0.04f;
    half grazingTerm = saturate(MaskColor.g + reflectivity);


    // 间接光的漫反射部分
    //环境光部分
    float3 ambient = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
    float indirectDiffuse = ambient * OcclusionColor.r * diffuseColor;

    // 间接光的高光反射部分

    FinalColor = (diffuseColor +  _selectSpecColor * SpecularTerm ) * light.color * sss_Color + indirectDiffuse 
    + surfaceReduction * MatcapColor * FresnelLerpFast(_selectSpecColor,grazingTerm,nv) + EmissionColor ;
    //FinalColor =light.shadowAttenuation;
    #endif

}

最终效果如下,目前对于PBR流程有了更多的了解,但还是有点云里雾里的,还需要后面多进行实践,才能真正地弄懂PBR。

image-20211110142924585

引用

  1. LOLm渲染分析
  2. Unity的BRDF简易理解
  3. Unity中BRDF各种类型
  4. Unity UE4 BRDF研究
  5. Unity BRDF函数计算
  6. Unity中BRDF与论文公式不一致的问题
  7. 迪士尼BRDF
  8. Unity Siggraph2015
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2021 Opda
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信