如果我們知道世界空間相機(jī)的坐標(biāo)庄涡,并且使用cam.depthTextureMode = DepthTextureMode.Depth;
讓相機(jī)能夠得到屏幕深度圖的話胰柑,那么我們實(shí)際上就能夠用這個(gè)兩個(gè)要素在后處理中重構(gòu)出(已寫入深度緩存的)片元在世界空間中的坐標(biāo)。
用這樣的方法,我們可以做一個(gè)類似Projector在地表投影的效果踏堡,但是只用到了一個(gè)腳本和一個(gè)Shader醋旦。
重構(gòu)世界空間坐標(biāo)
看網(wǎng)上的教程學(xué)了一招恒水,那就是我們可以自己寫一個(gè)自定義的Blit方法,給要?jiǎng)?chuàng)建的Quad送入額外的TexCoord饲齐。我們剛才提到可以用世界空間相機(jī)坐標(biāo)和深度圖重構(gòu)片元坐標(biāo)寇窑,但是我們還需要一個(gè)方向向量:從相機(jī)原點(diǎn)到Blit中創(chuàng)建的Quad上的點(diǎn)的方向。因此箩张,我們需要在自定義的Blit方法中為要?jiǎng)?chuàng)建的Quad的頂點(diǎn)添加上方向的數(shù)據(jù)甩骏,具體計(jì)算用到cam.transform
的幾個(gè)和方向有關(guān)的成員,以及相機(jī)FOV和屏幕長(zhǎng)寬比例先慷。
計(jì)算好四個(gè)頂點(diǎn)的方向向量后饮笛,使用GL.Begin(GL.QUADS)
在屏幕上畫一個(gè)Quad并傳入U(xiǎn)V和方向數(shù)據(jù),經(jīng)過(guò)插值后论熙,我們便能在fragment shader中獲取每個(gè)片元正確的方向向量(注意這里不能對(duì)方向向量normalization福青,否則計(jì)算世界坐標(biāo)會(huì)出一定偏差)。以下是自定義Blit的腳本:
void CustomBlit(RenderTexture source, RenderTexture dest, Material mat)
{
float camFov = cam.fieldOfView;
float camAspect = cam.aspect;
float fovWHalf = camFov * 0.5f;
Vector3 toRight = cam.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) * camAspect;
Vector3 toTop = cam.transform.up * Mathf.Tan(fovWHalf * Mathf.Deg2Rad);
Vector3 topLeft = (cam.transform.forward - toRight + toTop);
Vector3 topRight = (cam.transform.forward + toRight + toTop);
Vector3 bottomRight = (cam.transform.forward + toRight - toTop);
Vector3 bottomLeft = (cam.transform.forward - toRight - toTop);
RenderTexture.active = dest;
mat.SetTexture("_MainTex", source);
GL.PushMatrix();
GL.LoadOrtho();
mat.SetPass(0);
GL.Begin(GL.QUADS);
GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.MultiTexCoord(1, bottomLeft);
GL.Vertex3(0.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.MultiTexCoord(1, bottomRight);
GL.Vertex3(1.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.MultiTexCoord(1, topRight);
GL.Vertex3(1.0f, 1.0f, 0.0f);
GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.MultiTexCoord(1, topLeft);
GL.Vertex3(0.0f, 1.0f, 0.0f);
GL.End();
GL.PopMatrix();
}
}
紋理處理和Shader
紋理是網(wǎng)上隨便找了一張jpeg魔法陣脓诡,Photoshop中顏色反相后復(fù)制背景圖層到圖層2无午,用選擇顏色選擇黑色刪除留下透明部分,然后對(duì)圖層2進(jìn)行高斯模糊模擬發(fā)光效果祝谚。因?yàn)榘l(fā)光效果對(duì)圖像有一定外擴(kuò)宪迟,在開(kāi)始時(shí)要把畫布大小設(shè)置為110%。
Shader 很簡(jiǎn)單交惯,計(jì)算得到世界空間坐標(biāo)后取xz分量和傳入的效果中心xz做計(jì)算即可得到對(duì)紋理采樣的UV次泽,我還加入了一個(gè)旋轉(zhuǎn)效果。采樣到的值用于把顏色lerp到輸入的
_TintColor
到白色席爽,同時(shí)也代表加性發(fā)光強(qiáng)度意荤。以下是fragment shader的代碼:
half4 frag (v2f i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
float rawDepth = tex2D(_CameraDepthTexture, i.uv_depth);
float linearDepth = LinearEyeDepth(rawDepth);
float3 surfacePos = _WorldSpaceCameraPos + (linearDepth * i.interpolatedRay).xyz;
half4 effectCol = half4(0, 0, 0, 0);
float dist = distance(surfacePos.xz, _WorldSpaceEffectPos.xz);
float s = 0; float c = 0;
float angle = atan2((surfacePos.x - _WorldSpaceEffectPos.x),(surfacePos.z - _WorldSpaceEffectPos.z));
angle += _Time.x * _SpinningSpeed;
sincos(angle, s ,c);
float2 surfaceUV = (float2(s * dist , c * dist) / _Radius + 1) * 0.5;
float effectFactor = tex2D(_DetailTex, float2(surfaceUV));
if (dist < _Radius ) effectCol = lerp(_TintColor, float4(1,1,1,1) ,effectFactor) * effectFactor;
return col + effectCol;
}
不影響透明物體
在OnRenderImage
前加入[ImageEffectOpaque]
屬性即可,這會(huì)讓這個(gè)后處理效果在透明物體隊(duì)列渲染開(kāi)始之前其作用只锻。
其他
類似的我們可以用這種方法做爆炸范圍指示器之類的需要改變場(chǎng)景可視物體表面的效果玖像。這里距離計(jì)算直接取的重構(gòu)坐標(biāo)的距離和爆炸中心的距離,因此實(shí)際上會(huì)有種球的效果齐饮。
不過(guò)作為爆炸指示器的話捐寥,站在掩體后應(yīng)該是不能被炸到的,如果這時(shí)候想更精確的指示爆炸沈矿,那么應(yīng)該要把掩體后的效果消除上真,而直接計(jì)算距離是達(dá)不到這種效果的咬腋。
這個(gè)暫時(shí)沒(méi)想到太好的辦法羹膳,覺(jué)得比較可行的是在中心放一個(gè)點(diǎn)光源然后讀取其陰影圖(待完成)。