iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 8
0
自我挑戰組

初見Unity Shader系列 第 8

高光反射與Blinn-Phong Shading

文章使用Unity 2019 LTS

目標

  • 甚麼是經典模型
  • Blinn-Phong Shading

經典模型

首先要說明的一點,這邊的模型並不是指遊戲中我們常說的角色或場景物件的模型,這裡指的是數學中用來簡化系統的方式,數學模型

在現實生活中,計算「光」是複雜的。為了在電腦中呈現出光照射在物體的模樣,提出了標準光源模型,標準光源模型並不是真實反映實際與光的互動,這些模型則被稱為 「經驗模型」,尤其是在早期的遊戲上,有許多技術上的限制。

經驗模型的方法是將光線進入到攝影機的地方分成4個部分:

  • 自發光 (emissive):若在不使用全域光照的情況下,對於自發光的處理就是不經過處理的純色塊,以達到讓物體看起來更亮的效果。
  • 環境光 (ambient):現實生活中,空間不可能是全暗的。例如其他物體被光照射到後,反射出該物體顏色的光。
  • 漫射光 (diffuse):光線照到物體時所呈現的顏色。
  • 高反射光 (specular):金屬或一些光滑表面,會產生鏡面反射。

高光反射

在漫反射中,我們使用過環境光以及漫射光,這裡順序來介紹高光反射的部分,也就是模擬表面像是金屬反射的地方。

圖(1)描述了高光反射處是怎麼形成的,n為物體表面的法向量,l為光源方向,v為玩家看向物體的方向,r是根據法向量與光源反射出的方向。


圖(1)

我們需要利用反射向量r與視角方向v之間的夾角來計算出高光反射。

開始撰寫!

漫反射光中,我們了解到計算可以是在vertex shader或是fragment shader,計算高反射光的模型也是一樣可以在不同的處理階段進行計算,而且在不同地方計算的模型也是有名字的,計算方法寫在vertex shader的叫做Gouraud shading;fragment shader的叫做Phong shading。

以下要講解的是Phong shading的改良版,Blinn-Phong shading,如果想更加了解Phong shading可以參考這篇文(全英文注意),我在下面的範例也會附上Phong的作法。

Blinn-Phong Shading

Blinn-Phong與Phong不同的地方在於Blinn沒有使用反射的方向,而是利用光源方向與視角方向相加後的向量h,再與其表面的法向量形成的夾角進行高光效果,好處在於減少了對於反射光夾角的計算。


圖(2)

Shader "Learning/Blinn-Phong"
{
    Properties
    {
        _Diffuse("Diffuse Color", Color) = (1, 1, 1, 1)
        // 高光反射的顏色
        _Specular ("Specular Color", Color) = (1, 1, 1, 1)
        // 這裡的gloss表示反射光亮點的大小
        _Gloss ("Gloss", Range(8.0, 256.0)) = 32.0
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal : NORMAL;
                // 需要將物件的頂點由物件空間轉至世界空間
                float3 worldPos : TEXCOORD0;
            };

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex.xyz);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;

                fixed3 worldNormalDir = normalize(i.normal);

                fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormalDir, worldLightDir));
                
                // ----Blinn-Phong的作法---
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                fixed3 halfDir = normalize(worldLightDir + viewDir);

                // 記得Gloss變數不可為0
                // 讀者們可以試試看變為0的效果如何
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormalDir, halfDir)), _Gloss);
                // ---Blinn-Phong的作法結束---
                
                // ---Phong的作法---
                // fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormalDir));
                //
                // fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                //
                // fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);        
                // 
                // ---Phong的作法結束---

                return fixed4(ambient + diffuse + specular, 1);
            }
            ENDCG
        }
    }

    Fallback "Specular"
}

圖(3)為Blinn-Phong的結果


圖(3) Blinn-Phong Shading

把上面範例的Phong區塊開起來,可以看到Phong的結果 圖(4)

圖(4) Phong Shading

將Phong與Blinn-Phong兩個相互比較會發現,同樣的光澤度(Gloss),Blinn-Phong的高光區會看起來更大更亮。

最後但最重要

雖說Blinn-Phong是Phong的改良版,但這並不表示Blinn-Phong是「比較正確」的。如同在電腦圖學領域中一句有名的話:「如果他看起來是對的,那他就是對的

來源參考


上一篇
Unity Frame Debugger
下一篇
紋理貼圖,又或是叫做Texture
系列文
初見Unity Shader30

尚未有邦友留言

立即登入留言