iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 12
1
自我挑戰組

初見Unity Shader系列 第 12

看板、告示牌、Billboarding都是同一個東西

文章內使用Unity 2019 LTS

看板是一個遊戲中很常見的功能,讓一個2D平面永遠面向相機,常用在一些看起來重複模型相當多的背景上,例如星空、雲朵,給予玩家這些視覺效果,也可以減少效能的消耗。

目標

  • 實作一個看板功能

利用腳本控制

Unity完整的API函式庫,可以簡單的幾行(一行?!)程式碼完成這個功能:

using UnityEngine;

public class Billboard : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        // 直接呼叫LookAt()面向相機
        transform.LookAt(transform.position + Camera.main.transform.rotation * Vector3.forward);
    }
}

利用Shader控制

利用腳本可以簡單的一行把基本功能就做出來,但利用Shader就不一樣了,需要完整的去操控一些渲染的細節,並不是因為Shader的方法原理不一樣,只是Unity API將實作細節隱藏起來。

矩陣轉換

向量是製作遊戲的好朋友,遊戲中凡是位移、縮放、旋轉動畫、物體運動都與向量有關,想要改變向量位置的方法,如果還記得的話,叫做線性轉換,是線性代數中一個很重要的概念。

如果忘記了,或是第一次接觸想要知道甚麼是線性轉換的話,可以參考這部影片:

十分推薦這位大大的線性代數系列,我有很多觀念都是透過他的影片來重新認識的。

回到主題上,要利用Shader撰寫一個看板功能我們目標就是要找的一個矩陣可以將2D平面的頂點位置一直轉向相機。

既然我們是在一個三維世界,我們則需要一個3x3的矩陣來對應我們頂點向量

矩陣第一列(Column)的處理向量的x分量,第二列變換y分量,第三列變換z分量。我們知道矩陣的每一列都是由一組向量組成的,所以我們要找出三組向量所組成的矩陣。

我們的2D平面要面向相機,所以第一個向量就出來了:

z_axis = camera_position - center

看板永遠指向上方,所以:

y_axis = vector3(0, 1, 0)

最後,X分量的向量,我們可以直接利用叉積(cross product)(註1):

x_axis = cross_product(z_axis, y_axis)

註1: 叉積計算變數的前後順序會影響到答案!

這樣,我們所需要的向量收集全了,以下是看板功能的Shader:

Shader "Learning/Billboard" {
    Properties{
	    _MainTex("Main Tex", 2D) = "white" {}
	    _Color("Color Tint", Color) = (1, 1, 1, 1)
    }
    
    SubShader{
		
        // 兩個標籤
        // IgnoreProjector: True的話表示不受Projector元件影響
        // DiableBatching: 關閉批次處理是因為頂點會因為會需要對模型空間的頂點做處理,
        //                 Batching會導致資料遺失
        Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True"}

        Pass {
            Tags { "LightMode" = "ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata_base v) {
                v2f o;

                float3 center = float3(0, 0, 0);
                float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));

                float3 normal = viewer - center;
                float3 normalDir = normalize(normal);

                float3 upDir = float3(0, 1, 0);
                float3 rightDir = normalize(cross(upDir, normalDir));
                upDir = normalize(cross(normalDir, rightDir));

                float3 centerOffs = v.vertex.xyz - center;
                float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;

                o.pos = UnityObjectToClipPos(float4(localPos, 1));
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= _Color.rgb;

                return c;
            }

            ENDCG
        }
    }
}

Reference


上一篇
混和效果和一些我忘記的東西
下一篇
時間變數與2D動畫
系列文
初見Unity Shader30

尚未有邦友留言

立即登入留言