使用深度圖重構(gòu)世界空間坐標(biāo)

如果我們知道世界空間相機(jī)的坐標(biāo),并且使用cam.depthTextureMode = DepthTextureMode.Depth;讓相機(jī)能夠得到屏幕深度圖的話,那么我們實際上就能夠用這個兩個要素在后處理中重構(gòu)出(已寫入深度緩存的)片元在世界空間中的坐標(biāo)。

不影響透明物體

動態(tài)效果

用這樣的方法,我們可以做一個類似Projector在地表投影的效果,但是只用到了一個腳本和一個Shader。

重構(gòu)世界空間坐標(biāo)

看網(wǎng)上的教程學(xué)了一招,那就是我們可以自己寫一個自定義的Blit方法,給要創(chuàng)建的Quad送入額外的TexCoord。我們剛才提到可以用世界空間相機(jī)坐標(biāo)和深度圖重構(gòu)片元坐標(biāo),但是我們還需要一個方向向量:從相機(jī)原點(diǎn)到Blit中創(chuàng)建的Quad上的點(diǎn)的方向。因此,我們需要在自定義的Blit方法中為要創(chuàng)建的Quad的頂點(diǎn)添加上方向的數(shù)據(jù),具體計算用到cam.transform的幾個和方向有關(guān)的成員,以及相機(jī)FOV和屏幕長寬比例。

計算好四個頂點(diǎn)的方向向量后,使用GL.Begin(GL.QUADS)在屏幕上畫一個Quad并傳入UV和方向數(shù)據(jù),經(jīng)過插值后,我們便能在fragment shader中獲取每個片元正確的方向向量(注意這里不能對方向向量normalization,否則計算世界坐標(biāo)會出一定偏差)。以下是自定義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,用選擇顏色選擇黑色刪除留下透明部分,然后對圖層2進(jìn)行高斯模糊模擬發(fā)光效果。因為發(fā)光效果對圖像有一定外擴(kuò),在開始時要把畫布大小設(shè)置為110%。

處理前后

Shader 很簡單,計算得到世界空間坐標(biāo)后取xz分量和傳入的效果中心xz做計算即可得到對紋理采樣的UV,我還加入了一個旋轉(zhuǎn)效果。采樣到的值用于把顏色lerp到輸入的_TintColor到白色,同時也代表加性發(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]屬性即可,這會讓這個后處理效果在透明物體隊列渲染開始之前其作用。

其他

類似的我們可以用這種方法做爆炸范圍指示器之類的需要改變場景可視物體表面的效果。這里距離計算直接取的重構(gòu)坐標(biāo)的距離和爆炸中心的距離,因此實際上會有種球的效果。


不過作為爆炸指示器的話,站在掩體后應(yīng)該是不能被炸到的,如果這時候想更精確的指示爆炸,那么應(yīng)該要把掩體后的效果消除,而直接計算距離是達(dá)不到這種效果的。


這個暫時沒想到太好的辦法,覺得比較可行的是在中心放一個點(diǎn)光源然后讀取其陰影圖(待完成)。


Github: https://github.com/techizgit/UnityPlayground

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容