Unity Camera獲取深度數(shù)據(jù)的幾種方式

1. pipeline中的深度數(shù)據(jù)

深度是渲染管線當中的重要概念,它控制著三維世界中物體渲染的遮擋關(guān)系。我們以傳統(tǒng)的foward render為例,三角形提交draw call以后,頂點進入vertex shader,經(jīng)過model-view-projection matrix變換到齊次裁剪空間坐標系(又叫規(guī)則觀察體(Canonical View Volume)中,通過透視除法(硬件自動完成)進入pixel shader(當然,現(xiàn)代的gpu都會在進入pixel shader之前提供Early-Z kill,后文詳述)

z_ealy_z_流程.png

通過pixel shader上色以后,就使用到了本文的主角深度,深度測試階段(Z-test)所有頂點都處在NDC坐標系(也就是整個世界規(guī)范到xy = {-1, 1}, z = {0, 1}的長方體中),如下圖。

ndc_cvv_介紹.jpg

這么折騰一通,對于Z-test的意義就是所有的頂點position.z深度值都規(guī)范到{0,1}之間,這也就給比較遮擋關(guān)系提供了便利,距離越近的點才能渲染到render texture中,距離太遠的話當前像素就可以丟棄了。距離相機越近的點z值越小,假如我們使用如下的pixel shader來表示深度圖

    fixed4 frag (v2f i) : SV_Target
    {
        float depth = tex2D(_CameraDepthTexture, i.uv).r;
        float4 col = float4(depth, depth, depth, 1);
        return col
    }

由于rgb一致,所以深度圖都是以灰度圖呈現(xiàn)。


2. UNITY 獲取深度圖 -- camera的內(nèi)置depth texture

Camera可以生成depth texture, depth+normals texture,這些內(nèi)置數(shù)據(jù)可以用于延遲渲染以及shadow map,本文主要討論深度圖,其他概念暫且摁下不表。

獲取Camera內(nèi)置深度圖的介紹的比較多,demo可以參考這個例子,github需要翻墻,本文也使用這個場景做其他獲取方式的介紹。

內(nèi)置的深度圖名字為_CameraDepthTexture,我們在使用它的時候需要遵循如下流程

抓取內(nèi)置depth texture的獲取基本流程如下~

  1. 獲取深度圖需要指定Camera, 設(shè)置抓取深度圖的Camera.depthTextureMode = DepthTextureMode.Depth;
  2. 聲明一個rendertexture rt用于保存深度圖,rt = RenderTexture.GetTemporary(width, height, 0);
  3. 在Camera的Gameobj身上掛載一個script,重載 OnRenderImage方法
  4. OnRenderImage中 Graphics.Blit(source, dest, DepthRender);
  5. 保證場景中希望寫入深度圖的gameobj,它們上色的shader必須有 FallBack "Diffuse"代碼塊,否則深度圖不會將他的深度數(shù)據(jù)寫入

流程1說明需要向camera中保存depth tetxure到_CameraDepthTexture中。
流程2是保存深度圖的容器
當場景繪制完成以后,我們使用blit方法將數(shù)據(jù)拷貝到dest中去,請注意??!_CameraDepthTexture與blit(src,dest)中的src,dest位置順序沒有關(guān)系,_CameraDepthTexture中一直存在深度數(shù)據(jù),_MainTex的數(shù)據(jù)則只來自src
流程5是一個潛規(guī)則,原文是這樣的Depth texture is rendered using the same shader passes as used for shadow caster rendering (ShadowCaster pass type). 。

簡單來說內(nèi)置深度數(shù)據(jù)來自shadow map計算時使用的一個特殊pass,我們必須設(shè)置這個pass才能讓內(nèi)置depth buffer完成數(shù)據(jù)填充,FallBack "Diffuse"在這個時候就起到了作用,貌似自己寫的其他pass時不會在獲取內(nèi)置深度時調(diào)用。大坑一個,所以自己寫的shader結(jié)構(gòu)應(yīng)該保留FallBack "Diffuse"

Shader "Custom/yourshader" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader 
    {       
        Tags { "RenderType"="Opaque" }
        
        Pass
        {           
        }
    } 
    FallBack "Diffuse"
}

然后我們來看流程4的DepthRender,它的作用就是從_CameraDepthTexture獲取depth輸出給blit的dest,完整代碼如下

Shader "Unlit/depth_render"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "black" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            sampler2D _CameraDepthTexture;
            sampler2D _MainTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float4 col = float4(1, 1, 1, 1);

                // 內(nèi)置 深度獲取
                float depth = tex2D(_CameraDepthTexture, i.uv).r;
                col = float4(depth, depth, depth, 1 );
                return col;
            }
            ENDCG
        }
    }
}

其中獲取深度還可以使用unity的內(nèi)置宏SAMPLE_DEPTH_TEXTURE,一般情況下就是取texture的r分量即可
#define SAMPLE_DEPTH_TEXTURE(sampler, uv) (tex2D(sampler, uv).r)

tips -- 由于是image effect,我們關(guān)閉深度測試和裁剪**Cull Off ZWrite Off ZTest Always**, 關(guān)于內(nèi)置宏,請自行下載unity-build-in-shader查看

深度圖2.png

左邊是scene view 右邊是深度圖,我們可以得出這樣的結(jié)論

物體距離攝像機越近,內(nèi)置深度圖的深度值就越大(越接近1 - 白色)


3. UNITY 獲取深度圖 -- 創(chuàng)建一個專門的depth camera

上文使用的是unity的內(nèi)置方法獲取深度圖,那么我們通過自己的shader將vertex的z值寫入rt,當然就可以讓一個camera專門渲染深度了!!~~~

渲染深度數(shù)據(jù)的shader如下

Shader "Unlit/deptVisual"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag           
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.vertex.z, i.vertex.z, i.vertex.z, 1);
            }
            ENDCG
        }
    }
}

我們看一下渲染結(jié)果


深度圖3.png

但是這個方法帶來一個問題,難道我們?yōu)榱双@取深度數(shù)據(jù),難道還要讓所有的game obj 替換成上述的material嗎?答案當然是否定的,這時候就需要介紹一個camera的大殺器了

替換渲染Camera.renderwithshader()

講一個例子,假如場景中存在兩個camera,renderCamera和depthCamera,場景中其他的gameobj都是默認的材質(zhì)。那么我們?yōu)榱瞬挥绊憆enderCamera的正常渲染,depthCamera如何不修改game obj的材質(zhì)但是又能使用上述的deptVisual-shader進行深度圖提取呢?方式就是RenderWithShader(Shader replaceShader, string replacementTag)
它需要兩個參數(shù),頭一個是替換執(zhí)行的shader,很好理解,第二個是決定如何替換的規(guī)則,它一般都是使用"RenderType"。我們從unity doc可以知道每一個subshader都可以設(shè)置RenderType

Tags { "RenderType"="Opaque" }

這里rendertype的具體用法就是:replaceShader中所有RenderType的值 = originShader中RenderType的值的,都會進行替換渲染,也就是本來用originShader subshader 渲染的,因為originShader subshader 的RenderType與replaceShader subshader的一致,渲染都改為使用replaceShader subshader。
假如第二個參數(shù) = "",則此攝像機本次渲染的所有物體都會使用replaceShader 進行渲染。

replaceShader 中Tags { "RenderType"="Opaque" },就是所有的不透明物體都是用replaceShader渲染一次。我們使用renderwithshader把所有不透明的物體進行深度提取(半透明物體的深度zwrite off)。

unity的官網(wǎng)里已經(jīng)有了相關(guān)代碼,c# 代碼如下

 using UnityEngine;
 using System.Collections;
 
 [RequireComponent(typeof(Camera))]
 public class DepthRenderer : MonoBehaviour {
 
     GameObject depthCamera=null;
     Shader replacementShader=null;
 
     // Use this for initialization
     void Start () 
     {
         depthCamera=new GameObject();
         depthCamera.AddComponent<Camera>();
         depthCamera.camera.enabled=false;
         depthCamera.hideFlags=HideFlags.HideAndDontSave;
         
         depthCamera.camera.CopyFrom(camera);
         depthCamera.camera.cullingMask=1<<0; // default layer for now
         depthCamera.camera.clearFlags=CameraClearFlags.Depth;
 
         replacementShader=Shader.Find("RenderDepth");
         if (replacementShader==null)
         {
             Debug.LogError("could not find 'RenderDepth' shader");
         }
     }
     
     // Update is called once per frame
     void OnPreRender () 
     {
         if (replacementShader!=null)
         {
             Camera camCopy=depthCamera.camera;
 
             // copy position and location;
             camCopy.transform.position=camera.transform.position;
             camCopy.transform.rotation=camera.transform.rotation;
             
             camCopy.RenderWithShader(replacementShader, "RenderType");
         }
     }
 }

depthCamera 設(shè)置為false是為了手動RenderWithShader, 所以渲染深度圖的時機就可以自己控制了?。。。?!

tips -- 因為深度圖是灰度圖,假如渲染出來的rt全是default color,那么有可能是camera的遠近裁剪面太大,導致顏色看不清楚,可以自行修改裁剪面的值進行測試


4. UNITY 的Graphics API介紹 & Depth Stencil Test 注意事項

使用unity進行image post effect時,我們會遇到大量的Graphics Api,記錄幾個常用的

Graphics .Blit(Texture source, RenderTexture dest, Material mat)

Blit其實姐可以理解為遍歷source所有像素,使用mat的shader進行處理后,拷貝到dest中去。它的實現(xiàn)方法我猜就是正交投影下繪制一個覆蓋整個相機的長方體,然后通過已有數(shù)據(jù)進行處理。
可以肯定的是
Blit sets dest as the render target, sets source _MainTex property on the material, and draws a full-screen quad.
_MainTex 使用的數(shù)據(jù)來自source,寫入的位置肯定是dest,當dest = null的時候,寫入幀緩存。
當mat存在模板測試代碼的時候,參考的模板數(shù)據(jù)來自dest,畢竟每個rendertexture都可能同時存在colorbuffer & depthbuffer

Graphics.Blit(Texture source, Material mat)

這個函數(shù)還有一個重要的重載的形式,如上
它的寫入目標是Graphics.activeColorBuffer。depth & stencil buffer使用的是Graphics.activeDepthBuffer。
因此,我們使用這個方法的時候一定要設(shè)置好渲染目標

Graphics.SetRenderTarget(rt1.colorBuffer, rt2.depthBuffer);

這個api控制接下來的blit渲染中color 以及 depth & stencil buffer使用不同rt的數(shù)據(jù),寫入color的目標是rt1,測試使用的depth buffer的數(shù)據(jù)來源是rt2(當然zwrite 打開我認為rt2深度數(shù)據(jù)也會修改,待測
這個api感覺android不能用,也就是不能分開設(shè)置rendertarget

講一個關(guān)于模板測試的例子
// source 中模板已經(jīng)有數(shù)據(jù)了,現(xiàn)在希望對模板中特殊值的像素進行顏色xxx的后處理,那么就是
OnRenderImage(RenderTexture source, RenderTexture destination)
Graphics.Blit(source, TempBuffer);
Graphics.Blit(TempBuffer, source,PostRender); // 假設(shè)postrender會進行模板測試
原因就是blit中_MainTex使用的是TempBuffer,然而深度測試 & 模板測試數(shù)據(jù)使用的是source,先把TempBuffer的顏色拷貝過來
一般的blit后處理深度測試都關(guān)了,我猜測depth 打開的話使用的也是dest的深度buffer,沒有印證過


吭哧吭呲寫了這么多,累死了...........demo 的空我傳到GitHub上。如有紕漏之處,請多多指教

曾慮多情損梵行,入山又恐別傾城。
世間安得雙全法,不負如來不負卿。

2017/11/10 北京 -- shared_ptr
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容