主題

【Unity Image Shader 學習筆記】表面掃描效果、用GL自定義Blit、取得深度與shader取得世界座標

%%鼠 | 2021-07-19 02:50:04 | 巴幣 516 | 人氣 269

前言:
純粹好玩www

最終效果:

想直接拿程式的可以看這影片,資訊欄有他的成品:

以下是學習筆記: 完整筆記可以看Notion

單物件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)
URP是要掛載方法:
RenderPipelineManager.endCameraRendering += EndCameraRendering;
....
void EndCameraRendering(ScriptableRenderContext context, Camera camera)...
這邊為了方便,用SRP。

如何取得深度:
C#:
Shader
uniform  sampler2D_float _CameraDepthTexture;
uniform要加不加都可以,加上去單獨是設成唯讀,好提醒自己這個值是由系統提供的。

如果單獨輸出 tex2D(_CameraDepthTexture, i.uv); 畫面有可能是全黑的,別緊張,因為這深度資訊並不是線性的,把Camera Clipping near planes調高就行。

深度資訊存在R頻道
深度只是一個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.(,裡面有其他深度圖可以選)

線性深度

DecodeFloatRG
把兩個通道解碼成一個float。
DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv)) 等於 tex2D(_CameraDepthTexture, i.uv).rg

Screen方向
因為Image Shader作用在整個畫面上,可以把輸出的Screen想成一個大平面,vertex segment宣告的TEXCOORD就像uv座標。

目前 i.interpolatedRay就單純只是uv座標,等等會給他值。

畫圈
完整程式碼連結
不搭配C#畫面只會一片白。

C#:
完整程式碼連結,以下是學習筆記。

原本的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設定。
問題:
  1. GL.MultiTexCoord(1, bottomLeft);明顯超出0~1的範圍,為甚麼return i.interpolatedRay仍能顯示(0,0,0)~(1,1,1) ?
  2. frag會進行插值但為何是明顯的四塊顏色?


總結:
後製的image effect shader只是一個帶著RenderTexture的大Quade,搭配Depth的值,去算出該RenderTexture每個frag的與camera的距離,C#的功能則是帶進額外資料,例如camera視錐體的far plane座標,讓shader的linerDepth去計算出距離。


送禮物贊助創作者 !
0
留言

創作回應

蜥智(CizaHuang)
好酷
2021-07-19 10:48:37
%%鼠
沒想到能讓每個frag帶有自己的深度[e16]
2021-07-19 16:40:54
is樂小呈
比較泛用啊,image 就是純計算圖案的,compute 就是用來計算各種東西,圖案或其他有的沒的數學都行
2021-07-19 16:43:23
is樂小呈
他不具有材質之類的屬性,就是一個專門計算的腳本,可以輸入或輸出更彈性的數據
2021-07-19 16:45:11
%%鼠
他的執行順序是image之前還是之後?
2021-07-19 16:51:45
is樂小呈
應該是在他之前? compute shader 是要主動調用他計算的,Raymarching 也是先算出結果再讓 image shader 畫在畫面
2021-07-19 16:56:00
%%鼠
[補充]
Shader有個宣告prefix:uniform(統一值),說明這輸入值必須是一樣的,且唯獨。例如,CPU會把一個畫面拆成很多小區塊給不同thread的GPU執行,有些值必須一樣才能render出完整正確的畫面,像是u_time (時間), u_resolution (畫布尺寸)和 u_mouse (滑鼠位置)
https://thebookofshaders.com/03/?lan=ch&fbclid=IwAR00f9OXYhGTCX1isplo6BRx2NY0aCOeEEBuURqFrLDUazEesFH_nfWCH-Y
2021-07-20 00:25:26
追蹤 創作集

作者相關創作

更多創作