KhronosGroup / UnityGLTF

Runtime glTF 2.0 Loader for Unity3D
MIT License
1.79k stars 482 forks source link

Shader Variants / Huge ammount of Shader Variants #749

Open MarcusGT076 opened 2 months ago

MarcusGT076 commented 2 months ago

Hello there,

so i have a problem with the shader variants in the build. I cant include the two PBRGraphs in always include. If i do so he will compile over 200.000 thousands of shader variants. So i decided to track down the shader when i hit play and save the Shader as a ShaderVariantCollection from the graphic settings. Still he compile a huge ammount of shader variants in the build. So the shader ends up with 180MB in the build. So i think this is far too much in a webgl build only for the shader ?

Can you assist here or is it a bug ?

Cheers, Marcus

MarcusGT076 commented 1 month ago

Hello everybody,

in WebGl Build i managed to strip the Shader, UnityPBRGraph, from 180MB to nearly 2MB. I will share my code how i have achieved this. So first in PlayMode i made a ShaderVariantCollection and recorded it through the Graphics-Settings in Unity. Than use the ShaderVariantCollection for the Preloaded Shader section in the Graphic-Settings.

When you will use the script i only tested it on WebGl that the shader is still working with those stripped keywords. And take care of that you have to make a clean Build when you will use the script, otherwise he will not use this script, When something is broken with youre shader you can uncomment the case lines.

When you have other keywords in WebGl than this you have to analyze youre keywords that youre using. You can achive this by adding a line in the foreach loop at the beginning with Debug.Log(key.name); Than you can analyze those keywords and set them in the case lines within, when youre sure you dont need them.

And create a script with these lines of codes:

public class StripShaderVariants : IPreprocessShaders
{
    public int callbackOrder { get { return 0; } }
    private string foundKeywordSet;
    private bool valueFound;

    public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
    {
        if (shader.name == "UnityGLTF/PBRGraph")
        {
            //for (int i = 0; i < shader.keywordSpace.keywordCount; i++)
            for(int i = 0; i < data.Count;i++)
            {
                ShaderKeyword[] keywords = data[i].shaderKeywordSet.GetShaderKeywords();
                foreach (var key in keywords)
                {
                    switch (key.name)
                    {
                        case "_ADDITIONAL_LIGHTS_VERTEX":
                        case "DIRLIGHTMAP_COMBINED":
                        //case "LIGHTMAP_ON":
                        case "LIGHTMAP_SHADOW_MIXING":
                        case "SHADOWS_SCREEN":
                        //case "_ADDITIONAL_LIGHTS":
                        case "_ADDITIONAL_OFF":
                        case "POINT_COOKIE":
                        case "_CASTING_PUNCTUAL_LIGHT_SHADOW":
                            valueFound = true;
                        break;
                        default:
                            valueFound = false;
                            Debug.Log("No Shader Keyword has been found and not stripped!");
                        break;
                    }
                    if (valueFound)
                    {
                        data.RemoveAt(i);
                        --i;
                        break;
                    }
                }  
            }
        }
        //Debug.Log(data.Count);
        if (shader.name == "UnityGLTF/PBRGraph" && snippet.passName == "BuiltIn ForwardAdd")
        {
            //Debug.Log("Stripping pass: " + snippet.passName + " from shader: " + shader.name);
            data.Clear();
        }
        if (shader.name == "UnityGLTF/PBRGraph" && snippet.passName == "BuiltIn Deferred")
        {
            //Debug.Log("Stripping pass: " + snippet.passName + " from shader: " + shader.name);
            data.Clear();
        }
    }
}

So Cheers everybody :) Marcus

hybridherbst commented 1 month ago

That's strange – these keywords should be stripped by Unity automatically. Thanks for the code snippet.

MarcusGT076 commented 1 month ago

yes it should be but it is not. Youre welcome :)

ArDevKarl commented 1 month ago

@MarcusGT076 Can you give an example on how to do this for UWP. I have the same problem, but this code won't compile for a UWP build

MarcusGT076 commented 1 month ago

@ArDevKarl I dont know how to do it in UWP but i can tell you what i did so far. Maybe its also working in the UWP lets give it a try. So first before you start to play youre scene goto ProjectSettings/Graphics. There is a section Preloaded Shaders. There is also a clear button, so clear everything. Than start youre application in play mode. After that he should show you Currently tracked: x Shaders, x total Variants.

Save this to an Asset with the button. Search for youre ShaderVariantCollection that you saved at mark it as a adressable. The next step is to mark also the shader that you use as a adressable. The ShaderVariantCollection and the shader itself has to be in the same group of the adressables. Even you can strip out some keywords that youre sure that you will not need in the ShaderVariantCollection.

Than in addition use my script. Create a editor folder next to the asset folder and put it there. Watch out when you put my script in the editor folder, you have to make a clean build because he needs to run the editor-script on building the scene. But i am sure youre will need other keywords with the urp. My keywords that i used in the case function was targeting webgl in the Built-In pipeline.

If youre not sure with my script you can try to do this workflow first without this script.

When everything is working you can do a normal build out of it. Its because of the shader cache he will use. I am not sure if the workflow will be 100% the same but it should be similar in some cases.

Let me know if its helping :) Cheers, Marcus

ArDevKarl commented 1 month ago

@MarcusGT076 Ok turns out, I've not needed any additional scripting to strip unneeded shaders. The Unity documentation about this is a bit confusing. My mistake was to add the shaders to "Always Include".

Thank you, for the answer. :)