有各种管理 RenderTargets 的类。最后,引擎的核心API接收到RenderTextureRenderTargetIdentiferNameId(int),其他都是URP提供的包装类。

NameId (int)

Shader.PropertyId(string) 获取的整数 ID。在 RenderPipline 中,这个 ID 的引用基本上是中心的。
它基本上是一个全局名称引用。当然,如果名字发生冲突,你就出局了。

int texID = Shader.PropertyId("_TmpColorTarget");
cmd.GetTemporaryRT(texId, 1920, 1080);
cmd.ReleaseRT(texId);

类似的 ID... 或者更确切地说,使用相同的 Shader.PropertyId 获得的 ID 被绑定为 SRV。它应该像这样使用:

// hlsl
Texture2D _LightMap : t0;
// cs
int nameInShader = Shader.PropertyId("_LightMap");
Texture2D tex = Resources.Load<Texture2D>(...)
Shader.SetGlobalTexture(nameInShader, tex);

这两种类型的 ID 作为单独的 ID 进行管理。但是,令人困惑的是,前者(resource ID)在secure的时候,也绑定为后者(Bind ID to Shader),所以会如下。
基本上,这似乎是“每个应该被视为单独的事物,但不能重叠”的礼仪。

// hlsl
Texture2D _TexA : t0; // _TexA(赤)がBindされる
Texture2D _TexB : t1; // _TexA(赤)がBindされる
Texture2D _TexC : t2; // _TexA(赤)がBindされる
Texture2D _TexD : t3; // Bindされない(GraphicPiplineの場合、fallbackで灰色が刺さされる)
// cs
cmd.GetTemporaryRT("_TexA", 100, 100); // リソースIDの _TexA と、ShaderID の _TexA にBind
cmd.SetRenderTarget("_TexA");
cmd.ClearRenderTarget(RTClearFlags.Color, Color.red, 0, 0); // 赤

cmd.GetTemporaryRT("_TexB", 100, 100); // リソースIDの _TexB と、ShaderID の _TexB にBind
cmd.SetRenderTarget("_TexB");
cmd.ClearRenderTarget(RTClearFlags.Color, Color.blue, 0, 0); // 青

cmd.SetGlobalTexture("_TexB", "_TexA"); // ShaderID の _TexB に リソースID _TexA をBind(上書き)
cmd.SetGlobalTexture("_TexC", "_TexA"); // ShaderID の _TexC に リソースID _TexA をBind
cmd.SetGloablTexture("_TexD", "_TexC"); // error "render texture _TexC not found"

渲染目标标识符

NameID 的类型化版本。严格来说,您也可以将其作为 Native 指针或 InstanceID(使用 RenderTexture/Texture 时)。
这种类型包含在 Unity 的核心引擎中,API 基本上被重载以采用这种类型或 NameID。

struct RenderTargetIdentifier : IEquatable<RenderTargetIdentifier>
{
  BuiltinRenderTextureType m_Type; // Swapchain などを指す場合などで意味を持つ
  int m_NameID; // 基本ここだけ使う
  int m_InstanceID; // TextureAsset からキャストした場合などで意味を持つ
  ...
}

渲染目标处理程序

一组 NameID 和 RenderTargetIdentifer。它是作为 URP 包装器的最原始的资源 (ID) 管理类。
如下使用。它不保护和释放资源,所以有必要在外面做。

RenderTargetHandler m_Handler;
public void Init() => m_Handler.Init("_Texture_No_Onamae");
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
  ...
  cmd.GetTemporaryRT(m_Handler.id, descriptor, FilterMode.Bilinear); // NameID互換のAPIで使うとき
  cmd.Blit(..., m_Handler.Identifer()); // RenderTargetIdnentifer互換のAPIで使うとき
  cmd.ReleaseRT(m_Handler.id); // NameID互換のAPIで使うとき
  ...
}

RenderTargetIdentifer 是刚刚输入到 NameID 的相同数据,因此它具有不必将 NameID 作为原始 int 的优点。
不知怎的,我能感受到原生资源在范围内得到保护和释放的氛围。如果你把它写成一个接口,它看起来像这样。

interface IRenderable {}
interface IAllocFreeAndRenderable : IRenderable {}

struct RenderTargetIdentifer : IRenderable {}
struct RenderTargetHandler : IAllocFreeAndRenderable {}

渲染纹理

UnityEngine.Object 继承并被视为资产的类。特点是内部Native资源的生命周期与Object的生命周期相关联。但是,本机资源是延迟创建的,并在需要时保留。

它不可重用并且使用托管资源,因此它不像新/销毁每一帧。
除非您想使用 Inspector 进行设置,否则您基本上不会使用它。另外,当你想穿透一个材质时,除非它是一个物体,否则你不能穿透它,所以你需要使用它。

渲染目标缓冲区系统

URP 的第二代资源管理包装类。
作为 RenderTargetHandler 的一个进化点,

  • 包含分配/发布代码
  • 有两个资源,正面和背面(使用的内存量也是双倍的,没有延迟产生)

在 UniversalRenderer 中,它用于绘制对象的颜色缓冲区。

RenderTargetHandler m_BufferSys;
void Setup() => m_BufferSys = new RenderTargetBufferSystem("_CameraColorAttachment");
void Execute()
{
  ...
  m_BufferSys.SetCameraSettings(cmd, descriptor, FilterMode.Bilinear); // 途中でサイズを変えてもOK
  cmd.Blit(..., m_BufferSys.GetBackBuffer(cmd));
  m_BufferSys.Clear();
  ...
}

Swap 将切换 BackBuffer。由于它是双缓冲,它让我想起了 Swapchain 之类的东西,但它似乎不是为了跨越这样的帧。

var src = m_BufferSys.GetBackBuffer(cmd);
m_BufferSys.Swap();
cmd.Blit(src, m_BufferSys.GetBackBuffer(), negaMaterial);

比如在应用负片等后期效果的时候,概念是输入和输出会是同一个资源,但那是不可能的,所以一般的方法是准备一个临时的RT,复制一下,然后作为输入。是。使用此类,您可以在渲染后使用 BackBuffer 作为输入和 FrontBuffer 作为输出进行交换。

// これは無理
cmd.Blit("_ColorTarget", "_ColorTarget");

// 一般的なやりかた
cmd.GetTemporaryRT("_TmpColorTarget", 1920, 1080);
cmd.Blit("_ColorTarget", "_TmpColorTarget"); // Copy
cmd.Blit("_TmpColorTarget", "_ColorTarget");
cmd.ReleaseRT(_TmpColorTarget");

// RenderTargetBufferSystem
cmd.Blit(m_BufferSys.GetBackBuffer(), m_BufferSys.GetFrontBuffer());
m_BufferSys.Swap();

反之,缓存GetBackBuffer()的结果会出问题,所以要小心。尤其是 Renderer、Feature 和 Pass Executes 都延迟到了 Setup 中,所以如果在 Setup 期间采用 BackBaffer,可能会导致问题。
(就个人而言,如果你问我哪一种是最自然的行为,我觉得两者都很微妙……)

m_Pass.Setup(m_BufferSys.GetBackBuffer()); // Buffer_A
EnqueuePass(m_Pass); // Executeは遅延しているのでSwapの後
m_BufferSys.Swap(); // BackBufferが変わる -> Buffer_B

class Pass
{
  RenderTargetIdentifer m_Dst; // Buffer_A
  void Setup(RenderTargetIdentifer rt) => m_Dst = rt; // Buffer_A
  void Execute()
  {
    // ここでは、Swapされる前の Buffer_A が使われる
  }
}

RT 手柄系统

这是URP 提供的最新包装类。
与 RenderTargetBufferSystem 的区别在于

  • 根据全局比例自动调整大小
  • 缩小尺寸时不重新分配(较小的视口)
  • 取消双缓冲(使用BufferedRTHandleSystem

顾名思义,GetTemporaryRT 已被用于在相机内分配和释放。但这不是,它更像是一个相机......跨帧应用程序。 (使用示例还很少,所以我不太明白意图。)在内部,new RenderTexture 用作 Asset 而不是GetTemporaryRT
图像代码如下所示。

RTHandle screenColorBuffer = RTHandles.Alloc(cameraPixelSize => cameraPixelSize);
RTHandle screenNormalBuffer = RTHandles.Alloc(cameraPixelSize => cameraPixelSize);
for(var frame = 0; ; frame++)
{
  foreach(var camera in cameras) // カメラ解像度: [(512, 512), (1280, 720), (1920, 1080)]
  {
    RTHandles.SetReferenceSize(camera.width, camera.height);
    ...
    cmd.SetRenderTarget(new [] { screenColorBuffer, screenNormalBuffer }
    ...
  }
  await NextFrame();
}
screenColorBuffer.Release();
screenNormalBuffer.Release();

使用GetTemporaryRT 固定/释放每个摄像头时,每个摄像头的缓冲区大小不同,因此 6 个缓冲区的总值是固定的。另一方面,如果您使用 RTHandleSystem,则只需要 2 个即可获得全分辨率。此外,似乎可以重用相机中未同时使用的缓冲区。在这种情况下,最好使用分配器的实例版本而不是全局版本,因为它会执行未链接到相机范围的SetReferenceSize

// GetTemporaryRT
Camera#0 Color (512, 512) 
Camera#0 Normal (512, 512)
Camera#1 Color (1280, 720)
Camera#1 Normal (1280, 720)
Camera#2 Color (1920, 1080)
Camera#2 Normal (1920, 1080)
// RTHandleSystem
Camera#0~2 Color (1920, 1080)
Camera#0~2 Normal (1920, 1080)

原则上,保留一个与最大大小一样宽的缓冲区,如果小于该大小,则将 ViewPort(写入)和 UV(读取)调整为仅使用部分缓冲区增加。

RTHandle buff = ...;
// write
cmd.SetRenderTarget(buff);
cmd.SetViewport(buff.rtHandleProperties.currentViewportSize);
// read
cmd.SetGloablTexture(buff);
cmd.SetGlobalVector("_UVScale", buff.rtHandleProperties.rtHandleProperties.rtHandleScale);

缓冲RTHandle系统

RTHandleSystem 带缓冲功能。通过将bufferCount设置为2,可以和RenderTargetBufferSystem一样使用。 (3或更多的用法含义略有不同,但我觉得我希望您将其用作TAA或历史)

概括

如果您进行大量后期处理或使用大量反射和阴影,RenderTargets 往往会很快膨胀。最近,即使是智能手机硬件也有足够的内存,但如果你有额外的内存,你可以把它变成缓存。
针对此类资源问题,URP已经进行了各种尝试和错误,所以让我们参考并优化它!


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308622543.html

相关文章: