- Home /
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!
Answer by Tudor · Mar 26, 2015 at 12:22 PM
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.
Hey, thanks for the info. Did you have any success yet? I have the same problem, I want to create a runtime material and assign it a normal map, but the normal map only becomes visible in the game/editor view once ithe material is expanded/rolled out in the inspector (after assigning it to a gamneobject). Sadly, I ahve no experience with writing or modifying shaders at all, I took a look at the Standard shader source code and it had lines like
#pragma shader_feature _NOR$$anonymous$$AL$$anonymous$$AP
which according to the manual would mean that it gets included in the compiling process, right? Well, I guess I was wrong because I can'T figure out how to make it work :(`
Edit: The solution is in this thread:
#pragma shader_feature _NOR$$anonymous$$AL$$anonymous$$AP
means it only gets compiled into the build if there was a material in the scene which used a Normalmap. #pragma multi_compile _NOR$$anonymous$$AL$$anonymous$$AP
would make sure all combinations including Normalmaps will be included no matter what, regardless if you ever need them or not. But as was pointed out in your forum link, it's best to use shader feature with the described method.
Also thanks for finding that forum post!
Answer by yanik · Mar 25, 2015 at 06:26 PM
Hi. The material have to disable the keyword for heightmap :
material.DisableKeyword("_NORMALMAP");
Thanks for that. It pointed me in the right direction. I'm, gonna elaborate on it though as it's not as easy as it sounds.
Answer by saulth · Jul 06, 2017 at 11:50 PM
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 https://unity3d.com/get-unity/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 :).
Thanks for this! It helped me get a dynamic material editor working.