Accessing Properties At Run-time
Both of the example shaders utilize various properties to define their effects and up until now these properties were set in either the editor or the shader itself, however it is possible to access these properties at run-time via the Unity API.
The API has several setters and getters built in for assigning values to a material using a script and they all utilize a string to access a specific shader property. This string is the name assigned to the property in the shader program, i.e. to change the main texture of a material you would use the following function.
This syntax is similar for all material properties including Colors, Vectors, Matrices, Ints, and Floats, and is shared for the getters where instead of a new value you supply a default value, the important take away is that your script must contain a string with the name of the property you wish to access.
It’s important to understand the difference between unity materials and instanced materials, in short when you affect a material you change every mesh that uses that material where as if you only change an instanced material your changes will only propagate to the mesh that has that material instance. To illustrate the difference both methods will be described below.
Non-instanced Material Change
There are two ways to change material property values without instancing the material, store a reference to a material in a script, or access the shared material of a game object through the Renderer component.
Using the first method, storing a material reference, simply create a material field in your script then assign the material you wish to control using the editor. After the material is assigned you can access its properties and changes will propagate to all game objects in the scene.
The second method requires the script to be attached to a game object using the material you wish to control. Then the script can utilize the GetComponent function to get the object’s Renderer component, from this component the sharedMaterial property can be accessed which stores a reference to your material.
As an example say you want to change the color of all game enemies, using the enemy material, to a new color. Using the first method would be better in this case since the material we want to control is known and can be stored ahead of time. In which case the following function call would control the material color.
In the second method in order to change the enemy color the material must first be accessed. Its good practice to store a reference to the Renderer component in the script, to assign this reference use the following syntax
renderer = GetComponent<Renderer>();
After storing the reference to the renderer the next step is to get the shared material which can be accessed and stored like so.
enemyMaterial = renderer.sharedMaterial;
Then finally we can use the SetColor function just like in the first method.
Instanced Material Change
An important aside to changing materials in this way is that it will increase the draw calls your game must perform, meaning for every material you instance your game will have to draw separately which will take longer for each frame to process. Also material instancing only takes place the first time a script changes a material property per game object.
For example say a script changes the color of a game object, Unity then makes an instance of that object’s material then alters the _DiffuseColor property value and assigns that material instance its own draw call separate from all other objects that use that material. Now whenever that game object changes color it can alter its own material without affecting other game objects.