Proper way to change URP RendererFeature at Runtime

I want to add SSAO settings to ingame graphic settings menu. It seems I need to use reflection to access Renderer Feature fields. I managed to do this however since all URP assets are ScriptableObject. So when I change fields on runtime in Editor, Fields are changed after game stopped. I don’t want to change original asset.

I achieve this with instantiate all ScriptableObjects. Everything seems worked for now, but I’m afraid some side effects later. TLDR; I need some expert advices for proper way to change RendererFeature fields without change original asset.

private void LoadRendererData()
        {
            var rendererAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
            if (rendererAsset == null)
            {
                Debug.LogError("Renderer asset is null!");
                return;
            }

            //Instantiate and set URP asset
            QualitySettings.renderPipeline =
                GraphicsSettings.renderPipelineAsset =
                    _currentRenderPipelineAsset = Instantiate(rendererAsset);
           
            if (_renderDataListField == null)
            {
                _renderDataListField = typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList",
                    BindingFlags.NonPublic | BindingFlags.Instance);
            }

            //Instantiate and set renderer data
            var rendererDataList = _renderDataListField.GetValue(_currentRenderPipelineAsset) as ScriptableRendererData[];
            rendererDataList[0] = _currentRendererData = Instantiate(rendererDataList[0]);
           
            //Find SSAO
            var ssaoFeature = _currentRendererData.rendererFeatures
                .FirstOrDefault(x => x.GetType().Name == "ScreenSpaceAmbientOcclusion");

            if (ssaoFeature == null) return;
           
            //Instantiate new SSAO
            var newFeature = Instantiate(ssaoFeature);
           
            //Replace SSAO with new one
            _currentRendererData.rendererFeatures.Remove(ssaoFeature);
            _currentRendererData.rendererFeatures.Add(newFeature);
                       
            //Should I call these???
            ssaoFeature.Dispose();
            newFeature.Create();
        }
2 Likes

Did you ever find a solution to this? It’s annoying for me because every time I run my game in the editor, it changes my URP renderer asset, and version control picks it up so I have to revert to avoid a bunch of unwanted changed in version control.

Actually no, I had to solve in a different way. If you wondering thats my solution:

I thought it wouldn’t be correct to add and remove features at runtime, so I worked as follows:

I created URP Data and URP Asset for High, Medium, and Low settings. They all have the same RendererFeatures, but some are different or disabled depending on the level. For example, in my case:

The problem arises with Custom settings. I created a URP Data and URP Asset for Custom as well. Again, I added all the RendererFeatures. Each time I switched from predefined settings to Custom, I set the differences one by one using reflection:

public void SetCustomGraphicQuality(
            ScreenResolution screenResolution,
            DisplayMode displayMode,
            bool vsync,
            MsaaQuality antialiasing,
            float renderScale,
            GraphicsLevel lighting,
            bool softShadows,
            GraphicsLevel ssao,
            GraphicsLevel anisotropicTexture,
            GraphicsLevel particle,
            GraphicsLevel lodQuality)
        {
            GraphicsQuality = GraphicsLevel.Custom;

            var qualityLevel = _graphicsPreset.CustomQualityLevel;
          
            QualitySettings.SetQualityLevel(qualityLevel, true);
          
            //Screen Resolution
            ScreenResolution = screenResolution;
          
            //Display Mode
            DisplayMode = displayMode;
          
            //VSync
            VSync = vsync;
          
            //Antialiasing
            Antialiasing = antialiasing;
          
            //Render Scale
            RenderScale = renderScale;
          
            //Lighting
            Lighting = lighting;
          
            //Soft Shadows
            SoftShadows = softShadows;
          
            //SSAO
            SSAO = ssao;
          
            //Anisotropic Texture
            AnisotropicTexture = anisotropicTexture;
          
            //Particle
            Particle = particle;
          
            //LOD Quality
            LODQuality = lodQuality;
          
            UpdateAllValues();
        }

There is a bug in Unity, at least in version 2021.3, where setting values in URP assets at runtime does not register as a change. However, even if it did, since the changes would only be in the Custom renderer feature, discarding them is easy.

And also If you want to check it out how It works, there is our game demo live on steam: Inn Tycoon Demo.

Why not just change the camera renderer ?

You can set multiple renderers with various combos of effects and toggle them at will.

In my effects there is always a scene controller to save and set individually the effect state, so this is just bad design from Unity side that lock controls of the effects to the renderer only.

All renderer features should be looking at a scene controller existence and grab from there, or from a volume entry maybe.

I suggest not doing this. Perhaps you can refer to this GitHub: