菲涅尔反射及其应用

1,067 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,[点击查看活动详情]

前言

不知小伙伴们是否有过这样的场景?站在湖边向脚下看时,湖面的水是清澈的,甚至可以看清湖底的沙石,而如果向远方望去,却只能看到远处的倒影。此种光学现象称之为菲涅尔反射。本篇不会详细阐述菲涅尔方程的推导过程,而是注重其在实际开发中的应用。

image.png

菲涅尔反射

前言中的例子放在专业术语里可以这样来解释:当光由一种介质进入另一种不同的介质时,在物体表面会发生反射,另一部分进入物体内部发生折射或散射,反射的光和入射光之间存在着一种比率,而这种比率可以用菲涅尔方程来进行描述。不过由于原始的菲涅尔方程十分复杂,所以一般在实时渲染中,会使用Schlick菲涅尔近似等式来替代:

F = max(0,min(1,bias + scale X (1-v·n)^power))

其中的bias、scale、power都是控制项,可以简单理解为一个自己设置的常数项。由此等式可以知道对反射光与入射光的比率的求解,进而演化成了对视线方向v与物体表面的法向量之间夹角的余弦值的求值,即点乘结果。视线方向和物体表面的法向量在之前的文章中多次用到,所以复杂的问题变简单了。

image.png

菲涅尔单一效果

在将菲涅尔反射的效果与漫反射之类的其他颜色混合之前,先来看一下单纯使用公式来得到的效果是怎么样的。

  • 1、在Project面板右键创建一个Unlit Shader,将其命名为Frenel。

image.png

  • 2、选中刚刚创建的Frenel,右键创建一个Material。

image.png

  • 3、在Hierarchy面板创建一个Sphere,并将第2步创建的Material拖拽赋值到Sphere上。

image.png

  • 4、使用脚本IDE打开shader文件,添加如下代码。
Shader "Unlit/Fresnel"
{
    Properties
    {
        _Power("Power",Range(0,10)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            float _Power;

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 posWS : TEXCOORD0;
                float3 nDirWS : TEXCOORD1;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                o.nDirWS = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 nDirWS = i.nDirWS;
                float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
                //菲涅尔公式
                float fresnel = pow((1.0 - dot(nDirWS,vDirWS)),_Power);
                return fixed4(fresnel,fresnel,fresnel,1.0);
            }
            ENDCG
        }
    }
}

  • 5、在Material的Inspector面板调整Power的值,查看效果。

image.png

与漫反射、环境反射结合

根据视线方向与法向量之间的点乘结果与其他效果混合,可以根据自己的实际需求灵活应用。这里以一种效果作为展示,修改上面的shader脚本:

Shader "Unlit/Fresnel"
{
    Properties
    {
        _Power("Power",Range(0,10)) = 0.5
        _Cubemap("CubeMap",Cube) = "_Skybox"{}
        _EvnSpeculatInt("环境反射强度",Range(0,5)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            float _Power;
            float3 _Color;
            samplerCUBE _Cubemap;
            float _EvnSpeculatInt;

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

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 posWS : TEXCOORD0;
                float3 nDirWS : TEXCOORD1;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                o.nDirWS = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 nDirWS = i.nDirWS;
                float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
                float3 vrDirWS = reflect(-vDirWS,nDirWS);
                float3 cubeMapColor = texCUBE(_Cubemap,vrDirWS).rgb;
                //菲涅尔公式
                float fresnel = pow((1.0 - dot(nDirWS,vDirWS)),_Power);
                float3 envSpecLighting = cubeMapColor * fresnel * _EvnSpeculatInt;
                return fixed4(envSpecLighting,1.0);
            }
            ENDCG
        }
    }
}

最终效果:

image.png