Building a Unity 5 Standard Material at runtime

I know that with the new Unity5 standard material / standard shader, if you don’t populate a field (for example Normal map) then the shader is compiled without Normalmap calculations, for efficiency.

What I’m fuzzy on is how does this work if you want to make a new standard material at runtime?

If I do a Material mat = new Material() and set its shader to Shader.Find("Standard"), and then proceed to assign an Albedo texture and a Heightmap texture, will then my build at runtime compile an efficient Standard Material?

What if I already have a Standard Material with a Heightmap, and then at runtime I decide to remove its Heightmap entirely; will the standard shader then recompile to the version that doesn’t calculate heightmaps?

Does unity maintain an internal array of precompiled versions of the Standard Shader, one for each combination of features that can be turned on or off?
Or do I have to compile shaders manually? don’t think that’s possible.

Thanks for any insight!

Hi. The material have to disable the keyword for heightmap :

material.DisableKeyword(“_NORMALMAP”);

Alright, so thanks to Yanik I was able to research this properly.

Unity does compile a standard shader which holds “all combinations” or “variants” of features included in the standard material. It does this via the #pragma multi_compile and #pragma shader_feature directives. Read more on them on their manual page, especially the part about the “Difference between shader_feature and multi_compile”.

These directives use keywords to determine what bits of shader will be used at runtime to render the object. (e.g.: #pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON)

You can then specify which of these elements you want to use, with the Shader.EnableKeyword("") and Shader.DisableKeyword(""), like Yanik said.

However, if the shader is using shader_feature as opposed to multi_compile, then the unused variants of shader_feature will not be included into your game Build.

I had a quick look at unity5’s new standard shader source code and I see that it uses shader_feature a lot. I haven’t tested this yet but it must mean that you must make a build where you have one dummy object with a standard material using all the features you will require at runtime, in order for the full shader to make it into the build.

[EDIT] As Cherro pointed out linking to the unity forums post, when you do:

Material newMat = new Material(Shader.Find("Standard"));
newMat.CopyPropertiesFromMaterial(dummyObjMat); 

the newMat, by default will have all the properties disabled, even though the shader variants -were- included in the build, and dummyObjMat was using them. So to activate them, simply use:

newMat.EnableKeyword("_NORMALMAP"); //etc.

The comprehensive way to do this is to use the code written by Unity that is executed when you make changes to the material in the Editor. The code is available in the builtin_shaders-XXX.zip file (available for download from Download Archive). Then, open up the Editor/StandardShaderGUI.cs file and copy out the five functions at the bottom (SetupMaterialWithBlendMode, GetSmoothnessMapChannel, SetMaterialKeywords, MaterialChanged, SetKeyword). Also copy the enums at the top of the class (WorkflowMode, BlendMode, SmoothnessMapChannel) and put in your own class. Then you want to call MaterialChanged() with the WorkflowMode.Metallic on your Material that you modified. Works identically to in-Editor changes :).