Unity Shader - 深度缓存复用


环境

Unity : 2018.2.11f1
管线 : Built-In

(这个项目使用比较旧的 Unity 版本)


目的

最近制作一个功能,在 Bloom 后处理,只对某部分得 对象有效(需求有点奇怪)

如:原来的 Bloom 效果会对整个屏幕所有像素都有影响(这是后处理的特色),但美术想要只对 场景 中得 角色对象有 Bloom 效果


思路

  • 先绘制 场景中得所有得角色 到一个 _MaskTexR8 格式纹理中(shader 很简单:只要 输出 R 通道 1 即可)
  • 然后 Bloom 后效,在提取 光亮点的时候,只保留 _MaskTex 内的像素有重合的 像素即可
  • 但是将 所有角色绘制到 _MaskTex 前,所有角色被深度遮挡(深度剔除)的关系还是需要正常的,也就是说需要在 _MaskTex 时,保持原来的深度关系,那么简单的方式就是 复用之前的 depthBuffer

问题

上面 提到的 复用之前的 depthBuffer 中 遇到一些问题,如下代码

// jave.lin 2021/12/9 测试 深度复用伪代码
// bloom 后效类

CommandBuffer _cmdBuff;
private void Start()
{
    
    
	_cmdBuff = new CommandBuffer();
	_cmdBuff.name = "Draw Characters to BloomMask Texture";
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    
    
	...
	RenderTexture maskTex = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.R8);
	RenderTexture activeRT = RenderTexture.active;
	_cmdBuff.Clear();
	// color buffer 使用 maskTex 的颜色缓存
	// depth buffer 使用 activeRT 的深度缓存
	_cmdBuff.SetRenderTarget(maskTex.colorBuffer, activeRT.depthBuffer);
	_cmdBuff.ClearRenderTarget(false, true, Color.black);
	foreach (var renderer in characterRenderers)
	{
    
    
		if (renderer.isVisible)
		{
    
    
			_cmdBuff.DrawRenderer(renderer, _drawMaskMaterial);
		}
	}
	Graphics.ExecuteCommandBuffer(_cmdBuff);
    _bloomMaterial.SetTexture("_MaskTex", maskTex);
    ...

	// bloom handle
}


其中,留意:

	// color buffer 使用 maskTex 的颜色缓存
	// depth buffer 使用 activeRT 的深度缓存
	_cmdBuff.SetRenderTarget(maskTex.colorBuffer, activeRT.depthBuffer);

DX 中的问题,OpenGL 没有问题

上面的代码 _cmdBuff.SetRenderTarget(maskTex.colorBuffer, activeRT.depthBuffer); 在 DX 中是有问题的

具体表现,我就不截图项目中的情况(尽量不暴露公司资源)
所以就直接引用一下网上 unity 论坛上某贴子:SetRenderTarget problem 的情况,他的情况和我一样

如下图,他是在非 DX 下运行,还是 perfect 的,但是在 DX 并开启 AA 后,就报错了(我在 2018.2.11f1 中也是 DX + AA,但是没有报错)
在这里插入图片描述

他的报错如下:

ArgumentException: Graphics.SetRenderTarget called with depth RenderBuffer from screen and color RenderBuffer from RenderTexture
UnityEngine.Graphics.SetRenderTargetImpl (RenderBuffer colorBuffer, RenderBuffer depthBuffer, Int32 mipLevel, CubemapFace face, Int32 depthSlice) (at C:/buildslave/unity/build/Runtime/Export/Graphics.cs:189)
UnityEngine.Graphics.SetRenderTarget (RenderBuffer colorBuffer, RenderBuffer depthBuffer) (at C:/buildslave/unity/build/Runtime/Export/Graphics.cs:225)

其实具体原因是因为 DX 平台下会有限制:

最佳回答:

我不太了解 Unity,但我知道他们的基础层,如果他们可以映射 D3D9、D3d10 和 OpenGL,那么他们的抽象必须使用一个公分母。
在这种情况下,D3D10 是最受限制的,您不能在不同大小的渲染目标之间共享深度表面 .如果你有相同大小的屏幕和渲染目标,那么你确实可以将唯一的深度缓冲区绑定(bind)到不同的渲染目标。
深度缓冲区不是严格意义上的必要,就像您观察到的那样,您可以在没有深度缓冲区的情况下进行渲染,但结果只是按照发出绘制命令的顺序进行渲染。 (D3D 中的 draw call = DrawPrimitive,或 glDrawBuffers 等)甚至可以保证规范在三角形级别上的顺序是一致的,即使显卡非常平行,他们也拒绝为为了通过一次绘制调用的不同运行进行绘制的一致性。
如果您使用深度缓冲区,那些碰巧在以较低深度绘制的对象之后进行绘制调用的对象将覆盖这些靠近的对象(在 View 空间中)并给出错误的结果,深度缓冲区有助于逐个像素地丢弃,一个对象的像素(在 View 空间中)比之前已经在这个像素上渲染的东西更深,但深度更近。
绑定(bind)深度缓冲区也有助于提高性能,因为如果一个块中的每个像素都具有某个值的最小深度,则图元光栅化器在顶点缓冲区退出后知道您的图元的整个部分将永远不会在该块上呈现,并丢弃整个街区。这称为早期 Z 剔除,对性能有很大帮助。所以最好保持深度缓冲区堵塞。
相机在低级图形理论中没有概念,它只是由一个 View 矩阵表示,该矩阵是应用于整个世界的逆变换,将世界从世界空间移动到 View 空间,作为任何单顶点变换计算的一部分。这就是为什么在经典的顶点着色器中,位置取自对象空间(在顶点缓冲区流中),然后乘以对象矩阵变换,然后乘以 View 矩阵变换,然后是投影矩阵变换,然后光栅化器划分’w’ 的所有内容都可以使视角分开。
那就是通过利用这种管道行为来“创建”相机的概念。 Unity 必须通过暴露相机类来抽象所有这些。也许相机类甚至有一个“纹理目标”成员来解释来自这个相机的渲染将存储在哪里。
是的,渲染是直接对指定的渲染纹理进行的,没有中间前端缓冲区或其他任何东西,渲染目标是硬件支持的功能,并且在渲染结束时不需要复制。
渲染目标本身就是一个完整的配置,因为硬件多重采样,它实际上可能绑定(bind)多个所需分辨率大小的缓冲区。
有颜色缓冲区,例如 MSAA4x 中的 4 个 RGBA 表面,深度缓冲区,通常是定点 24 位表示,以及 8 位用于模板表面。所有这些表面都代表渲染目标配置并且是渲染所必需的。
我希望这有帮助
关于c# - 为什么我需要 depthBuffer 来使用 RenderTexture?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19023273/

其原因在与:DX 下开了 AA 那么 depthBuffer 中的(depth buffer 或是 stencil buffer) 宽高尺寸不一致导致的,我们在选用 AA 是,可以看到 AAx2, AAx4, AAx8, 其实在 frame buffer 中,我们如果申请尺寸大小为 1024x1024,在 AAx2 就是 2048x2048 的尺寸,便于做采样混合以便达到 AA 的效果,以此类推 x4, x8 就是在放大这些分辨率,提高采样数量,在输出到 我们的 1024x1024前,先 采样周边的点做混合来达到 抗锯齿的效果

所以我们一旦开启了 DX + AA,那么 maskTex.colorBuffer 和 activeRT.depthBuffer(depth + stencil) 可能在尺寸上就对不上了

另一个曲线救国法:将 _CameraDepthTexture 或是 _CameraDepthNormalTexture 传入 shader,然后再 shader 中的片段 depth 和 depthTexture 中的 depth 做对比来 discard 掉该片段,起到类似收到 ztest 的过程


References

猜你喜欢

转载自blog.csdn.net/linjf520/article/details/121817318#comments_22267286