Developers familiar with the Universal Render Pipeline (URP) should understand that rendering capabilities can be expanded by RenderFeature. While this approach is in line with the principles of data-driven design, practical scenarios may necessitate the dynamic addition of Render Passes. For instance, when a character in the game enters a state of death, it may be necessary to apply a post-processing effect to the screen to indicate their demise.
Texture data source: https://lindenreidblog.com/2019/01/06/color-spread-post-processing-effect-tutorial-in-unity/
However, RenderFeatures are not accessible during runtime, which means that if we want to switch between multiple RenderFeatures, the current official method provided by Unity is to switch RenderData during runtime. This requires preparing a large amount of data beforehand, which can be quite cumbersome. Of course, you can use reflection to access it, but I do not recommend doing so.
The solution presented here is referred to as DynamicAddPassFeature. It utilizes a static container named ms_addPassInterfaces to store a list of interfaces that need to listen for the AddRenderPasses event. Those who wish to receive this event can simply call AppendAddPassInterfaces directly. The relevant code is provided below:
public interface IAddPassInterface
{
void OnAddPass(ScriptableRenderer renderer,
ref RenderingData renderingData);
};
public class DynamicAddPassFeature : ScriptableRendererFeature
{
public static bool AppendAddPassInterfaces(
IAddPassInterface theInterface)
{
if ((theInterface == null) ||
(ms_addPassInterfaces.Find(theInterface) != null))
return false;
ms_addPassInterfaces.AddLast(theInterface);
return true;
}
public static bool RemoveAddPassInterfaces(
IAddPassInterface theInterface)
{
return ms_addPassInterfaces.Remove(theInterface);
}
public override void Create()
{
}
public override void AddRenderPasses(ScriptableRenderer renderer,
ref RenderingData renderingData)
{
var element = ms_addPassInterfaces.GetEnumerator();
while (element.MoveNext())
element.Current.OnAddPass(renderer, ref renderingData);
element.Dispose();
}
private static LinkedList<IAddPassInterface> ms_addPassInterfaces =
new LinkedList<IAddPassInterface>();
}
Here is an example demonstrating how to draw outlines. The relevant code is provided below:
public class DynamicOutlinePassTest : MonoBehaviour, IAddPassInterface
{
private void OnEnable()
{
DynamicAddPassFeature.AppendAddPassInterfaces(this);
m_drawOutLinePass = new DrawOutLinePass();
}
private void OnDisable()
{
DynamicAddPassFeature.RemoveAddPassInterfaces(this);
}
void IAddPassInterface.OnAddPass(ScriptableRenderer renderer,
ref RenderingData renderingData)
{
if (!enabled)
return;
renderer.EnqueuePass(m_drawOutLinePass);
}
private DrawOutLinePass m_drawOutLinePass = null;
class DrawOutLinePass : ScriptableRenderPass
{
public DrawOutLinePass()
{
renderPassEvent = RenderPassEvent.BeforeRenderingOpaques;
m_filteringSettings = new FilteringSettings(RenderQueueRange.opaque,
~0);
m_profilingSampler = new ProfilingSampler(m_profilerTag);
m_shaderTagId = new ShaderTagId("OutLine");
}
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
CommandBuffer command = CommandBufferPool.Get(m_profilerTag);
using (new ProfilingScope(command, m_profilingSampler))
{
context.ExecuteCommandBuffer(command);
command.Clear();
var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
var drawSettings = CreateDrawingSettings(m_shaderTagId,
ref renderingData, sortFlags);
context.DrawRenderers(renderingData.cullResults, ref drawSettings,
ref m_filteringSettings);
}
context.ExecuteCommandBuffer(command);
CommandBufferPool.Release(command);
}
private FilteringSettings m_filteringSettings;
private const string m_profilerTag = "Render Opaque outLines";
private ShaderTagId m_shaderTagId = ShaderTagId.none;
private ProfilingSampler m_profilingSampler = null;
}
}
From the example above, it is evident that we can dynamically add our own Render Pass during the runtime of any MonoBehaviour. This allows us to meet various needs during the game’s execution, simply by inheriting the IAddPassInterface interface.
Here you can find the corresponding GitHub repository:
沒有留言:
張貼留言