Hello,
I would like to enable or disable some render features at runtime, is it possible?
Thanks.
Hello,
I would like to enable or disable some render features at runtime, is it possible?
Thanks.
bump
Hello,
Here is my code that works with URP 8+
public static class UniversalRenderPipelineAssetExtensions
{
public static ScriptableRendererFeature DisableRenderFeature<T>(this UniversalRenderPipelineAsset asset) where T : ScriptableRendererFeature
{
var type = asset.GetType();
var propertyInfo = type.GetField("m_RendererDataList", BindingFlags.Instance | BindingFlags.NonPublic);
if (propertyInfo == null)
{
return null;
}
var scriptableRenderData = (ScriptableRendererData[])propertyInfo.GetValue(asset);
if (scriptableRenderData != null && scriptableRenderData.Length > 0)
{
foreach (var renderData in scriptableRenderData)
{
foreach (var rendererFeature in renderData.rendererFeatures)
{
if (rendererFeature is T)
{
rendererFeature.SetActive(false);
return rendererFeature;
}
}
}
}
return null;
}
}
Somewhere in my code when I want to disable a render feature:
private void DisableRenderFeature<T>(UniversalRenderPipelineAsset asset) where T : ScriptableRendererFeature
{
var renderFeature = asset.DisableRenderFeature<T>();
if (renderFeature != null)
{
m_DisabledRenderFeatures.Add(renderFeature);
}
}
I use a list of disabled render features to enable them when the application quit, because it’s a scriptable object and it’s saved automatically. Another thing could be to clone the current renderer but I don’t know how to do that.
You could do something similar to the AdditionalCameraData if the switching of features is on a per-camera basis and read off the data from the affected camera as to enqueue the passes for the feature or not.
You can also switch to another Forward Renderer
_camData = YourCamera.GetUniversalAdditionalCameraData();
_camData.SetRenderer(index); // switch to renderer without/with render feature.
I would assume switching the entire Renderers could result in a bit more significant performance impact than changing a specific field alone, but I might be wrong.
Switching Renderers is cheap on a per-camera basis (it is just switching the index for the renderer list) as the URP render loops on a per-camera basis rather than a per-renderer basis.
And besides neither the Renderer or the Camera retains information pertaining each other for cleanup code to cleanup during switching.
The only downfall to use a switching Renderer style is that one is limited to very small number of available renderers to switch (I believe it to be 7 or 8).
ScriptableRendererFeature supports SetActive(). I made a criminally simple script that allows you to enable or disable your defined URP render features at runtime and in the editor:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;
[System.Serializable]
public struct RenderFeatureToggle
{
public ScriptableRendererFeature feature;
public bool isEnabled;
}
[ExecuteAlways]
public class RenderFeatureToggler : MonoBehaviour
{
[SerializeField]
private List<RenderFeatureToggle> renderFeatures = new List<RenderFeatureToggle>();
[SerializeField]
private UniversalRenderPipelineAsset pipelineAsset;
private void Update()
{
foreach (RenderFeatureToggle toggleObj in renderFeatures)
{
toggleObj.feature.SetActive(toggleObj.isEnabled);
}
}
}
It may be a bit unsafe, I dont know what will happen if you enable or disable a render feature you defined in the list through the editor that is not supported by the currently active RenderPipelineAsset. Also it doesn’t save anything to disk with a SO, but thats all additional logic that shouldn’t be too much trouble to implement. For me this is working fine in it’s current form for now.
Thank you so much, this works like a charm!
Did this end up throwing an issues with you? I am trying to black out a screen between loads so a VR player doesn’t puke.
A variation of @demonixis approach to make it more versatile. I am using this to access various features. It offers getting RendererFeatures by string too (use with caution). Using strings we can even get to some hidden (internal) feature types like “ScreenSpaceAmbientOcclusion”.
Not sure why they call these the “scriptable” render pipelines when we have to use reflection to get to the stuff we need. In the old legacy built-in renderer it was all freely accessible via post processing profiles sigh.
Usage:
// Yeah, accessing via strings sucks, but "this is the way" in Unity it seems.
// There is a generic SetRendererFeatureActive<T> too but the ScreenSpaceAmbientOcclusion
// type is declared as "internal" by Unity, thus we can not use it that way.
UniversalRenderPipelineUtils.SetRendererFeatureActive("ScreenSpaceAmbientOcclusion", true);
var isActive = UniversalRenderPipelineUtils.IsRendererFeatureActive("ScreenSpaceAmbientOcclusion");
// Thanks to: https://discussions.unity.com/t/800351
using System.Reflection;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public static class UniversalRenderPipelineUtils
{
private static FieldInfo RenderDataList_FieldInfo;
static UniversalRenderPipelineUtils()
{
try
{
var pipelineAssetType = typeof(UniversalRenderPipelineAsset);
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
RenderDataList_FieldInfo = pipelineAssetType.GetField("m_RendererDataList", flags);
}
catch (System.Exception e)
{
Debug.LogError("UniversalRenderPipelineUtils reflection cache build failed. Maybe the API has changed? \n" + e.Message);
}
}
public static ScriptableRendererData[] GetRendererDataList(UniversalRenderPipelineAsset asset = null)
{
try
{
if (asset == null)
{
asset = (UniversalRenderPipelineAsset)GraphicsSettings.currentRenderPipeline;
}
if (asset == null)
{
#if UNITY_EDITOR
Debug.LogWarning("GetRenderFeature() current renderpipleline is null.");
#endif
return null;
}
if (RenderDataList_FieldInfo == null)
{
#if UNITY_EDITOR
Debug.LogWarning("GetRenderFeature() reflection failed to get m_RendererDataList field.");
#endif
return null;
}
var renderDataList = (ScriptableRendererData[])RenderDataList_FieldInfo.GetValue(asset);
return renderDataList;
}
catch
{
// Fail silently if reflection failed.
return null;
}
}
public static T GetRendererFeature<T>(UniversalRenderPipelineAsset asset = null) where T : ScriptableRendererFeature
{
var renderDataList = GetRendererDataList();
if (renderDataList == null || renderDataList.Length == 0)
return null;
foreach (var renderData in renderDataList)
{
foreach (var rendererFeature in renderData.rendererFeatures)
{
if (rendererFeature is T)
{
return rendererFeature as T;
}
}
}
return null;
}
public static ScriptableRendererFeature GetRendererFeature(string typeName, UniversalRenderPipelineAsset asset = null)
{
var renderDataList = GetRendererDataList();
if (renderDataList == null || renderDataList.Length == 0)
return null;
foreach (var renderData in renderDataList)
{
foreach (var rendererFeature in renderData.rendererFeatures)
{
if (rendererFeature == null)
continue;
if (rendererFeature.GetType().Name.Contains(typeName))
{
return rendererFeature;
}
}
}
return null;
}
public static bool IsRendererFeatureActive<T>(UniversalRenderPipelineAsset asset = null, bool defaultValue = false) where T : ScriptableRendererFeature
{
var feature = GetRendererFeature<T>(asset);
if (feature == null)
return defaultValue;
return feature.isActive;
}
public static bool IsRendererFeatureActive(string typeName, UniversalRenderPipelineAsset asset = null, bool defaultValue = false)
{
var feature = GetRendererFeature(typeName, asset);
if (feature == null)
return defaultValue;
return feature.isActive;
}
public static void SetRendererFeatureActive<T>(bool active, UniversalRenderPipelineAsset asset = null) where T : ScriptableRendererFeature
{
var feature = GetRendererFeature<T>(asset);
if (feature == null)
return;
feature.SetActive(active);
}
public static void SetRendererFeatureActive(string typeName, bool active, UniversalRenderPipelineAsset asset = null)
{
var feature = GetRendererFeature(typeName, asset);
if (feature == null)
return;
feature.SetActive(active);
}
}
Thanks! That saves my life