前言:
純粹好玩www
最終效果:
想直接拿程式的可以看這影片,資訊欄有他的成品:
單物件Shader取得世界座標
需求:之前做的
tile map的遊戲,美術說想要tile有破碎的感覺,他會出一張破碎的大圖(不是背景),在場上的tile就套用破碎感。為了保證相鄰的tile能取得連續的破碎感,需要讓tile能取得正確世界位置。
成品:
目前比較熟悉的Shader程度在撰寫個shader,讓單一物件能有特殊遮罩效果。
取得世界座標:
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; 將vertex轉換至世界座標
fixed4 noisy = tex2D(_NoiseTex , i.worldPos); 及可取得noisy圖的世界座標。
計算貼圖tiling 跟 offset的方法 TRANSFORM_TEX(v.uv, _MainTex); 需要該貼圖的uv,但我們沒有輸入遮罩貼圖的uv,也不想多浪費一個變數去記他。
TRANSFORM_TEX的定義是:
TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
之前
談過,"變數名稱_ST" 即可取得該貼圖的tiling跟offset的值,接下來手動算就好。
fixed4 noisy = tex2D(_NoiseTex , i.worldPos * _NoiseTex_ST.xy + _NoiseTex_ST.zw);
遮罩有兩種做法,一種比較簡單,遮罩圖片自帶透明度,輸出時只需乘上alpha即可。
但像雜訊圖僅有灰階資訊,該如何將灰階轉透明度呢?
像是這張perlin noise,rgb介於0~1,a為1。 (事後拿去檢色軟體看發現任一點r=g=b,所以才灰灰的)
大家都記得畢氏定理吧? 在2D的情況下,magnitude就是第三邊的長度。
先用2D來講,rg介於0~1,將r=x軸,g=y軸畫在座標圖上的話,剛好可以畫出個radious為1的圓。圓上任一點可以用(cosθ,sinθ)表示,從圓心到該點的距離就是rg的magnitude。假設rg為(0.5,0.5),則magnitude =0.707
使用rgb的magnitude當作alpha
fixed4 col = tex2D(_MainTex, i.uv);
col.a=saturate( length( noisy.rgb) );
照理說magnitude因介於0~1之間,但若圖上有個點rgb為 (1,1,1),則magnitude會超過1造成過曝,所以加個saturate去限制在0~1。
暖身練習結束~ 接下來是正經的depth buffer
Image Shader 和 Unlit Shader差在哪?
這次撰寫的Image Effect Shader,其內容跟Unlit Shader差不多,差在 Image Shader是後製,通常會搭配C#腳本。(
wiki)
上次做的
螢幕縮圈效果也是Image Shader,注意Stander Render Pipeline是呼叫
void OnRenderImage(RenderTexture src, RenderTexture dst)
RenderPipelineManager.endCameraRendering += EndCameraRendering;
void EndCameraRendering(ScriptableRenderContext context, Camera camera)...
這邊為了方便,用SRP。
如何取得深度:
uniform sampler2D_float _CameraDepthTexture;
uniform要加不加都可以,加上去單獨是設成唯讀,好提醒自己這個值是由系統提供的。
如果單獨輸出 tex2D(_CameraDepthTexture, i.uv); 畫面有可能是全黑的,別緊張,因為這深度資訊並不是線性的,把Camera Clipping near planes調高就行。
深度只是一個0~1的值,存放在R頻道。《如果直接拿來作為紋理使用的話,就是會由於只有r通道有數據而成為一片紅紅的樣子。 如果只取r值的話,就能顯示出淡淡的灰色,這是因為它是一種非線性空間中的深度值(Pixel values in the Depth Texture range between 0 and 1, with a non-linear distribution.)。》(
源)
要帶有深度及法線的深度圖,則可以這樣獲取:sampler2D _CameraDepthNormalsTexture;
它包含rgba四個通道,其中用rg兩個通道存儲來法線,ba存儲深度。 This builds a screen-sized 32 bit (8 bit/channel) texture, where view space normals are encoded into R&G channels, and depth is encoded in B&A channels. Normals are encoded using Stereographic projection, and depth is 16 bit value packed into two 8 bit channels.(
源,裡面有其他深度圖可以選)
把兩個通道解碼成一個float。
DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv)) 等於 tex2D(_CameraDepthTexture, i.uv).rg
因為Image Shader作用在整個畫面上,可以把輸出的Screen想成一個大平面,vertex segment宣告的TEXCOORD就像uv座標。
目前 i.interpolatedRay就單純只是uv座標,等等會給他值。
畫圈
完整程式碼連結,以下是學習筆記。
原本的Graphics.Blit(src, dest, effectMaterial); 是直接畫在render結果並輸出在螢幕上,現在我們需要先計算視錐體,分別取得far plane最四邊的世界座標位置,回傳shader去計算距離。
視錐體(源)
FOV
視線範圍,單位:角度。
Angle of view can be measured horizontally, vertically, or diagonally. (wiki)
計算四個角落
第67行: float fovWHalf = camFov * 0.5f;
取得半邊的fov角度。
第69、70行: Vector3 toRight = _camera.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) * camAspect;
_camera.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) 和 _camera.transform.up * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) 剛好是正方形上與右邊。
乘上camAspect才是畫面寬度。
tan(弧度) ? 得斜率
atan(斜率)? 得弧度
Camera.aspect ? 寬 / 高的比例
第73行 float camScale = topLeft.magnitude * camFar;
取得far plane的長度。(猜測?)
實際畫出topRight,topLeft,bottomRight,bottomLeft(綠色線條),發現並沒有貼在far plane上
UnityEngine.GL
Low-level graphics library。
詳解
GL.PushMatrix():保存Matrices至matrix stack上
GL.PopMatrix(): 從matrix stack上讀取matrices。
GL.LoadOrtho():設置ortho perspective,即水平視角。GL.Vertex3()取值范圍從左下角的(0,0,0)到右上角的(1,1,0)即需要除上Screen.width和Screen.height。
GL.Begin(int mode):繪製基本3D形狀(Begin drawing 3D primitives.)
GL.MultiTexCoord(int unit, float x, float y):unit=0代表main texture,1代表second texture。
unit要對照vertex架構的TextCoord編號輸入:
所以GL.MultiTexCoord(1, bottomLeft); 會把bottomLeft的值給ray
如果單純輸出 ray(標上rgb):
回頭看之前的
float4 wsDir = linerDepth * i.interpolatedRay;
float3 wsPos = _WorldSpaceCameraPos + wsDir;
linerDepth 範圍0~1,0=near plane,1=far plane
在VertIn架構的ray 是C#傳進來的camera far plane最四邊的世界座標。在轉成v2f架構的interpolatedRay時候會進行插值,變成far plane最左下至最右上的世界座標(猜測)。差值的強度看filter設定。
問題:
- GL.MultiTexCoord(1, bottomLeft);明顯超出0~1的範圍,為甚麼return i.interpolatedRay仍能顯示(0,0,0)~(1,1,1) ?
- frag會進行插值但為何是明顯的四塊顏色?
總結:
後製的image effect shader只是一個帶著RenderTexture的大Quade,搭配Depth的值,去算出該RenderTexture每個frag的與camera的距離,C#的功能則是帶進額外資料,例如camera視錐體的far plane座標,讓shader的linerDepth去計算出距離。