Billboards 技術在Unity 中的幾種使用方法

關於billboards技術,原理就是計算出來一個始終朝向攝像機的面片,可以在CPU里計算,也可以在GPU里實現。應用的場合很多:

  • 遊戲角色的頭頂文字,血條
  • 場景的樹,草
  • 特效粒子片
  • 3d場景里的2d角色

world space計算

思路:shader中傳入面片的中心點的世界坐標,以及攝像機的right和up在world space的方向,中心點直接沿著right和up方向計算四個頂點的世界坐標,定點數據中包含了每個頂點的偏移信息。然後乘以ViewProjection矩陣,作為輸出。

這裡有個trick的地方,就是從object space 到world space是沒有旋轉的,只有偏移,所以攝像機在世界空間的right就是模型空間的right。MV的逆矩陣相當於View到Object的變換,轉置是為了去列向量好取。

CGPROGRAM #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float2 vertexOffset : TEXCOORD1; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; v2f vert (appdata v) { v2f o; float3 right = UNITY_MATRIX_IT_MV[0].xyz; float3 up = UNITY_MATRIX_IT_MV[1].xyz; v.vertex.xyz += v.vertexOffset.x * right + v.vertexOffset.y * up; o.vertex = mul(UNITY_MATRIX_VP, float4(v.vertex.xyz, 1.0)); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG

unity c#中生成mesh的代碼

Vector3[] vertices = new Vector3[4] { worldPos, worldPos, worldPos, worldPos }; int[] indices = new int[6] { 0, 2, 1, 0, 3, 2 }; Vector2[] uvs = new Vector2[4] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0), }; Vector2[] uv2s = new Vector2[4] { new Vector2(-0.5f, -0.5f), new Vector2(0.5f, -0.5f), new Vector2(0.5f, 0.5f), new Vector2(-0.5f, 0.5f), }; meshFilter.mesh.vertices = vertices; meshFilter.mesh.triangles = indices; meshFilter.mesh.uv = uvs; meshFilter.mesh.uv2 = uv2s;

clip space計算

思路:還是傳入中心點的世界坐標,以及4個頂點的偏移信息。把中心點轉到

clip space,然後按照偏移信息縮放。這個適用的場合是面片不隨距離攝像機的遠近而縮放。

CGPROGRAM #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float2 vertexOffset : TEXCOORD1; }; struct v2f { float2 uv : TEXCOORD0; float3 color : TEXCOORD1; float4 vertex : SV_POSITION; }; sampler2D _MainTex; v2f vert (appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.vertex.xyz /= o.vertex.w; o.vertex.xy += v.vertexOffset.xy * float2(0.2, 0.05); o.vertex.w = 1; o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG

view space計算

上面兩種方式都是通過傳入中心點坐標到shader進行計算,但是有時候並不能獲得面片的中心點坐標,比如Unity中的SpriteRenderer,這時候的思路是:把中心點即(0,0,0,1)(Object space)轉到View Space,然後頂點再做偏移。

o.pos = mul(UNITY_MATRIX_P, mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0)) + float4(i.vertex.x, i.vertex.y, 0.0, 0.0));

CPU計算

思路:面片的位置發生變化,或者攝像機發生變化的時候,重新計算一下面片的旋轉,讓它始終朝向攝像機

private void CalcBillboards() { if(instance != null) { instance.transform.rotation = Camera.main.transform.rotation; } }

參考資料

  • Unity wiki的實現
  • opengl實現billboards

推薦閱讀:

300行代碼實現Minecraft(我的世界)大地圖生成
【Unity】工具類系列教程—— 代碼自動化生成!
幻影坦克架構指南(二)
GPU Gems 基於物理模型的水面模擬 學習筆記 (一)
Unity特效(1) 夢幻旋屏

TAG:Unity游戏引擎 | 计算机图形学 | 3D渲染 |