正好电脑看奥本海默,全程尿点十足,就一边看一边把之前整合HDRP遇到的问题说一下。
那就是RTHandle的未知问题,这是官方对RTHandle的说明:
unity RTHandle
源代码如下:
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Rendering
{
/// <summary>
/// A RTHandle is a RenderTexture that scales automatically with the camera size.
/// This allows proper reutilization of RenderTexture memory when different cameras with various sizes are used during rendering.
/// <seealso cref="RTHandleSystem"/>
/// </summary>
public class RTHandle
{
internal RTHandleSystem m_Owner;
internal RenderTexture m_RT;
internal Texture m_ExternalTexture;
internal RenderTargetIdentifier m_NameID;
internal bool m_EnableMSAA = false;
internal bool m_EnableRandomWrite = false;
internal bool m_EnableHWDynamicScale = false;
internal string m_Name;
internal bool m_UseCustomHandleScales = false;
internal RTHandleProperties m_CustomHandleProperties;
/// <summary>
/// By default, rtHandleProperties gets the global state of scalers against the global reference mode.
/// This method lets the current RTHandle use a local custom RTHandleProperties. This function is being used
/// by scalers such as TAAU and DLSS, which require to have a different resolution for color (independent of the RTHandleSystem).
/// </summary>
/// <param name="properties">Properties to set.</param>
public void SetCustomHandleProperties(in RTHandleProperties properties)
{
m_UseCustomHandleScales = true;
m_CustomHandleProperties = properties;
}
/// <summary>
/// Method that clears any custom handle property being set.
/// </summary>
public void ClearCustomHandleProperties()
{
m_UseCustomHandleScales = false;
}
/// <summary>
/// Scale factor applied to the RTHandle reference size.
/// </summary>
public Vector2 scaleFactor { get; internal set; }
internal ScaleFunc scaleFunc;
/// <summary>
/// Returns true if the RTHandle uses automatic scaling.
/// </summary>
public bool useScaling { get; internal set; }
/// <summary>
/// Reference size of the RTHandle System associated with the RTHandle
/// </summary>
public Vector2Int referenceSize { get; internal set; }
/// <summary>
/// Current properties of the RTHandle System. If a custom property has been set through SetCustomHandleProperties method, it will be used that one instead.
/// </summary>
public RTHandleProperties rtHandleProperties { get { return m_UseCustomHandleScales ? m_CustomHandleProperties : m_Owner.rtHandleProperties; } }
/// <summary>
/// RenderTexture associated with the RTHandle
/// </summary>
public RenderTexture rt { get { return m_RT; } }
/// <summary>
/// RenderTargetIdentifier associated with the RTHandle
/// </summary>
public RenderTargetIdentifier nameID { get { return m_NameID; } }
/// <summary>
/// Name of the RTHandle
/// </summary>
public string name { get { return m_Name; } }
/// <summary>
/// Returns true is MSAA is enabled, false otherwise.
/// </summary>
public bool isMSAAEnabled { get { return m_EnableMSAA; } }
// Keep constructor private
internal RTHandle(RTHandleSystem owner)
{
m_Owner = owner;
}
/// <summary>
/// Implicit conversion operator to RenderTargetIdentifier
/// </summary>
/// <param name="handle">Input RTHandle</param>
/// <returns>RenderTargetIdentifier representation of the RTHandle.</returns>
public static implicit operator RenderTargetIdentifier(RTHandle handle)
{
return handle != null ? handle.nameID : default(RenderTargetIdentifier);
}
/// <summary>
/// Implicit conversion operator to Texture
/// </summary>
/// <param name="handle">Input RTHandle</param>
/// <returns>Texture representation of the RTHandle.</returns>
public static implicit operator Texture(RTHandle handle)
{
// If RTHandle is null then conversion should give a null Texture
if (handle == null)
return null;
Debug.Assert(handle.m_ExternalTexture != null || handle.rt != null);
return (handle.rt != null) ? handle.rt : handle.m_ExternalTexture;
}
/// <summary>
/// Implicit conversion operator to RenderTexture
/// </summary>
/// <param name="handle">Input RTHandle</param>
/// <returns>RenderTexture representation of the RTHandle.</returns>
public static implicit operator RenderTexture(RTHandle handle)
{
// If RTHandle is null then conversion should give a null RenderTexture
if (handle == null)
return null;
Debug.Assert(handle.rt != null, "RTHandle was created using a regular Texture and is used as a RenderTexture");
return handle.rt;
}
internal void SetRenderTexture(RenderTexture rt)
{
m_RT = rt;
m_ExternalTexture = null;
m_NameID = new RenderTargetIdentifier(rt);
}
internal void SetTexture(Texture tex)
{
m_RT = null;
m_ExternalTexture = tex;
m_NameID = new RenderTargetIdentifier(tex);
}
internal void SetTexture(RenderTargetIdentifier tex)
{
m_RT = null;
m_ExternalTexture = null;
m_NameID = tex;
}
/// <summary>
/// Get the Instance ID of the RTHandle.
/// </summary>
/// <returns>The RTHandle Instance ID.</returns>
public int GetInstanceID()
{
if (m_RT != null)
return m_RT.GetInstanceID();
else if (m_ExternalTexture != null)
return m_ExternalTexture.GetInstanceID();
else
return m_NameID.GetHashCode(); // No instance ID so we return the hash code.
}
/// <summary>
/// Release the RTHandle
/// </summary>
public void Release()
{
m_Owner.Remove(this);
CoreUtils.Destroy(m_RT);
m_NameID = BuiltinRenderTextureType.None;
m_RT = null;
m_ExternalTexture = null;
}
/// <summary>
/// Return the input size, scaled by the RTHandle scale factor.
/// </summary>
/// <param name="refSize">Input size</param>
/// <returns>Input size scaled by the RTHandle scale factor.</returns>
public Vector2Int GetScaledSize(Vector2Int refSize)
{
if (!useScaling)
return refSize;
if (scaleFunc != null)
{
return scaleFunc(refSize);
}
else
{
return new Vector2Int(
x: Mathf.RoundToInt(scaleFactor.x * refSize.x),
y: Mathf.RoundToInt(scaleFactor.y * refSize.y)
);
}
}
/// <summary>
/// Return the scaled size of the RTHandle.
/// </summary>
/// <returns>The scaled size of the RTHandle.</returns>
public Vector2Int GetScaledSize()
{
if (scaleFunc != null)
{
return scaleFunc(referenceSize);
}
else
{
return new Vector2Int(
x: Mathf.RoundToInt(scaleFactor.x * referenceSize.x),
y: Mathf.RoundToInt(scaleFactor.y * referenceSize.y)
);
}
}
#if UNITY_2020_2_OR_NEWER
/// <summary>
/// Switch the render target to fast memory on platform that have it.
/// </summary>
/// <param name="cmd">Command buffer used for rendering.</param>
/// <param name="residencyFraction">How much of the render target is to be switched into fast memory (between 0 and 1).</param>
/// <param name="flags">Flag to determine what parts of the render target is spilled if not fully resident in fast memory.</param>
/// <param name="copyContents">Whether the content of render target are copied or not when switching to fast memory.</param>
public void SwitchToFastMemory(CommandBuffer cmd,
float residencyFraction = 1.0f,
FastMemoryFlags flags = FastMemoryFlags.SpillTop,
bool copyContents = false
)
{
residencyFraction = Mathf.Clamp01(residencyFraction);
cmd.SwitchIntoFastMemory(m_RT, flags, residencyFraction, copyContents);
}
/// <summary>
/// Switch the render target to fast memory on platform that have it and copies the content.
/// </summary>
/// <param name="cmd">Command buffer used for rendering.</param>
/// <param name="residencyFraction">How much of the render target is to be switched into fast memory (between 0 and 1).</param>
/// <param name="flags">Flag to determine what parts of the render target is spilled if not fully resident in fast memory.</param>
public void CopyToFastMemory(CommandBuffer cmd,
float residencyFraction = 1.0f,
FastMemoryFlags flags = FastMemoryFlags.SpillTop
)
{
SwitchToFastMemory(cmd, residencyFraction, flags, copyContents: true);
}
/// <summary>
/// Switch out the render target from fast memory back to main memory on platforms that have fast memory.
/// </summary>
/// <param name="cmd">Command buffer used for rendering.</param>
/// <param name="copyContents">Whether the content of render target are copied or not when switching out fast memory.</param>
public void SwitchOutFastMemory(CommandBuffer cmd, bool copyContents = true)
{
cmd.SwitchOutOfFastMemory(m_RT, copyContents);
}
#endif
}
}
我自己把它理解为一个动态的RT,和RT一样的使用,当然写代码用起来很容易。却碰到一个问题,以多迭代高斯滤波为例,使用RTHandle会造成纹理“发黄”,如下:
当时没感觉有问题,后面同事说这效果好奇怪,好黄?!我就感觉确实。起初我以为我shader颜色计算出问题了,查了半天不是。我百度google查了仅有不多有关RTHandle的帖子,并没找到相关问题,不过有个博主说复制一份RTHandle使用,我尝试做了一下,果然ok了。
//复制一份原始纹理
private RTHandle copySource;
public override void Setup()
{
if (Shader.Find(shaderName) != null)
{
mat = new Material(Shader.Find(shaderName));
copySource = RTHandles.Alloc(Vector2.one, filterMode: FilterMode.Bilinear, dimension: TextureDimension.Tex2DArray);
}
else
Debug.LogError($"Unable to find shader '{shaderName}'. Post Process Volume GuassBlurPostProcessVolume is unable to load.");
}
public override void Render(CommandBuffer cmd, HDCamera camera, RTHandle source, RTHandle destination)
{
if (mat == null || !isBlur.value)
{
return;
}
mat.SetFloat("_Brightness", brightness.value);
//复制一份纹理
//注意:由原始的纹理数组传递到单纹理,原始的纹理数组封装了多种平台下纹理集合
HDUtils.BlitCameraTexture(cmd, source, copySource);
//使用复制RTH还是原始RTH
RenderTexture rtinput = isCopyRT.value ? copySource : source;
int rtwid = rtinput.width / downSample.value;
int rthei = rtinput.height / downSample.value;
mat.SetTexture("_InputTex", rtinput);
//对pass0进行迭代,增加高斯滤波次数和效果
for (float it = 0; it < iterations.value; it += 0.2f)
{
mat.SetFloat("_BlurSpread", 1.0f + it * blurSpread.value);
//滤波采样一次
RenderTexture rt1 = RenderTexture.GetTemporary(rtwid, rthei, 0);
cmd.Blit(rtinput, rt1, mat, 0);
//叠加滤波
cmd.Blit(rt1, rtinput);
RenderTexture.ReleaseTemporary(rt1);
}
//采样迭代后的纹理
mat.SetTexture("_IterTex", rtinput);
cmd.Blit(rtinput, destination, mat, 1);
}
public override void Cleanup()
{
CoreUtils.Destroy(mat);
RTHandles.Release(copySource);
}
虽然不明白为什么会这样,但确实解决了问题,如下:
如果大家写渲染功能,碰到类似的问题,不妨Copy一份RTHandle。