book/05-texture-resource-and-ids.md

05. Texture resources and ID structure

Organizes the differences between RenderTexture/RTHandle/TextureHandle and Shader Property ID and exchange patterns in RenderGraph/Compatibility Mode.

05. Texture resources and “ID” structure: RenderTexture / RTHandle / TextureHandle

This chapter explains “how to exchange render textures” and what exactly the commonly referred to “reference ID” is.

Precedence: 04. RenderGraph

5.1 Glossary of terms

  • RenderTexture: Unity engine level texture resource (render target possible)
  • RTHandle: Render target handle considering dynamic resolution/scale (common to URP/HDRP)
  • TextureHandle: Handle for RenderGraph to track resources (internal representation of the graph)
  • “Shader Property ID”: Global property slot identifier such as Shader.PropertyToID("_MyTex")

Why mixing up “IDs” causes confusion

  1. Global slot ID (integer): Used as a key when setting shader global texture/constants
  2. Resource Handle (RTHandle/TextureHandle): Actual GPU resource (or wrapping it)
  3. RenderTexture instance: C# object reference

In practice, these three must be connected according to the situation.

5.2 Global Texture Slot: Shader.PropertyToID

The oldest (and still common) pattern is:

  • _MyColorTex for the same name int id = Shader.PropertyToID("_MyColorTex")
  • cmd.SetGlobalTexture(id, rt) or cmd.SetGlobalTexture("_MyColorTex", rt)

Here id is just a “slot key with name hashed to an integer” and not the texture itself.

5.2.1 Reasons and pitfalls of using “ID slot”

  • Reason: Reduce string comparison costs and speed up property access
  • Pitfall: If you mistakenly believe that “ID is a resource,” your design will suffer.

The ID is just the result of the (name → slot) transformation.

  • Resources are RenderTexture/RTHandle/TextureHandle
  • The slot is int or string

, and a call like cmd.SetGlobalTexture connects the two.

5.3 Basic pattern for exchanging RenderTexture (Compatibility Mode)

The simplest way is:

  1. Create a temporary render target (GetTemporaryRT or allocate RTHandle)
  2. Render in any pass (Blit/Draw)
  3. Bind to global slot (SetGlobalTexture)
  4. Sample from another pass/shader
C#
static readonly int MyTexId = Shader.PropertyToID("_MyTex");

var cmd = CommandBufferPool.Get("Example");
try
{
    cmd.GetTemporaryRT(MyTexId, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf);
    cmd.SetRenderTarget(MyTexId);
    // ... draw ...

    cmd.SetGlobalTexture(MyTexId, MyTexId);
    context.ExecuteCommandBuffer(cmd);
}
finally
{
    cmd.ReleaseTemporaryRT(MyTexId);
    CommandBufferPool.Release(cmd);
}

The above example is for “concept” explanation only. In practice, it is recommended to move on to RTHandle/RenderGraph.

5.3 RTHandle: Why do you need it?

When dealing with dynamic resolutions (Scale, XR, different sizes for each camera), carrying RenderTexture directly can easily create leakage/reallocation issues.

RTHandleis:

  • Scale/dynamic resolution response
  • Inter-frame reuse and lifespan management
  • Used with URP/HDRP common utility

This is the “handle system” introduced for this purpose.

5.3.1 RTHandle and resolution scale (dynamic resolution)

When performing Forward+/Post/Upscaling, the camera target is:

  • May differ from screen size
  • Apply render scale
  • May vary from eye to eye in XR.

At this time, if you directly create/manage RenderTexture with “exact pixel size,” errors will easily occur. RTHandle is a method of managing “reference (handle) + actual texture (backing)” separately for these cases.

5.4 TextureHandle: “Resource Reference” inside RenderGraph

RenderGraph internally references texture resources as TextureHandle.

  • The texture created by the graph is referenced by the handle obtained by CreateTexture()
  • External RTHandle/Texture is imported and referenced as a handle by ImportTexture()

The key is to declare “resource dependency (read/write)” in handle units.

5.5 Basic pattern for exchanging textures in RenderGraph (recommended)

In RenderGraph it usually goes one of the following:

  1. Read the camera texture provided by URP (UniversalResourceData.activeColorTexture, etc.)
  2. Create a temporary texture inside the graph (CreateTexture)
  3. Declare read/write in path (UseTexture/SetRenderAttachment)
  4. Update resources that subsequent passes will reference (e.g. replace activeColorTexture with temp)

Important differences:

  • Compatibility Mode makes it easy for “global slots” to become the center of flow.
  • In RenderGraph, “handles (dependencies)” are the center of the flow.

5.5 3 types of exchange method patterns (summary)

  1. (Legacy) CommandBuffer + GlobalTexture slot

    • Pros: Simple, many examples
    • Disadvantage: resource lifetime/dependencies scattered throughout the code (difficult to debug/optimize)
  2. RTHandle-based (URP internal style)

    • Renderer/Pass maintains resources around RTHandle
  3. RenderGraph(TextureHandle) based (current/recommended)

    • Pass declares read/write through handle
    • Graphs help with lifespan/reuse

5.6 How to answer “Where does camera color/depth texture come from?”

First thing to do in the RenderGraph route:

  • Read the Frame Data document (concept),
  • View the actual graph (phenomenon) with RenderGraph Viewer,
  • Try changing URP settings (Requirements/Depth Texture/Normal Texture) (cause).

Related official documents:

Further reading (official/authoritative sources)