Custom shader does not work in standalone

I am currently working on a project that creates a material through the following steps:

  1. Takes string input from a user in the application
  2. Append the input as text into a Shader file that is stored in the Resources folder
  3. A class then loads the Shader and return it as a Material

It works fine in Unity Editor but I am unable to build the project as it makes use of AssetDatabase that is not present outside of Unity Editor. It returns an error during building.

string shaderText = .......
string path = Path.Combine(resDir.FullName, "Programmable_Color.shader");
File.WriteAllText(path, shaderText);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
Shader shader = Resources.Load<Shader>("Programmable_Color");
        return new Material(shader);

Can I use the same method to generate a Material in the standalone? This method will mean that the Material is generated at runtime from new Shader files from different inputs by the user. I have tried adding it to ‘Always Included Shaders’ but the Material does not get updated. Is the Material being generated the moment I build the project?

Otherwise, what alternatives do I have?

Thanks in advance!

No, you can’t re-write the literal text/code of a Shader file at runtime and then compile it as far as I know. What exactly are you trying to accomplish with the user’s input? Based on the file name of Programmable_Color, it seems more like you just want to use material.SetColor and are going about it in an incredibly bizarre way?

The code has been left behind by the previous person who worked on the project. I was told some bug is causing it to not work and I’m tasked to fix it. I’m really new at this. Here, it is creating a color appearance using the varying input parameter u,v,w.

currentObjectMeshRenderer.material = ProgrammableColor.CreateMaterial(programColorRed, programColorGreen, programColorBlue);
using UnityEngine;
using UnityEditor;
using System.IO;

public static class ProgrammableColor
{
    public static Material CreateMaterial(string red, string green, string blue)
    {

        var resDir = new DirectoryInfo(Path.Combine(Application.dataPath, "Resources"));
        if (!resDir.Exists) resDir.Create();

        string shaderText =
            "Shader \"Programmable_Color\"\n"+
            "{\n" +
                "Properties\n" +
                "{\n" +
                    "_URange(\"URange\", Vector) = (0, 1, 1, 1)\n" +
                    "_VRange(\"VRange\", Vector) = (0, 1, 1, 1)\n" +
                    "_WRange(\"WRange\", Vector) = (0, 1, 1, 1)\n" +
                    "_TRange(\"TRange\", Vector) = (0, 1, 1, 1)\n" +
                    "_Tcycle(\"Timecycle\", float) = 1\n" +
                "}\n" +
                 "SubShader\n" +
                 "{\n" +
                     "Pass\n" +
                     "{\n" +
                        "Tags{\"LightMode\" = \"ForwardBase\" }\n" +
                        "Cull Off CGPROGRAM\n" +
                        "#pragma vertex vert\n" +
                        "#pragma fragment frag\n" +
                        "uniform float4 _URange;\n" +
                        "uniform float4 _VRange;\n" +
                        "uniform float4 _WRange;\n" +
                        "uniform float4 _TRange;\n" +
                        "uniform float _Tcycle;\n" +

                        "struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; };\n" +
                        "struct vertexOutput { float4 pos : SV_POSITION; float4 posWorld : TEXCOORD0; float3 normalDir : TEXCOORD1; };\n" +
                        "vertexOutput vert(vertexInput input)\n" +
                        "{\n" +
                            "vertexOutput output;\n" +
                            "float4x4 modelMatrix = unity_ObjectToWorld;\n" +
                            "float4x4 modelMatrixInverse = unity_WorldToObject;\n" +
                            "output.posWorld = mul(modelMatrix, input.vertex);\n" +
                            "output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);\n" +
                            "output.pos = UnityObjectToClipPos(input.vertex);\n" +
                            "return output;\n" +
                        "}\n" +

                        "static const float pi = 3.14159265358;\n" +

                        "float4 frag(vertexOutput input) : COLOR \n" +
                        "{\n" +
                            "float u = (input.posWorld.x - _URange.x) / (_URange.y - _URange.x);\n" +
                            "float v = (input.posWorld.y - _URange.x) / (_URange.y - _URange.x);\n" +
                            "float w = (-input.posWorld.z - _URange.x) / (_URange.y - _URange.x);\n" +
                            "float t = cos((_Time.y * 6) / _Tcycle) * (_TRange.y - _TRange.x) + _TRange.x;\n" +

                            //User code goes here
                            "float r = " + red + ";\n" +
                            "float g = " + green + ";\n" +
                            "float b = " + blue + ";\n" +

                            "return float4(r, g, b, 1);\n" +
                        "}\n" +
                        "ENDCG\n" +
                    "}\n" +
                "}\n" +
            "}";

        string path = Path.Combine(resDir.FullName, "Programmable_Color.shader");
        File.WriteAllText(path, shaderText);

        AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
        //AssetDatabase.LoadAssetAtPath<Shader>("Resources/Programmable_Color.shader");
        //Shader shader = Shader.Find("Programmable_Color");
        //below works the same way as the 2 commented lines above
        Shader shader = Resources.Load<Shader>("Programmable_Color");
        return new Material(shader);
    }
}

7500044--923966--upload_2021-9-16_21-30-42.png

For just those parameters, that’s absolute lunacy. Expose 3 parameters in your shader and set their float value in the material. If this is some sort of tool that generates savable shaders, then maybe I could understand, but that doesn’t seem to be what you’re doing?

Do you mean taking all three parameters out of CreateMaterial, so I’ll generate a Material without any of the parameters (Which means I can even take CreateMaterial out totally, pass in my pre-generated Material). Then subsequently set their float value to that Material? Seems to me like the parameters will need to be passed to the Shader to generate colors for each fragment isn’t it? How does this work?

Its not really for generating savable shaders but allowing users (students of the school app) to observe various colors on their object when they pass in their own parameters

Yes, create a regular shader with three exposed parameters for your R/G/B and a regular material. Use SetFloat on that material, which I’ve already linked to the manual pages on, to change the parameters at runtime. You do not need to create a shader or material at runtime at all just to tweak basic shader parameters.

Thank you so so much! I have tried your suggested method and it works like a charm.

1 Like