UnityShader BRDF的简易实现

最近《3D游戏与计算机图形学中的数学方法》看到双向反射分布函数,开头的各种术语把爷整懵了,看完才发现竟然前面只是介绍了原理!!!

果然我还是安安分分地套公式好了。

BRDF函数:

\varrho(V,L)=kD+(1-k)\varrho _{s}(V,L)

k表示表示入射光中被漫反射出去的部分光能,1-k则表示剩余的光能,被物体吸收或成为镜面反射。可以差不多把1-k看做金属值。

D表示漫反射颜色,我们简单的用兰伯特光照模型计算即可。

重点就在于镜面反射函数\varrho _{s} 


我们使用Cook-Torrance的BRDF光照模型 

它的镜面反射分量如下:

\varrho _{s}(V,L)=F(V,L)\frac{D(V,L)G(V,L)}{\pi (N\cdot V)(N\cdot L)}

F 为Fresnel因子,是反射光线的强度和颜色与入射角度之间的关系函数。

D 为微平面分布函数,返回某一给定方向微平面比例。

G 为微平面的自屏蔽情况。

我们提供了视角方向V,光源方向L,中间向量H=(V+L)/2 ,通过D筛选得到了该点微平面中法向量N=H的比例。(这里的N=H仅是说明微平面的筛选条件,公式中的N指的是该点的法向量,不一定等于H)

Cook-Torrance光照模型将物体表面分解成多个微小平面,即微平面的集合,可以产生真实感很强的镜面反射。每个微平面都被当做符合电磁反射定律的理想反射平面,微平面的斜度反映了表面粗糙度。

UnityShader BRDF的简易实现_第1张图片

 

所以我们要做的就是选择合适的F、 D、G函数,将它们组合在一起。

我找到了这篇文章,它有总结FDG的各种公式,可以看一下(其中π的倒数算在D中了)。


1、菲涅尔系数 

书中讲解的是Cook-Torrance 的公式:η=1+F0−−√1−F0−−√ c=v⋅h g=η2+c2−1−−−−−−−−−√ FCook−Torrance(v,h)=12(g−cg+c)2(1+((g+c)c−1(g−c)c+1)2)

UnityShader BRDF的简易实现_第2张图片

但是这个公式感觉好复杂。。。。所以我选择使用看起来比较简单的Schlick公式:

 

关于F_0

这两个公式中的F_0表示光线方向和法线方向一致时的折射率,你可以直接把它当做一个菲涅尔的调节值。

在公式1中,F_0的目的就是为了得到\eta,可能是F0比较直观吧,\eta表示的是两个介质之间的折射率之比

当光线从法线法线照射时 L·N=1 也就是L·H=1,因为H就是L和V的中间向量,所以V·H=1.

此时公式1简化为F_\lambda ()_L_=_H=(\frac{\eta _\lambda -1}{\eta _\lambda +1})^2

就可以得到\eta

\eta =\frac{1+\sqrt{F_0}}{1-\sqrt{F_0}}

创建一个单独的cginc文件来放置这些函数

#ifndef MY_BRDF_TERM
#define MY_BRDF_TERM

    float Fresnel(float F0,float3 viewDir,float3 halfDir)
    {//schlick
        return F0+(1-F0)*pow((1-dot(viewDir,halfDir)),5);
    }
    //2019.3.29
    //突然发现更多的是这个schlick公式 而且感觉这个比较对
    //- -||可能我理解错原式的v h了?
    F0+(1-F0)*pow((1-dot(normal,view)),5);



#endif

2、微表面分布函数

我们使用书中的 backmann分布函数


同向异性:D_m(V,L)=\frac{1}{4m^2(N\cdot H)}*exp(\frac{(N\cdot H)^2-1}{m^2(N\cdot H)^2})

m表示粗糙度


各向异性:D_m(V,L)=\frac{1}{4m_xm_y(N\cdot H)}*exp[(\frac{(T\cdot P)^2}{m_x^2}+\frac{1-(T\cdot P)^2}{m_y^2})\frac{(N\cdot H)^2-1}{(N\cdot H)^2}]

m表示二维粗糙度向量,T表示表面的切向量,P是中间向量H到切平面的规范化投影

P=\frac{H-(N\cdot H)N}{\left \| H-(N\cdot H)N \right \|}

当mx=my时,该式等价于同向异性公式。


在顶点的切线空间计算微平面分布值会很方便,因为在切线空间中,法向量为(0,0,1),切向量为(1,0,0)公式可简化为:D_m(V,L)=\frac{1}{4m_xm_yH_Z}*exp[(\frac{P_X^2}{m_x^2}+\frac{1-P_X^2}{m_y^2})\frac{H_Z^2-1}{H_Z^2}]

    //Normal Distribution Function
    //in Tangent Space
    float tangentNDC(float2 roughness,float3 normal,float3 tangent,float3 halfDir)
    {
        float3 P=normalize(halfDir-dot(halfDir,normal)*normal);
        
        float px2=P.x*P.x;
        float hz2=halfDir.z*halfDir.z;
        float2 r2=roughness*roughness;
        return 0.25/(roughness.x*roughness.y*halfDir.z)*exp((px2/r2.x+(1-px2)/r2.y)*(hz2-1)/hz2);
    }

3 、几何衰减系数

光线在反射过程中会受到其余微平面的遮挡,共有两种情况(图我是偷来的( ̄ε(# ̄),第三种是反射了两次的,不用管)

UnityShader BRDF的简易实现_第3张图片

一是入射光线在射向目标微平面是受到路径中其余微平面的遮挡

G_1=\frac{2(N\cdot H)(N\cdot L)}{L\cdot H}

二是反射光线在射向观察方向时受到微平面的遮挡。

G_2=\frac{2(N\cdot H)(N\cdot V)}{V\cdot H}

取最小值作为几何衰减系数

G(V,L)=\min \{1,\frac{2(N\cdot H)(N\cdot L)}{L\cdot H},\frac{2(N\cdot H)(N\cdot V)}{V\cdot H}\}

    float GeometricShadowing(float3 normal,float3 lightDir,float3 viewDir)
    {
        float3 halfDir=normalize(lightDir+viewDir);
        
        float3 nh=dot(normal,halfDir);
        float3 nl=dot(normal,lightDir);
        float3 lh=dot(lightDir,halfDir);
        float3 nv=dot(normal,viewDir);
        float3 vh=dot(viewDir,halfDir);
        
        float g1=2*nh*nl/lh;
        float g2=2*nh*nv/vh;
        
        return min(1,min(g1,g2));
    }

4 BRDF Shader

Shader "Custom/MyBRDF"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _smoothnessX("smoothnessX", Range(0,0.99)) = 0.5
        _smoothnessY ("smoothnessY", Range(0,0.99)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Fresnel("Fresnel",Range(0,1))=0.5
    }
    SubShader
    {
        Pass
        {
            Tags{"RenderType"="ForwardBase"}
            CGPROGRAM
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "Assets/Shader/Library/MyBRDFterm.cginc"
            #pragma vertex vert
            #pragma fragment frag
            
            #define PI 3.1415926
            struct a2v{
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float3 tangentViewDir:TEXCOORD0;
                float3 tangentLightDir:TEXCOORD1;
            };
            
            float4 _Color;
            float _smoothnessX;
            float _smoothnessY;
            float _Metallic;
            float _Fresnel;
            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                //次切线
                float3 bitangent=cross(v.tangent.xyz,v.normal)*v.tangent.w;
                //模型-切线空间矩阵
                float3x3 rotation=float3x3(v.tangent.xyz,bitangent,v.normal);
                //转换光源方向和观察方向至切线空间
                o.tangentLightDir=mul(rotation,ObjSpaceLightDir(v.vertex));
                o.tangentViewDir=mul(rotation,ObjSpaceViewDir(v.vertex));
                return o;
            }
            
            float4 frag(v2f i):SV_Target{
                //不使用额外的法线贴图,切线空间下,N(0,0,1)T(1,0,0)
                float3 tangentNormal=float3(0,0,1);
                float3 tangentT=float3(1,0,0);
                float3 tangentViewDir=normalize(i.tangentViewDir);
                float3 tangentLightDir=normalize(i.tangentLightDir);
                float3 tangentHalfDir=normalize(tangentViewDir+tangentLightDir);
                
                fixed3 diffuse=_LightColor0.rgb*_Color*saturate(dot(tangentNormal,tangentLightDir));
                
                //菲涅尔
                float F=Fresnel(_Fresnel,tangentViewDir,tangentHalfDir);
                //微平面分布
                float2 roughness=float2(1-_smoothnessX,1-_smoothnessY);
                float D=tangentNDC(roughness,tangentNormal,tangentT,tangentHalfDir);
                //几何衰减
                float G=GeometricShadowing(tangentNormal,tangentLightDir,tangentViewDir);
                
                fixed3 specular=_LightColor0.rgb*F*D*G/(PI*dot(tangentNormal,tangentViewDir)*dot(tangentNormal,tangentLightDir));
                
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*_Color;
                float3 finalCol=(1-_Metallic)*diffuse+_Metallic*specular+ambient;
                
                
                return float4(finalCol,1);
            }
            
            ENDCG
        }
    }
    FallBack "Diffuse"
}

参数的可配置范围可能不太合理,另外金属值高时,只会有高光,因为我只简单加了环境光懒得弄环境反射了

UnityShader BRDF的简易实现_第4张图片UnityShader BRDF的简易实现_第5张图片

UnityShader BRDF的简易实现_第6张图片UnityShader BRDF的简易实现_第7张图片

UnityShader BRDF的简易实现_第8张图片UnityShader BRDF的简易实现_第9张图片

 

 

你可能感兴趣的:(Unity,图形学)