Lets say you have 2 cubes, and have a material "mat1".
I have create a simple unlit shader for this material "mat1"
Shader "ShaderLearning/UnlitColor"
{
Properties
{
_Color("Main Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform half4 _Color;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
All you need is to change these 2 cubes to 2 different colors.
First, you apply the material mat1(with above UnlitColor Shader) to these 2 cubes, and when you try to change the color of one cube from the material inspector, the color of the other cube also changes to the same.
Hmm, interesting!! Then how can I get 2 different colors for these 2 cubes?
Firstly and the most obvious solution is, create an other material(mat2) and apply 2 materials to these 2 cubes.
This is the most obvious solution one can come up with!! but wait, What if I have 1000 cubes and need 1000 colors for those cubes? Should I go on and create 1000 materials just to change one color property?
Nope, ok let try this!!
lets apply the same material "mat1" to both the cubes and try to change the _Color property defined in the above shader through C# script. Coming back to the previous scenario, lets attach a C# script to both the cubes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MaterialPropertyBlocks : MonoBehaviour
{
void Start()
{
GiveColor();
}
public void GiveColor()
{
Color colorToGive = new Color(Random.Range(0f,1f),Random.Range(0f, 1f),Random.Range(0f, 1f));
transform.GetComponent<Renderer>().material.SetColor("_Color", colorToGive);
}
}
This script is pretty straight forward. I am trying to access _Color property from the above shader and giving 2 cubes some random color.
Execute this!!
Once you execute this script, you can see although both the cubes are using the same material "mat1", they get different color. How?
The problem with this is that in order to change the color in the shader, Unity needs to tell the GPU that this object is going to be rendered differently, and the only way it can do so is by changing the material instance. Because of this, Unity creates a copy of a material when you access renderer.material for the first time. This copy is henceforth used for that renderer1.
If you want to see this visually,
Once you play the scene and GiveColor() method gets executed, click on the cube and in the inspector tab under MeshRenderer, an instance of mat1(clone) is created for both the cubes.
Did we solve our problem then? NO!! This transform.GetComponent<Renderer>().material.SetColor("_Color", colorToGive) did no good to our problem. Indirectly we are creating a separate material for each cube again(this time unity did it for us internally).
So 1. Creating a separate material manually 2. Try to access material property and change the color value did no good. Both created extra materials and extra space is consumed.
Now what is the solution to this problem? Yes you guessed it right!! Material Property Blocks!!
MaterialPropertyBlock class should solve our problem.
MaterialPropertyBlock is used by Graphics.DrawMesh and Renderer.SetPropertyBlock. Use it in situations where you want to draw multiple objects with the same material, but slightly different properties. For example, if you want to slightly change the color of each mesh drawn. Changing the render state is not supported.
Unity's terrain engine uses MaterialPropertyBlock to draw trees; all of them use the same material, but each tree has different color, scale & wind factor.
public void GiveColor()
{
Color colorToGive = new Color(Random.Range(0f, 1f),Random.Range(0f, 1f),Random.Range(0f, 1f));
//WithOut using material Property Block!!
//transform.GetComponent<Renderer>().material.SetColor("_Color", colorToGive);
//Using Material Property Block!!
MaterialPropertyBlock mpb = new MaterialPropertyBlock();
transform.GetComponent<Renderer>().GetPropertyBlock(mpb);
mpb.SetColor("_Color", colorToGive);
transform.GetComponent<Renderer>().SetPropertyBlock(mpb);
}
So here I am creating MaterialPropertyBlock object, setting the color of the material block and then instead of accessing material form the render, i am setting PropertBlock directly to the cube.
It is important to note that setting a property block on a renderer overwrites any other data set by property blocks on that renderer. If your new property block doesn’t hold a value that was present before, that value will be reset. This means that if there are multiple scripts affecting the renderer through material property blocks, they will be interfering with each other. Therefore, you should always get the current property values first, before making any new changes.
Once you do this, unity no longer creates a clone of the material. It is just 1 material and you are changing the property values.
You can profile our findings and compare!!
Lets create 10,000 cubes and give a random color to each cube.
Lets do this using "transform.GetComponent<Renderer>().material.SetColor("_Color", colorToGive)" and then using Material Property Block, and compare the result!!
Firstly, but accessing material and then setting color, as I have already mentioned unity creates an extra material clone(for mat1(clone)) for each cube.
If you go to the profiler window and check the memory created by materials in the scene, you can see 6mb of memory is occupied by the materials.
Now, lets create the same 10,000 cubes using Material Property Block and inspect the profiler!!
You can clearly see the memory difference using Material Property Block!!
Comentarios