Imagine a scene with ten simple "cubes" all the same.
They each have the same material "M1" which has the simple standard shader, and, a simple PNG as the texture.
If you go to the material, and adjust the tiling,
You can conveniently flip the texture around for different looks.
Note that of course this will change all ten of the cubes all at once.
Is it possible to modify the standard shader, so that, simply,
on each object, which uses the material
the shader randomly changes the tiling (and, say, the offset)
again, I mean on a "per object" basis; in the example each of the ten would be randomly different.
(So: one cube would show tiling -1,1, one cube would show tiling -1,-1 and so on ... each cube different, although all using the same material, only one material exists.)
Note,
(1) it's totally trivial to generate a few different versions of the material, each with different tiling, and randomly select one of those for each cube. that's not the way to go
(2) note that if you vary a material, it of course makes more than one copy. you can't have thousands and thousands of materials.
The solution is to have (one) shader which knows to vary (say, randomly) the offset, inside the shader - on each object it is working on. (ie on a per specific object basis)
That's what I'm asking about here.
To rotate a texture in a shader, you have to rotate its texture coordinates. This solution should work for both 2D and 3D. uv is the texture coordinate you're initially using. pivot is the point the uv will rotate about.
I noticed that you really really want to do this with nothing but a shader. You can't do this with a shader alone. The issue is not being able to generate a random number in a shader. The problem is being able to do it once. I haven't found a way to do so and don't think you can.
This is a problem that should be tackled with a code on the C# side.
(1) it's totally trivial to generate a few different versions of the material, each with different tiling, and randomly select one of those for each cube. that's not the way to go
(2) note that if you vary a material, it of course makes more than one copy. you can't have thousands and thousands of materials.
Not a problem at-all. This is what MaterialPropertyBlock
is used for. It allows you to modify a shader property without creating new instance of that material.
"Use it in situations where you want to draw multiple objects with the same material, but slightly different properties."
MaterialPropertyBlock
The code below would have cause many instances of the material to be created:
void Start()
{
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
Vector2 tile = new Vector2(tileX, tileY);
meshRenderer.material.SetTextureScale("_MainTex", tile);
}
With MaterialPropertyBlock
, this issue is solved. Material copy is not made. Since you care about performance, you should also use Shader.PropertyToID
:
void Start()
{
int propertyID = Shader.PropertyToID("_MainTex_ST");
meshRenderer = gameObject.GetComponent<MeshRenderer>();
int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
Vector2 tile = new Vector2(tileX, tileY);
MaterialPropertyBlock matPropBlock = new MaterialPropertyBlock();
//Get the current MaterialPropertyBlock settings
meshRenderer.GetPropertyBlock(matPropBlock);
//Assign the new tile value
matPropBlock.SetVector(propertyID, tile);
//matPropBlock.SetVector(Shader.PropertyToID("_MainTex_ST"), tile);
//Apply the modified MaterialPropertyBlock back to the renderer
meshRenderer.SetPropertyBlock(matPropBlock);
}
If this is done once in the game, it doesn't really make sense to attach the script to each GameObject. Just attach it t to one empty GameObject only, find all the objects by tag then run the code on each one.
void Start()
{
GameObject[] objs = GameObject.FindGameObjectsWithTag("YourObjTag");
int propertyID = Shader.PropertyToID("_MainTex_ST");
for (int i = 0; i < objs.Length; i++)
{
MeshRenderer meshRenderer = objs[i].GetComponent<MeshRenderer>();
int tileX = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
int tileY = (UnityEngine.Random.Range(1, 3) == 1) ? 1 : -1;
Vector2 tile = new Vector2(tileX, tileY);
MaterialPropertyBlock matPropBlock = new MaterialPropertyBlock();
//Get the current MaterialPropertyBlock settings
meshRenderer.GetPropertyBlock(matPropBlock);
//Assign the new tile value
matPropBlock.SetVector(propertyID, tile);
//Apply the modified MaterialPropertyBlock back to the renderer
meshRenderer.SetPropertyBlock(matPropBlock);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With