Remove FilmGrain textures in 2020 without disabling Post-Processing

Currently URP is adding a dozen, very large FilmGrain post-processing textures to every scene bundle in WebGL. Our clients are very sensitive to download size and this useless addition of 3MB+ can often double our bundle sizes. It’s a big problem because we have to fight so hard to optimize every inch of every texture, then this comes along and blows our budget completely.

This has been discussed with much frustration in other threads generally, as well as in at least one bug fix.

However, we want to use SOME Post-Processing, like Bloom and HBAO, so a fix that requires disabling all Post-Processing will not help. I also understand that the fix to do that will not be back-ported to 2020, so that also wouldn’t help us.

Our urgent need is to be able to just remove the FilmGrain textures from our builds. We used to be able to solve the problem by simply removing or shrinking those textures, but Unity’s gotten more and more “helpful” such that we can no longer do either of those things–it keeps repairing and adding them back in.

Can anyone please help us find a way to either remove, shrink, or compress these textures?

No straightforward way of doing this as far as I know since all the relevant fields and functions are marked internal. I have done something similar but I have a modified URP package for other reasons so the internal API wasn’t a problem. That said I tried doing it without modifying too much of the codebase so I think the process below should work.

What I have is a custom class that inherits from IPreprocessBuildWithReport. Then in the OnPreprocessBuild function I can get access to the URP assets in the project. The textures you are talking about are referenced in the post process data scriptable object which should be accessible from the ForwardRendererData object that the URP asset has via its m_RendererDataList field. Then just null out the references to those textures and that should be enough for the build to not package them. Of course nulling them out could open up the possibility of issues in the build if something happens to use it so just watch out for that.

The issue is m_RendererDataList is marked internal so you would need reflection to get access to it and then you should be able to do what I did above.

No access to pipeline's RendererDataList has an example of using reflection to access m_RendererDataList.

I think I remember seeing some other post that had a script for reducing the file size of those textures but don’t remember where it may be.

Hope it helps!

Sounds good–thank you! I’ll see if one of our smart guys can digest what you’ve done there and get it working for us.

I’ve also created a bug report on the issue. I’m sure it’s a super easy thing for Unity to address–we’ll just have to see if if they decide to take the time for it.

We had a script that used to do something similar to what you’re describing for 2019, but 2020 got too smart for it. Here’s that script just in case it’s helpful to someone as a reference.

RemoveURPTextures.cs

using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

public class RemoveURPTextures : IPreprocessBuildWithReport
{
    public int callbackOrder { get { return 0; } }
    public void OnPreprocessBuild(BuildReport report)
    {
        Debug.Log("MyCustomBuildProcessor.OnPreprocessBuild for target " + report.summary.platform + " at path " + report.summary.outputPath);

        string packagesPath = Application.dataPath.Replace("/Assets", "") + "/Library/PackageCache";
        Debug.Log($"packagesPath: {packagesPath}");
        string[] directories = Directory.GetDirectories(packagesPath);

        foreach (string directory in directories)
        {
            Debug.Log($"directory: {directory}");
            if (directory.Contains("com.unity.render-pipelines.universal"))
            {
                Debug.Log($"Modifying entries in: {directory}");
                string texturesPath = Path.Combine(directory, "Textures/FilmGrain");
                string[] files = Directory.GetFiles(texturesPath, "*.meta");
                foreach (string filename in files)
                {
                    Debug.Log($"Modifying entry: {filename}");
                    string[] lines = File.ReadAllLines(filename);

                    for (int i = 0; i < lines.Length; i++)
                    {
                        lines[i] = lines[i].Replace("maxTextureSize: 2048", "maxTextureSize: 32");
                    }

                    File.WriteAllLines(filename, lines);
                }
            }
        }
        //throw new BuildFailedException("aa");

        foreach (string directory in directories)
        {
            Debug.Log($"directory: {directory}");
            if (directory.Contains("com.unity.render-pipelines.universal"))
            {
                Debug.Log($"Modifying entries in: {directory}");
                string texturesPath = Path.Combine(directory, "Textures/SMAA");
                string[] files = Directory.GetFiles(texturesPath, "*.meta");
                foreach (string filename in files)
                {
                    Debug.Log($"Modifying entry: {filename}");
                    string[] lines = File.ReadAllLines(filename);

                    for (int i = 0; i < lines.Length; i++)
                    {
                        lines[i] = lines[i].Replace("maxTextureSize: 2048", "maxTextureSize: 32");
                    }

                    File.WriteAllLines(filename, lines);
                }
            }
        }
    }
}

So we’re finding that this isn’t helping us with Asset Bundles because OnPostprocessBuild isn’t affecting those. Hmmm…

@xgonzal2 , It looks like we’re getting access to the m_RendererDataList that you referred to, but could not get the ForwardRendererData. Any tips?

The renderer data list is an array of ScriptableRendererData which the ForwardRendererData derives from. So you can iterate through the array and type cast each entry to ForwardRendererData while checking for null. If it isn’t null then you have a reference to a ForwardRendererData and that has a public field for the post process data. That post process data is what stores all the textures you are trying to remove. I added a snippet of what my script does below. I should say that I haven’t tried this with asset bundles or addressables yet so I can’t guarantee it works there. In a build without any of those I can see none of these textures make it to the build through the editor log after the build is done.

        private void StripBuiltinUnityPostProcessTextures(ref List<UniversalRenderPipelineAsset> urps)
        {
            foreach (UniversalRenderPipelineAsset urp in urps)
            {
                foreach (ScriptableRendererData srd in urp.m_RendererDataList)
                {
                    UniversalRendererData data = srd as UniversalRendererData;
                    if (data == null)
                        continue;

                    data.postProcessData.textures = null;
                }
            }
        }

Boom! In the end I guess it was pretty simple, but we were very unfamiliar with it, so it took some time to figure it out, but your tips worked and it shrunk the textures in our main build 60%. Nice! Thank you!

We thought it would be in the OnPostprocessBuild, but when we look at the debugs, it seems it’s actually happening in the OnPreprocessBuild, so we actually removed that call (but I left it in here just for reference). Hopefully this can help someone else along the way

using UnityEngine;
using UnityEngine.Rendering.Universal;

#if UNITY_EDITOR && UNITY_WEBGL
using UnityEditor.Build;
using UnityEditor.Build.Reporting;

public class PostDataProcessor : IPostprocessBuildWithReport, IPreprocessBuildWithReport
{
    public int callbackOrder { get { return 0; } }

    public void OnPreprocessBuild(BuildReport report) => PostProcessData();

    public void OnPostprocessBuild(BuildReport report) => PostProcessData();

    void PostProcessData()
    {
        ScriptableRendererData[] rendererDataList = (ScriptableRendererData[])typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(UniversalRenderPipeline.asset);

        for (int i = 0; i < rendererDataList.Length; i++)
        {
            ForwardRendererData frd = (ForwardRendererData)rendererDataList[i];
            if (frd != null)
            {
                frd.postProcessData.textures = null;
            }
        }
    }

}
#endif
4 Likes

Has anyone seem to solve this issue of blue noise and filmgrain , its taking about 3mb in my project ?

Is something about the above solution not working for you?

For anyone who is still struggling, I resize textures in C:\Users\admin\AppData\Local\Unity\cache\packages\packages.unity.com\com.unity.render-pipelines.universal@10.10.1 and Library/ShaderCache/…
It works perfectly

This did not work as of September 2023.

Something like this works. I went to the package manager, found the textures → Open in Explorer and then resized them outside of Unity.

First time I try they rebuilt as the package ‘validated’. Second try seemed to stick.

I’ve been trying the manual resizing trick but Unity keeps randomly reinstalling the originals. There has to be a solution for this. I have a very strict max uncompressed build size target of 30mb. My “used” assets are only 15mb but Unity is adding an additional 27mb!!!

2 Likes

Did you find a solution for this? I’m having the same issue. I tried removing the texture references inside PostProcessData, but that messes up references elsewhere.