Hey all,
Could someone please shed some light how I would get access to a renderer feature at runtime?
Previously I had linked the asset directly with a serialized field, but now I have put the asset into bundles with addressables, this simply creates an instance of it rather than the current asset in use.
Many thanks!
EDIT
I’ve managed to find a way to get this done via reflection to access the private property (why are the renderer features private?) Is there a way to do this without hacking it?
var renderer = (GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset).GetRenderer(0);
var property = typeof(ScriptableRenderer).GetProperty("rendererFeatures", BindingFlags.NonPublic | BindingFlags.Instance);
List<ScriptableRendererFeature> features = property.GetValue(renderer) as List<ScriptableRendererFeature>;
ScriptableRenderFeatures are ScriptableObjects, and would be serialized as an asset. You should simply be able to reference that asset directly and skip the reflection all together.
Yup, and that’s exactly what I was doing prior to them being bundled with addressables.
When I bundled them, the serialised link wouldn’t point to the instance in memory, so it would create a new instance of it, and any changes had no effect. ♂️
I’m doing the reflection hack too. Just wait until you want to modify the settings of any of those at runtime (eg SSAO).I understand the purpose of making things internal if those things actually are internal, but if you can modify them in the editor, then they’re not really internal anymore are they?
My guess as to why it’s internal is “corporate politics” although it’s purely speculation. Every exposed property requires documentation in multiple languages (English and Chinese at a minimum) and maybe additional code reviews. And if they add something late in a dev cycle, there might not be time to send it off o the documentation teams.
Ah yes, that’s my fault for not reading fully. Apologies.
Thought: you could have the renderer feature implement something like a singleton pattern, or perhaps place itself into a statically held array. The asset itself could store the index it’s placed at. Then, instead of storing the renderer features asset itself as an addressable, have a ‘token’ ScriptableObject which maps to the same ID.
This is all just a workaround of course, but seems less hacky than Reflection. It’s also a pattern that could applied more broadly to work around this quirk.
Plausible, yes. Reasonable… well it’s been more than a year and accessing custom Render Features to adjust their parms at runtime is still troublesome.
Ona related note, are you keeping a reference to each quality levels’ render assets and just sniffing the current quality level. each time you need to make a change?
var renderer = (GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset).GetRenderer(0);
var property = typeof(ScriptableRenderer).GetProperty("rendererFeatures", BindingFlags.NonPublic | BindingFlags.Instance);
List<ScriptableRendererFeature> features = property.GetValue(renderer) as List<ScriptableRendererFeature>;
foreach (var feature in features)
{
if (feature.GetType() == typeof(BlurKawaseURP))
{
(feature as BlurKawaseURP).Settings.downsample = 4;
}
}
This is worked for me. But I encountered an issue where I couldn’t access this feature in the Awake method; I need to retrieve it in real-time when making modifications.
Coming from a Unity 6 project and now trying to adapt for a 2022 project.
In Unity 6 the class UniversalRendererData is serializable so you can simply drag the reference of the asset and simply access the rendererFeatures.
Example case to create to create an instance material of a FullScreenPassRendererFeature
public UniversalRendererData rendererData;
public FullScreenPassRendererFeature postPass;
public Material postSharedMaterial;
public Material postIntanceMaterial;
private void Awake()
{
postPass= rendererData.rendererFeatures.Where(x => x is FullScreenPassRendererFeature).Select(x => x as FullScreenPassRendererFeature).FirstOrDefault();
postSharedMaterial = postPass.passMaterial;
postIntanceMaterial = new Material(postSharedMaterial);
postPass.passMaterial= postIntanceMaterial;
}
private void OnDestroy()
{
postPass.passMaterial = postSharedMaterial;
}
In Unity 2022 not only the UniversalRendererData is not serialized out of the box but also the rendererFeatures is “inaccessible due to protection levels” so the above solution of fetching the active pipeline asset/renderer and using Reflection got it working.
Encountered this in Unity 2022 as well, but had my own way of solving it. If it’s your intention to simply access some exposed variables on your full-screen renderer material and modify them, there’s a vastly simpler way to access them through shader globals.
For example, I have a simple fullscreen renderer feature/material set up that handles brightness, set by the end user. In Shader Graph, all you have to do is take your variable you’d like to modify, and override your property declaration to be “Global”. In my case, it’s a simple float called _GlobalBrightness.