首先,为何要这么重定向渲染目标。

因为Unity中,如果使用内置的深度,会导致DC翻倍,在Camera.depthTextureMode中开启。
(经我测试发现,用FrameDebugger可以看到,开启内置的阴影功能也会DC翻倍,因为阴影也是需要深度的,后面会讲到)

关键API:_Camera.SetTargetBuffers(colorRT.colorBuffer, depthRT.depthBuffer);CommandBuffer.SetRenderTarget(RenderTargetIdentifier[] colors, RenderTargetIdentifier depth);

下面我用到的是Camera的API

然后重定向ColorBuffer与DepthBuffer后,一定要有应用才能知道有无问题。
那么我就用直接的一篇:Unity Shader - 根据片段深度重建片段的世界坐标,来应用深度吧。
关于深度纹理如何获取,也可以参考之前写的一篇:Unity Shader - 获取BuiltIn深度纹理和自定义深度纹理的数据

先来说一个像素格式,下面的CSharp代码中有一个长长的注释:

// RHalf,注意这里别使用RHalf,否则精度没有原来的RenderTextureFormat.Depth的那么高
// 在正交相机模式下很明显,透视没什么问题
depthTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R16); 

这个depthTex会读取depthBuf得深度内容。depthBuf创建如下:(注意下面depthBuf与depthTex的区别)

depthBuf = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth);

因为我们的原来指定depthBuf的对象的depth&stencil(第三个参数)精度为:24位,格式为:Depth,其中,24位中,有16位是depth的,还有8位是stencil的。这个参数有三种有效值,分别是:

  • 0:就是不使用深度,也不使用模板。
  • 16:使用16位深度,但不使用模板。
  • 24:使用16位深度,也使用8位模板。

如果你要使用深度,那可以填入:16或是24。如果需要使用到stencil那就天24。这里我们暂时天了24。因为模板功能后面我其他功能需要用到。但这个例子不需要。

所以这参数可以按需来填入对应的数值。

另外注意,除了这个参数会影响到精度,后面的那个:RenderTextureFormat也会影响精度的。
depthBuf 使用的是:RenderTextureFormat.Depth。API注释是:A depth render texture format.,就是一个深度格式,当精度就是按深度的来设置,我们前一个参数设置了24,有16位是深度的,所以depthBuf的深度精度是有16位的。

然后是depthTex纹理的精度,我们用他的R通道来存放深度(这里是unity底层设置的,深度值写入R通道),所以我们的depthTex的纹理像素格式,精度都的设置。如下:

depthTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R16);

可以看到第三个参数为0,就是不需要深度,模板的数据位,因为我们读取深度到R通道,格式是:R通道16位。这样就可以正常读取到深度缓存的值了。之前因为这里参考了别的博主的代码,它填入的是:RHalf,在透视下,深度的差异与_CameraDepthTexture中的没什么差别,看不出来。但在正交下,就很大差别了。所以这里要使用R16好些。

差别在这:
先设置正交投影
在这里插入图片描述
然后,把RHalf改为R16,在看看
在这里插入图片描述

OK,好了。

然后是,显示我们重定向的ColorBuffer与DepthBuffer与WorldPositionFromDepth的三种显示结果:
在这里插入图片描述

然后我测试时发现:就我的Camera.depthTextureMode = DepthTextureMode.None;但是在Shader中,使用_CameraDepthTexture依然是有内容的,我就觉得奇怪了。然后仔细看了看FrameDebugger,发现第一处DC分类就有一项:Update Depth Texture,而且是为了绘制阴影而执行的。

如下图:
在这里插入图片描述

所以可以确定在unity中,内部执行绘制深度纹理(翻倍DC的方式,但是是针对OpaqueRenderType)有好几个条件:

  • Camera.depthTextureMode的设置。
  • 阴影功能的开启。
  • 或是其他我们未发现,但这些功能是依赖深度来实现的,都会自动开启渲染深度纹理。

想看要某个Camera是否开始的depthTextureMode属性的功能可以看Inspector视图中Camera组件的底部:(下面我只是开启了:DepthTextureMode.Depth,如果连DepthNormalsMotionVector都开启,那么会显示3个)
在这里插入图片描述

CSharp

using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// jave.lin 2020.03.19
/// </summary>
public class RetargetColAndDepthBuf : MonoBehaviour
{
    public enum ProjType
    {
        Perspective,
        Orthographic
    }

    private static int _AfterSkyboxColTex_hash = Shader.PropertyToID("_AfterSkyboxColTex");
    private static int _AfterForwardOpaqueDepthTex_hash = Shader.PropertyToID("_AfterForwardOpaqueDepthTex");

    private static int _Perspective_Ray_hash = Shader.PropertyToID("_Perspective_Ray");

    private static int _Ortho_Ray_hash = Shader.PropertyToID("_Ortho_Ray");
    private static int _Ortho_Ray_Oringin_hash = Shader.PropertyToID("_Ortho_Ray_Oringin");

    public bool enableCustomColDepth = false;
    private bool lastEnableCustomColDepth = false;

    public bool enableBuiltInDepth = false;
    private bool lastEnableBuiltInDepth = false;

    public ProjType projType;
    public Material blitBackbuffMat;

    private Camera cam;
    private CommandBuffer blitColorBufCMD;
    private CommandBuffer blitDepthBufCMD;
    private CommandBuffer blitBackBufCMD;

    private RenderTexture colorBuf;
    private RenderTexture depthBuf;

    private RenderTexture colorTex;
    private RenderTexture depthTex;

    private void Start()
    {
        cam = GetComponent<Camera>();

        StepCmd();
    }

    private void StepCmd()
    {
        if (lastEnableCustomColDepth != enableCustomColDepth)
        {
            lastEnableCustomColDepth = enableCustomColDepth;
            if (enableCustomColDepth)
            {
                colorBuf = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RGB111110Float);
                depthBuf = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth);

                colorTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.RGB111110Float);
                depthTex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R16); // RHalf,注意这里别使用RHalf,否则精度没有原来的RenderTextureFormat.Depth的那么高,在正交相机模式下很明显,透视没什么问题

                colorBuf.name = "Custom - ColorBuffer";
                depthBuf.name = "Custom - DepthBuffer";
                colorTex.name = "Custom - ColorTexture";
                depthTex.name = "Custom - DepthTexture";

                blitColorBufCMD = new CommandBuffer();
                blitColorBufCMD.name = "AfterSkyBox - BlitColor";
                blitColorBufCMD.CopyTexture(colorBuf.colorBuffer, colorTex.colorBuffer);
                cam.AddCommandBuffer(CameraEvent.AfterSkybox, blitColorBufCMD);

                blitDepthBufCMD = new CommandBuffer();
                blitDepthBufCMD.name = "AfterForwardOpaque - BlitDepth";
                blitDepthBufCMD.Blit(depthBuf.depthBuffer, depthTex.colorBuffer);
                cam.AddCommandBuffer(CameraEvent.AfterForwardOpaque, blitDepthBufCMD);

                blitBackBufCMD = new CommandBuffer();
                blitBackBufCMD.name = "AfterEverything - BlitBackBuf";
                blitBackBufCMD.Blit(colorBuf.colorBuffer, (RenderTexture)null, blitBackbuffMat);
                cam.AddCommandBuffer(CameraEvent.AfterEverything, blitBackBufCMD);
            }
            else
            {
                DestroyCmd();
            }
        }
    }

    private void OnPreRender()
    {
        cam.orthographic = projType == ProjType.Orthographic;

        if (cam.orthographic)
        {
            // unity 1 unit == 100 pixels,所以view volume height = 10 unit == 1000 pixel
            var halfOfHeight = cam.orthographicSize;
            var halfOfWith = cam.aspect * halfOfHeight;
            //Debug.Log($"size:{cam.orthographicSize}, aspect:{cam.aspect}, hw:{halfOfWith}, hh:{halfOfHeight}");

            // 了解orthographic的投影矩阵,更方便与对参数的应用:http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho

            var upVec = cam.transform.up * halfOfHeight;
            var rightVec = cam.transform.right * halfOfWith;

            // 左下角 bottom left
            var bl = -upVec - rightVec;
            // 左上角 top left
            var tl = upVec - rightVec;
            // 右上角 top right
            var tr = upVec + rightVec;
            // 右下角 bottom right
            var br = -upVec + rightVec;

            // 正交相机的四个角落射线的起点
            var viewVolumeCornerPos = Matrix4x4.identity;
            // 经shader中顶点颜色赋值后出入到屏幕,可以确定,第0是:左下角,1:左上角,2:右上角,3:右下角
            viewVolumeCornerPos.SetRow(0, bl);
            viewVolumeCornerPos.SetRow(1, tl);
            viewVolumeCornerPos.SetRow(2, tr);
            viewVolumeCornerPos.SetRow(3, br);

            blitBackbuffMat.SetVector(_Ortho_Ray_hash, transform.forward * cam.farClipPlane);
            blitBackbuffMat.SetMatrix(_Ortho_Ray_Oringin_hash, viewVolumeCornerPos);
        }
        else
        {
            var aspect = cam.aspect;                // 宽高比
            var far = cam.farClipPlane;             // 远截面距离长度
            var rightDir = transform.right;         // 相机的右边方向(单位向量)
            var upDir = transform.up;               // 相机的顶部方向(单位向量)
            var forwardDir = transform.forward;     // 相机的正前方(单位向量)

            // fov = field of view,就是相机的顶面与底面的连接相机作为点的夹角,
            // 我们取一半就好,与相机正前方方向的线段 * far就是到达远截面的位置(这条边当做下面的tan公式的邻边使用)
            // tan(a) = 对 比 邻 = 对/邻
            // 邻边的长度是知道的,就是far值,加上fov * 0.5的角度,就可以求出高度(对边)
            // tan(a)=对/邻
            // 对=tan(a)*邻
            var halfOfHeight = Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad) * far;
            // 剩下要求宽度
            // aspect = 宽高比 = 宽/高
            // 宽 = aspect * 高
            var halfOfWidth = aspect * halfOfHeight;
            // 前,上,右的角落偏移向量
            var forwardVec = forwardDir * far;
            var upVec = upDir * halfOfHeight;
            var rightVec = rightDir * halfOfWidth;
            // 左下角 bottom left
            var bl = forwardVec - upVec - rightVec;
            // 左上角 top left
            var tl = forwardVec + upVec - rightVec;
            // 右上角 top right
            var tr = forwardVec + upVec + rightVec;
            // 右下角 bottom right
            var br = forwardVec - upVec + rightVec;

            var frustumCornersRay = Matrix4x4.identity;
            // 经shader中顶点颜色赋值后出入到屏幕,可以确定,第0是:左下角,1:左上角,2:右上角,3:右下角
            frustumCornersRay.SetRow(0, bl);
            frustumCornersRay.SetRow(1, tl);
            frustumCornersRay.SetRow(2, tr);
            frustumCornersRay.SetRow(3, br);
            blitBackbuffMat.SetMatrix(_Perspective_Ray_hash, frustumCornersRay);
        }

        StepCmd();

        if (enableCustomColDepth)
        {
            colorBuf.DiscardContents();
            depthBuf.DiscardContents();
            colorTex.DiscardContents();
            depthTex.DiscardContents();

            Shader.SetGlobalTexture(_AfterSkyboxColTex_hash, colorTex);
            Shader.SetGlobalTexture(_AfterForwardOpaqueDepthTex_hash, depthTex);

            // Camera.targetTexture和SetTargetBuffers的区别
            // https://blog.csdn.net/wodownload2/article/details/104424005
            // when targetTexture is null, camera renders to screen.
            // when rendering into a texture, the camera always renders into the whole texture;
            // it is also possible to make camera render into separate RenderBuffers, or into multiple textures at once, using SetTargetBuffers function.
            cam.SetTargetBuffers(colorBuf.colorBuffer, depthBuf.depthBuffer);
        }
        else
        {
            cam.targetTexture = null;
        }

        if (lastEnableBuiltInDepth != enableBuiltInDepth)
        {
            lastEnableBuiltInDepth = enableBuiltInDepth;
            // 位操作---置位(置1)、清除和取反
            // https://blog.csdn.net/cw616729/article/details/90032228
            if (enableBuiltInDepth)
                cam.depthTextureMode |= DepthTextureMode.Depth;
            else
                // 相当于:先取Depth得位,再取反所有位,这时Depth都为0,再与原来得depthTextureMode得位求与,即可将出了Depth以外的所有位都保留下来
                cam.depthTextureMode &= ~DepthTextureMode.Depth;
        }
    }

    private void DestroyCmd()
    {
        Destroy(colorBuf);
        Destroy(depthBuf);
        Destroy(colorTex);
        Destroy(depthTex);

        if (blitColorBufCMD != null)
        {
            cam.RemoveCommandBuffer(CameraEvent.AfterSkybox, blitColorBufCMD);
            blitColorBufCMD.Dispose();
            blitColorBufCMD = null;
        }
        if (blitDepthBufCMD != null)
        {
            cam.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, blitDepthBufCMD);
            blitDepthBufCMD.Dispose();
            blitDepthBufCMD = null;
        }
        if (blitBackBufCMD != null)
        {
            cam.RemoveCommandBuffer(CameraEvent.AfterEverything, blitBackBufCMD);
            blitBackBufCMD.Dispose();
            blitBackBufCMD = null;
        }
    }

    private void OnDestroy()
    {
        DestroyCmd();
    }
}

Shader

// jave.lin 2020.03.19 - 显示自定义的缓存内容 - 然后还加上了深度转世界坐标的验证看看有无问题,结果发现了深度纹理中RHalf与R16的一些精度区别
Shader "Custom/ShowCustomBuf" {
    Properties {
        [KeywordEnum(COLOR,DEPTH)] _SHOWTYPE("ShowType", Float) = 0                         // 控制显示ColorBuffer还是DepthBuffer的内容
        [KeywordEnum(CUSTOM_DEPTH,BUILIN_DEPTH)] _DEPTHTYPE("DepthType", Float) = 0         // 控制显示DepthBuffer时,是用自定义的还是unity内置的
        [MaterialToggle(SHOW_DEPTH_WP)] SHOW_DEPTH_WP("Show Depth WorldPos", Float) = 1     // 是否实现深度的世界坐标
    }
    SubShader {
        Cull Off ZWrite Off ZTest Always
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _SHOWTYPE_COLOR _SHOWTYPE_DEPTH
            #pragma multi_compile _DEPTHTYPE_CUSTOM_DEPTH _DEPTHTYPE_BUILIN_DEPTH
            #pragma multi_compile _ SHOW_DEPTH_WP
            #include "UnityCG.cginc"
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                uint vid : SV_VertexID;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;

                float3 ray : TEXCOORD1;
                float4 rayOrigin : TEXCOORD2;
            };
            sampler2D _AfterSkyboxColTex;               // 外部用CmdBuf添加指定生成的纹理,并指定全局的ColorBuf纹理
            sampler2D _AfterForwardOpaqueDepthTex;      // 外部用CmdBuf添加指定生成的纹理,并指定全局的DepthBuf纹理
            sampler2D _CameraDepthTexture;              // unity内置的深度纹理

            // ==== depth to world pos start ====
            // perspective
            float4x4 _Perspective_Ray;                  // 透视投影时用的射线

            // orthographic
            float4 _Ortho_Ray;                          // 正交投影时用的射线(起始就是相机forward)
            float4x4 _Ortho_Ray_Oringin;                // 正交投影时用的射线起点
            // ==== depth to world pos end ====

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                // x = orthographic camera's width
                // y = orthographic camera's height
                // z = unused
                // w = 1.0 if camera is ortho, 0.0 if perspective
                //float4 unity_OrthoParams;
                // 这里可以写keyword提升性能,但是可读性比较差,这里就使用if判断
                if (unity_OrthoParams.w == 1) { // ortho
                    o.rayOrigin = _Ortho_Ray_Oringin[v.vid];
                } else { // perspective
                    o.ray = _Perspective_Ray[v.vid];
                }
                return o;
            }
            float4 DepthToWorldPos(v2f i, float depth) {
                if (unity_OrthoParams.w == 1) { // ortho
                    float3 wp = _WorldSpaceCameraPos.xyz + i.rayOrigin.xyz + _Ortho_Ray.xyz * depth;
                    return float4(wp, 1);
                } else { // perspective
                    float3 wp = _WorldSpaceCameraPos.xyz + i.ray * depth;
                    return fixed4(wp, 1);
                }
            }
            float4 GetDepth(v2f i) {
                if (unity_OrthoParams.w == 1) { // ortho
                    #if _DEPTHTYPE_CUSTOM_DEPTH
                        float depth = SAMPLE_DEPTH_TEXTURE(_AfterForwardOpaqueDepthTex, i.uv);
                    #else
                        float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
                    #endif
                    // 正交下的相机,_CameraDepthTexture纹理中存储的是线性比例值
                    #if defined(UNITY_REVERSED_Z) // 正交需要处理这个宏定义,透视不用,估计后面unity版本升级后会处理正交的这个宏定义处理吧
                    depth = 1 - depth;
                    #endif
                    return depth;
                } else { // perspective
                    #if _DEPTHTYPE_CUSTOM_DEPTH
                        float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_AfterForwardOpaqueDepthTex, i.uv));
                    #else
                        float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
                    #endif
                    return depth;
                }
            }
            float4 ShowDepth(v2f i) {
                #if SHOW_DEPTH_WP
                return DepthToWorldPos(i, GetDepth(i));
                #else
                return GetDepth(i);
                #endif
            }
            fixed4 frag (v2f i) : SV_Target {
                #if _SHOWTYPE_COLOR
                    return tex2D(_AfterSkyboxColTex, i.uv);
                #else // #if _SHOWTYPE_COLOR
                    return ShowDepth(i);
                #endif // #if _SHOWTYPE_COLOR
            }
            ENDCG
        }
    }
}

总结

如果要手动重定向渲染,将ColorBuffer或是DepthBuffer都指定到我们的一个RT,来减少unity内置的DC。

那么我们首先要将一下几点必须自己去处理:

  • shader深度纹理,用自己的DepthBuffer的RT;
  • 阴影自己实现,因为开启了阴影unity底层也会去update depth texture(_CameraDetphTexture);
  • 还有可能unity其他功能是需要深度信息来实现的,unity都可以会去执行:update depth texture。并且我们都需要去一一实现这些功能,改用我们的DepthBuffer RT。(这个就很蛋疼了,所以尽量还是使用户会unity自带的深度纹理吧,因为粗略想了一下,还是挺多的,特别是unity内置的后处理特效,基本上都会与深度有关系,你难不成全都实现一篇吗?或是unity后续版本不断迭代,各种后效也需要深度的,你都得去一一实现,-_-!)

Project

backup : UnityShader_CmbBuf_RetargetColor&DpethBuf_2018.3.0f2

References

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐