Unity CommandBuffer或是Camera重定向RenderTarget的ColorBuffer & DepthBuffer
关键API:_Camera.SetTargetBuffers(colorRT.colorBuffer, depthRT.depthBuffer);或CommandBuffer.SetRenderTarget(RenderTargetIdentifier[] colors, RenderTargetIdentifier depth);下面我用到的是Camera的API然后重定向ColorBuff..
首先,为何要这么重定向渲染目标。
因为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的方式,但是是针对Opaque
的RenderType
)有好几个条件:
Camera.depthTextureMode
的设置。- 阴影功能的开启。
- 或是其他我们未发现,但这些功能是依赖深度来实现的,都会自动开启渲染深度纹理。
想看要某个Camera是否开始的depthTextureMode属性的功能可以看Inspector视图中Camera组件的底部:(下面我只是开启了:DepthTextureMode.Depth
,如果连DepthNormals
和MotionVector
都开启,那么会显示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
更多推荐
所有评论(0)