PBR渲染方程简单总结和实现

渲染方程的一些总结和简单实现

背景

看了很多PBR相关的文章了,虽然复杂,但似乎使用起来很简单的样子。
这儿写个简单版的PBR实现,留下点学习痕迹。

渲染方程

经典光照模型

1975年Phong提出Phong反射模型(Phong Reflection Model):


1977年Blinn对Phong模型做出修改,这就是后来广泛使用的Blinn-Phong反射模型:


一个简单的基于物理的Blinn-Phong:

辐射度量学

物理上辐射度量学的基本量及其关系(符号被我简化了,比如立体角和偏导):

名称 英文名 符号 单位 公式 补充描述
辐射能量 Radiant energy 焦耳() - 电磁辐射能量
辐射通量 Radiant flux 瓦() 单位时间辐射的能量
辐==照==度 Irradiance 到达单位面积的辐射通量
辐射度 Radiosity ==离开==单位面积的辐射通量
辐射强度 Radiant Intensity 通过单位立体角的辐射通量
==辐射率== Radiance 不随距离变化
立体角 Solid Angle 角度的二维扩展,

辐射率被用来量化单一方向上发射来的光线的大小或者强度,不随距离变化,人眼看到的颜色强度的度量单位就是这个。

PBR渲染方程

PBR的渲染方程(反射方程)一般长这样:

v是观察方向,l是光照方向,。
有些文章使用, 表示按向量的分量相乘,因为和都包含RGB三个分量。
辐射率与辐照度按定义有这样的关系:

双向反射分布函数BRDF的定义是:。

积分方程一般用蒙特卡洛方法近似计算,即便如此,也是不能实时计算,一般都是先离线预处理。知道的几个方法

  1. 离线计算积分结果,保存成类似cubemap,然后shader里根据方向采样。
  2. 离线计算3阶左右的球谐系数,使用球谐光照来快速计算积分。

理想光源的渲染方程

对于理想光源(点光源、方向光等),是没有意义的,比如平行光,只有一个方向有值,且值是无限大。
对于理想光源照射的物体来说,有意义的是辐照度,渲染方程退化成:

这个方程可以在shader里实时算。

BRDF

下面这个模型大概是叫Cook-Torrance BRDF模型吧。
首先BRDF分为漫反射和镜面反射两部分:
系数用来控制能量守恒的。

漫反射BRDF:

镜面反射BRDF:


n是表面法线
h就是前面提到的半角向量,在这儿它多了一个含义,微表面法线

法线分布函数,D(Normal Distribution Function,NDF):


α是表面的粗糙度(UE的文章定义,导致有些文章写错了,这儿直接就是roughness),n是表面法线。这个方程是大佬特地研究出来的。

菲涅尔方程,F(Fresnel equation)


菲涅尔现象是掠角越大,镜面反射越强,表现出边缘高光现象。
表示的是表面基础反射率,这个值区分了金属和非金属。金属大于0.5,非金属小于0.2,并且常见的非金属小于0.04。
这个值输入的方式一般是:金属的通过贴图输入,非金属的直接设置为0.04。

vec3 F0 = vec3(0.04);
F0 = lerp(F0, albedo, metallic);
漫反射系数

前面提到的系数与有关系。
令,就能达到能量守恒了:镜面反射+漫反射<=1,同时还减弱金属的漫反射。

几何遮挡函数,G(Geometry function)




整合后的PBR渲染方程


其中有各种不同的方案,disney、unity、unreal的渲染方程就有差别。
这儿只是列一个可行的方案。

关于实现

分两种情况

  • 理想光源用累加方程实时算。
  • 积分方程,知道有两种方式
    • 采用预计算出纹理的方式,在shader里采样纹理计算。
    • 球谐光照,预计算去球谐系数,然后shader里算球谐向量积。

PBR的渲染需要综合间接光才能效果不错。

关于线性相加

PBR需要在线性空间里计算,不同光源是可以线性相加的。当结果>1时,有专门的算法归一。


exposure 默认是1,可以设置,也可以根据上一帧的图像平均亮度平滑过渡。
可以看LearnOpenGL的评论区。

代码

Unity SimplePBRShader

只实现了一个,只支持一个平行光。
额外利用了Unity内置的一些功能:Shadow,SH环境光,SkyBox。
标准胶囊体的渲染接近Standard了,有那么个感觉了。
获取环境间接光的部分真是~~~下次搞懂吧。

Shader "SimplePBRShader"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        [Gamma] _Metallic("Metallic", Range(0,1)) = 0
        _Glossiness("Smothness", Range(0, 1)) = 0.5 // 方便对比Standard
        //_Roughness("Roughness", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque"}
        LOD 200
        Pass{
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // shadow on 不加默认是关闭的
            #pragma multi_compile_fwdbase
                
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"

            float4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _Metallic;
            float _Glossiness;

            static const float PI = 3.14159265359;

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
                UNITY_FOG_COORDS(4)
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);// 法线特殊处理
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                TRANSFER_SHADOW(o);
                UNITY_TRANSFER_FOG(o, o.pos);
                return o;
            }

            float BRDF_D(float3 N, float3 H, float roughness)
            {
                const float PI = 3.14159265359;
                float a2 = roughness * roughness;
                float a4 = a2 * a2;
                float NdotH = max(dot(N, H), 0);

                float t1 = (NdotH * NdotH) * (a4 - 1) + 1;
                float t2 = PI * t1 * t1;
                return a4 / t2;
            }

            float BRDF_G1(float3 N, float3 V, float k)
            {
                float NdotV = max(dot(N, V), 0);
                return NdotV / (NdotV * (1 - k) + k);
            }

            float BRDF_G(float3 N, float3 V, float3 L, float roughness)
            {
                float k = (roughness + 1) * (roughness + 1) / 8.0;// Kdirect

                float g1 = BRDF_G1(N, V, k);
                float g2 = BRDF_G1(N, L, k);
                return g1 * g2;
            }

            float3 BRDF_F(float3 H, float3 V, float3 F0)
            {
                float HdotV = max(dot(H, V), 0);
                return F0 + (1 - F0) * pow(1 - HdotV, 5);
            }

            float3 BRDF_All(float3 N, float3 V, float3 L, float3 albedo, float metallic, float roughness)
            {
                float3 H = normalize(V + L);

                float NdotL = max(dot(N, L), 0);
                float NdotV = max(dot(N, V), 0);

                float3 F0 = lerp(0.04, albedo, metallic);

                float D = BRDF_D(N, H, roughness);
                float3 F = BRDF_F(H, V, F0);
                float G = BRDF_G(N, V, L, roughness);

                float3 kD = 1 - F;
                kD *= 1 - metallic;

                float3 DFG = D * F * G;
                float3 specular = DFG / max(4 * NdotV * NdotL, 0.001);// avoid divide zero

                float3 brdf = kD * albedo / PI + specular;
                return brdf * NdotL;
            };

            float3 Get_EnvColor(float3 N, float3 V, float3 albedo, float metallic, float roughness)
            {
                // https://github.com/Arcob/UnityPbrRendering/blob/master/Assets/unity%20pbr/Height2.shader
                // 又瞎调了一遍参数,效果勉强有些的样子,Orz。得找找关于天空盒子之类的文章。
                // 发现竟然和Unity的Standard差不多,赞一个。

                float3 F0 = lerp(0.04, albedo, metallic);
                float NdotV = max(dot(N, V), 0);
                float3 F = F0 + (max(1.0 - roughness, F0) - F0) * pow(1.0 - NdotV, 5.0);
                // float3 F = F0 + (1 - F0) * pow(1.0 - NdotV, 5.0);

                float3 reflectViewDir = reflect(-V, N);
                float4 skyData = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectViewDir, roughness*10);
                float3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);

                //float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                float3 ambient = ShadeSH9(float4(N, 1));
                float3 kD = 1 - F;
                kD *= 1 - metallic;
                return skyColor * F + ambient * kD * albedo/* / PI*/;
            }

            float4 frag(v2f i) :SV_TARGET
            {
                float3 worldNormal = normalize(i.worldNormal);
                float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);// 平行光
                float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

                float3 albedo = tex2D(_MainTex, i.uv) * _Color;

                float _Roughness = 1 - _Glossiness*0.95;// 没有绝对光滑的物体,这样就和Unity更接近了

                float3 brdf = BRDF_All(worldNormal, viewDir, worldLightDir, albedo, _Metallic, _Roughness);
                float3 L0 = brdf * _LightColor0.rgb;
                L0 *= PI;// Unity trick, Otherwise, the result is too dark.

                float shadow_pass = SHADOW_ATTENUATION(i);

                float3 envColor = Get_EnvColor(worldNormal, viewDir, albedo, _Metallic, _Roughness);
                float3 color = envColor + shadow_pass * L0;

                UNITY_APPLY_FOG(i.fogCoord, color);
                return float4(color,1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Unity自带的一些系统的使用

  1. Shadow
    1. https://www.jianshu.com/p/7b56eeb5ba4c

    2. https://blog.csdn.net/A13155283231/article/details/95073757

  2. LightProbe
    1. https://www.jianshu.com/p/6dfe403f75f6
  3. SkyBox
    1. https://github.com/Arcob/UnityPbrRendering/blob/master/Assets/unity%20pbr/Height2.shader

你可能感兴趣的:(PBR渲染方程简单总结和实现)