How to get shader source code from script?

The main issue I trying to solve is that shader graphs are not working with unity’s UI Mask (not maskable). This can be easily fixed by adding a few lines of code into generated shader code. What I try to do, is to automate the process: select shader graph asset in project → choose menu button → source code extracted from shader, lines added, new *.shader file created with modified code.
So, that’s the problem: how to get compiled code from script?

I found out how to get particular Pass code (HLSL Program), but what about ShaderLab part?

What I can do, is to grab generated code from Temp folder, or copy it from buffer. But for this, I need to press “View Generated Shader” or “Copy Shader”…

1 Like

+1

I would be happy to have this functionality at hand.

1 Like

Personally I ended up with the next flow: choose shaderGraph file, press “Copy Shader” button, then go to my custom menu and launch my custom function - which will grab shader’s code from clipboard, parse and modify it, and then creates a new shader file, and imports in via assets database. I’ll ask the permission to share the code from my customer, let’s see, what he’ll answers.

Hi, could anybody automate this?

Until there is an official API you can use this (2021.3.18f1):

private static string GenerateShaderCode(string shaderAssetPath, string shaderName = null)
{
    Type editorType =
        Type.GetType("UnityEditor.ShaderGraph.ShaderGraphImporterEditor, Unity.ShaderGraph.Editor")!;
    Type generatorType =
        Type.GetType("UnityEditor.ShaderGraph.Generator, Unity.ShaderGraph.Editor")!;
    Type modeType =
        Type.GetType("UnityEditor.ShaderGraph.GenerationMode, Unity.ShaderGraph.Editor")!;

    MethodInfo method = editorType.GetMethod(
        "<OnInspectorGUI>g__GetGraphData|2_0",
        BindingFlags.NonPublic | BindingFlags.Static
    )!;
    shaderName ??= Path.GetFileNameWithoutExtension(shaderAssetPath);
    var importer = AssetImporter.GetAtPath(shaderAssetPath);
    object graphData = method.Invoke(null, new object[] { importer });

    // new Generator(graphData, null, GenerationMode.ForReals, assetName, null, true);
    object forReals = ((FieldInfo)modeType.GetMember("ForReals")[0]).GetValue(null);
    object generator = Activator.CreateInstance(
        generatorType, graphData, null,
        forReals, shaderName, null, true
    );
    object shaderCode = generatorType
        .GetProperty("generatedShader", BindingFlags.Public | BindingFlags.Instance)!
        .GetValue(generator);

    return (string)shaderCode;
}

It’s hideous, but it works :smile:

4 Likes

Some how I can’t call the method by “g__GetGraphData|2_0”
So I implement a more complex one and support ShaderGraph 14.0

    private static object GetGraphData(string shaderAssetPath)
    {
        var importer = AssetImporter.GetAtPath(shaderAssetPath);

        var textGraph = File.ReadAllText(importer.assetPath, Encoding.UTF8);
        var graphObjectType = Type.GetType("UnityEditor.Graphing.GraphObject, Unity.ShaderGraph.Editor")!;

        // var graphObject = CreateInstance<GraphObject>();
        var graphObject = ScriptableObject.CreateInstance(graphObjectType);

        graphObject.hideFlags = HideFlags.HideAndDontSave;
        bool isSubGraph;
        var extension = Path.GetExtension(importer.assetPath).Replace(".", "");
        switch (extension)
        {
            case "shadergraph":
                isSubGraph = false;
                break;
            case "ShaderGraph":
                isSubGraph = false;
                break;
            case "shadersubgraph":
                isSubGraph = true;
                break;
            default:
                throw new Exception($"Invalid file extension {extension}");
        }
        var assetGuid = AssetDatabase.AssetPathToGUID(importer.assetPath);
       
        // graphObject.graph = new GraphData { assetGuid = assetGuid, isSubGraph = isSubGraph, messageManager = null };
        var graphObject_graphProperty = graphObjectType.GetProperty("graph")!;
        var graphDataType = Type.GetType("UnityEditor.ShaderGraph.GraphData, Unity.ShaderGraph.Editor")!;
        var graphDataInstance = Activator.CreateInstance(graphDataType);
        graphDataType.GetProperty("assetGuid")!.SetValue(graphDataInstance, assetGuid);
        graphDataType.GetProperty("isSubGraph")!.SetValue(graphDataInstance, isSubGraph);
        graphDataType.GetProperty("messageManager")!.SetValue(graphDataInstance, null);
        graphObject_graphProperty.SetValue(graphObject, graphDataInstance);

        // MultiJson.Deserialize(graphObject.graph, textGraph);
        // = MultiJson.Deserialize<JsonObject>(graphObject.graph, textGraph, null, false);
        var multiJsonType = Type.GetType("UnityEditor.ShaderGraph.Serialization.MultiJson, Unity.ShaderGraph.Editor")!;
        var deserializeMethod = multiJsonType.GetMethod("Deserialize")!;
        var descrializeGenericMethod = deserializeMethod.MakeGenericMethod(graphDataType);
        descrializeGenericMethod.Invoke(null, new object[] { graphDataInstance, textGraph, null, false });

        // graphObject.graph.OnEnable();
        graphDataType.GetMethod("OnEnable")!.Invoke(graphDataInstance, null);

        // graphObject.graph.ValidateGraph();
        graphDataType.GetMethod("ValidateGraph")!.Invoke(graphDataInstance, null);

        // return graphData.graph
        return graphDataInstance;
    }

    private static string GenerateShaderCode(string shaderAssetPath, string shaderName = null)
    {
        Type generatorType =
            Type.GetType("UnityEditor.ShaderGraph.Generator, Unity.ShaderGraph.Editor")!;
        Type modeType =
            Type.GetType("UnityEditor.ShaderGraph.GenerationMode, Unity.ShaderGraph.Editor")!;

        shaderName ??= Path.GetFileNameWithoutExtension(shaderAssetPath);

        object graphData = GetGraphData(shaderAssetPath);

        // new Generator(graphData, null, GenerationMode.ForReals, assetName, target:null, assetCollection:null, humanReadable: true);
        object forReals = ((FieldInfo)modeType.GetMember("ForReals")[0]).GetValue(null);
        object generator = Activator.CreateInstance(
            generatorType,
            new object[] { graphData, null, forReals, shaderName, null, null, true }
        );
        object shaderCode = generatorType
            .GetProperty("generatedShader", BindingFlags.Public | BindingFlags.Instance)!
            .GetValue(generator);

        return (string)shaderCode;
    }
1 Like

For your problem,the best way to do may be just to adjust the shadergraph template i guess.