1. 程式人生 > >【unity shader】高階光照 --- 薄膜干涉

【unity shader】高階光照 --- 薄膜干涉

-光照模型是shader程式設計的核心與基礎。 一般的光照模型–不管是lambert還是phong–其實都是對現實光照的模擬。

但是現實中的光照效果要複雜得多。但就光的反射而言, 薄膜干涉就是一種非常常見的高階光照效果。

什麼是薄膜干涉?

薄膜干涉的常見例子可以是陽光下的肥皂泡, 又或者是一張光碟

foam bubble

關於薄膜干涉的原理,可以參考wiki上的介紹 【https://en.wikipedia.org/wiki/Thin-film_interference

如圖所示,光線進入薄膜時,在薄膜的上下表面均產生反射,由於反射光線走過的路徑不同,就產生了一個光程差。一般,當光程差正好等於光線波長的整數倍時候,反射光線就彼此增強,當光程差等於波長的整數倍又半波長時,光線就彼此抵消。

光程差與波長有關,又與光線的入射角度相關,所以我們就看到了陽光下的肥皂泡呈現出紅綠藍的光譜,並且隨著觀察角度的不同而不斷變化。

薄膜干涉的shader程式設計

  • 利用Ramp貼圖模擬薄膜干涉效果

薄膜干涉的shader模擬,在NVIDIA的網站教程中有一個範例。對一艘外星UFO施以薄膜干涉,效果是這樣滴。
UFO
網上有人將這個demo轉到了unity shader中,畫風有些不一樣了。
UFO
具體差異從何而來先不管,這個shader的實現原理就是計算光程差,以此為座標對一副Ramp貼圖取樣,從而模擬光譜效果。實際觀感還有賴於其他手段優化。
Ramp Tex
完整的shader程式碼如下:

Shader "thinfilm2"
{ Properties { _MainTex ("Texture", 2D) = "white" {} _Ramp ("Shading Ramp", 2D) = "gray" {} _SurfColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1) _SpecExpon ("Spec Power", Range (0, 125)) = 12 _FilmDepth ("Film Depth", Range (0, 1)) = 0.05 } SubShader { Tags { "RenderType"
= "Opaque" } CGPROGRAM #pragma surface surf Ramp sampler2D _Ramp; float _SurfColor; float _SpecExpon; float _FilmDepth; half4 LightingRamp (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) { half3 Hn = normalize (lightDir + viewDir); half ndl = dot (s.Normal, lightDir); half ndh = dot (s.Normal, Hn); half ndv = dot (s.Normal, viewDir); float3 diff = max(0,ndl).xxx; float nh = max (0, ndh); float3 spec = pow (nh, _SpecExpon).xxx; //*viewdepth即光程差,這裡用光在薄膜中行程長度近似。* float viewdepth = _FilmDepth/ndv*2.0; half3 ramp = tex2D (_Ramp, viewdepth.xx).rgb; half4 c; c.rgb = (s.Albedo*_SurfColor * diff + ramp * spec) *(atten); c.a = s.Alpha; return c; } struct Input { float2 uv_MainTex; half3 viewDir; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
  • 模擬光程差公式
    薄膜干涉的光程差公式很簡單,已知光的波長,折射率以及薄膜厚度,即可求解。
    當光程差等於波長的整數倍又半波長時,反射加強;
    這裡寫圖片描述
    當光程差等於半波長的整數倍,反射抵消。
    這裡寫圖片描述
    我們利用三角函式來實現反射的週期特性,同時針對紅綠藍光設定不同的波長和折射率,這樣就模擬出了一個實時的薄膜干涉效果
    這裡寫圖片描述
    相應的shader程式碼如下:
Shader "Unlit/thinfilm_Unlit_IOR"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    _FilmDepth("film thickness", Range(1, 2000)) = 500.0
        _IOR("refraction index", Vector) = (0.9, 1.0, 1.1, 1.0)
    }
        SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100

        Pass
    {
        CGPROGRAM
#pragma vertex vert
#pragma fragment frag
        // make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"
#include "Lighting.cginc"
        struct appdata
    {
        float4 vertex : POSITION;
        float4 normal: NORMAL;
        float2 uv : TEXCOORD0;
    };

    struct v2f
    {
        float3 worldNormal : TEXCOORD0;
        float3 worldPos : TEXCOORD1;
        float2 uv : TEXCOORD2;
        UNITY_FOG_COORDS(1)
            float4 vertex : SV_POSITION;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;
    Vector _IOR;


    fixed thinFilmReflectance(fixed cosI, float lambda, float thickness, float IOR)
    {
        float PI = 3.1415926;
        fixed sin2R = saturate((1 - pow(cosI, 2)) / pow(IOR,2));
        fixed cosR = sqrt(1 - sin2R);
        float phi = 2.0*IOR*thickness*cosR / lambda + 0.5; //計算光程差
        fixed reflectionRatio = 1 - pow(cos(phi * PI*2.0)*0.5+0.5, 1.0);  //反射係數

        fixed  refRatio_min = pow((1 - IOR) / (1 + IOR), 2.0);

        reflectionRatio = refRatio_min + (1.0 - refRatio_min) * reflectionRatio;

        return reflectionRatio;
    }

    v2f vert(appdata v)
    {
        v2f o;

        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
        // Transform the normal fram object space to world space
        o.worldNormal = mul(v.normal, (float3x3)_World2Object);
        // Transform the vertex from object spacet to world space
        o.worldPos = mul(_Object2World, v.vertex).xyz;


        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        UNITY_TRANSFER_FOG(o,o.vertex);
        return o;
    }


    float _FilmDepth;

    fixed4 frag(v2f i) : SV_Target
    {
        fixed3 worldNormal = normalize(i.worldNormal);
        fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
        // Get the view direction in world space
        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
        // Get the reflect direction in world space
        fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
        fixed3 H = normalize(viewDir + worldLightDir);

        fixed ndl = max(0,dot(worldNormal, worldLightDir));
        fixed hdn = dot(worldNormal, H);
        //fixed ndv = max(0, dot(worldNormal, viewDir));
        fixed rdv = max(0, dot(reflectDir, viewDir));
        // sample the texture
        fixed4 albedo = tex2D(_MainTex, i.uv);

        fixed ref_red = thinFilmReflectance(hdn, 650.0, _FilmDepth, _IOR.r); //紅光
        fixed ref_green = thinFilmReflectance(hdn, 510.0, _FilmDepth, _IOR.g); //綠光
        fixed ref_blue = thinFilmReflectance(hdn, 470.0, _FilmDepth, _IOR.b); //藍光
        fixed4 tfi_rgb = fixed4(ref_red, ref_green, ref_blue, 1.0);

        fixed4 col = albedo*tfi_rgb*ndl*0.35 +tfi_rgb * pow(rdv, 25.);
        col.a = 1.0;
        // apply fog
        //UNITY_APPLY_FOG(i.fogCoord, col);
        return col;
    }
        ENDCG
    }
    }
}
  • fresnel(菲涅爾)方程計演算法
    菲涅爾方程(wiki),這個比較高端了,是光學中的經典公式,用於描述光(或者一切電磁波)在不同介質表面的折射反射。因此用菲涅爾方程不僅能模擬光的薄膜干涉,還能模擬包括色散等更復雜的光學效應。關於菲涅爾方程的程式設計,完全參照網上一篇博文。最終的實現效果也很真實,而且還自帶了很漂亮的rim light。

這裡寫圖片描述

參考閱讀

【1】