Dynamic Texture Painting

Rodrigo Fernandez Diaz
5 min readFeb 25, 2022

--

Introduction:

Games are trending to be more and more customizable by its users everyday. and there are a ton of options regarding this factors.

I found particulary interesting and easy to do trick for dynamically painting a mesh texture and keep it for later use.

You could use this texture as seen in the example for full customization or with selected prefabs as decals or templetes with some optional features.

The perfect example of what you can achive is what the guys on APB:Reloaded made for their game.

PreRequisites:

  • Work around with 3D models and its textures in unity.
  • What a UV Map and a Render Texture are.
  • Unity 5.X or Unity 4.X Pro

Tackling the problem:

The main situations faced when solving this problem are:

  1. The relation between the UV Map and the point of interaction.
  2. How to dynamically paint this new texture.
  3. How to save the texture for later use.

Pointing at the UV Map:

To simulate painting the mesh, we have to find a way to paint the texture of our material of the mesh in real time.

To do this we will be using render textures. The camera of the render texture will be looking at a quad with the uv map of our model.

The quad in the background should have “Unlit Texture” as shader in its material to avoid wierd coloring.

This way anything that is placed in front of our camera will be added the texture.

Now, we will create a material to use the texture with, we will name this material canvasMaterial.

This material is used by our mesh, and it can have any effect over it, the only requirement is a slot for our painted texture of course, for this example we will be using the Standard Shader.

After adding the material to our mesh the next part will be figuring out how to place stuff in front of this camera to simulate painting over it. For this we will be using Raycasting and a wonderful unity function.

bool HitTestUVPosition(ref Vector3 uvWorldPosition)
{
RaycastHit hit;
Vector3 mousePos=Input.mousePosition;
Vector3 cursorPos = new Vector3 (mousePos.x, mousePos.y, 0.0f);
Ray cursorRay=sceneCamera.ScreenPointToRay (cursorPos);
bool hitSuccess=false;
if (Physics.Raycast(cursorRay,out hit,200))
{
MeshCollider meshCollider = hit.collider as MeshCollider;
if (meshCollider != null && meshCollider.sharedMesh != null)
{
Vector2 pixelUV = new Vector2(hit.textureCoord.x,hit.textureCoord.y);
uvWorldPosition.x=pixelUV.x;
uvWorldPosition.y=pixelUV.y;
uvWorldPosition.z=0.0f;
hitSuccess=true;
}
}
return hitSuccess;
}

With this function we can determine which part of the UV map are we hitting with the RayCast, so now we can place things in our dynamic texture!

Dynamic Painting:

Now if you haven’t figured it out already, we will be instantiating stuff in front of our render camera to simulate painting over it, now the trick will be doing in it in a clean way and to be able to blend our colors when painting.

The first thing to note is what will we be instantiating, which will be the following image

To avoid having too many draw calls, we will be using sprites, in this way we can change our brush instance color without creating a material instance to allow unity to batch this sprites together.

The fact that our brush instance is faded and transparent, allows us to simulate the color merging with each other when painting.

To avoid having too many instances of this brush in our scene its when things get a little bit tricky, the main problem is, after doing this step there is no way to undo our mesh painting.

First we will determine a certain number of brush instance allowed in our scene (In this example we have 1000 brushes), when we reach that number of instantiated brushes we will then merge our current texture with the things we have painted so far, after merging we will have to clean our brushes.

public RenderTexture canvasTexture;
int brushCounter=0; //Increases for every brush instantiated
void MergeTexture()
{
RenderTexture.active = canvasTexture;
int width=canvasTexture.width;
int height=canvasTexture.height;
Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false);
tex.ReadPixels (new Rect (0, 0, width, height), 0, 0);
tex.Apply ();
RenderTexture.active = null;
//Put the painted texture as the base
baseMaterial.mainTexture =tex;
//Clear all brushes
foreach(Transform child in brushContainer.transform)
{
Destroy(child.gameObject);
}
//Reset how many brushes in the scene
brushCounter=0;
}

This process will take very little time to do but its sometimes able to be noticed (depends on our texture resolution mostly), but this will allow uss to continue painting indefinitely.

Saving the texture:

After we got our painting covered, and our texture is getting merged correctly the next optional step is to save this texture to a file for later use.

Since we have a Texture2D we can use the next method to save it to a file:

IEnumerator SaveTextureToFile(Texture2D savedTexture)
{
string fullPath=System.IO.Directory.GetCurrentDirectory();
System.DateTime date = System.DateTime.Now;
string fileName = "CanvasTexture.png";
if (!System.IO.Directory.Exists(fullPath))
{
System.IO.Directory.CreateDirectory(fullPath);
}
var bytes = savedTexture.EncodeToPNG();
System.IO.File.WriteAllBytes(fullPath+fileName, bytes);
yield return null;
}

Call the function as a corutine (For saving it on the background):

StartCoroutine ("SaveTextureToFile");

Tips and tricks:

  • You can project a cursor on the mesh (like in the demo) by placing it on the UV map at real time, just be careful to hide it before saving!
  • You can add your brushes instances to a parent object to be cleared more easily and have a cleaner scene.
  • For placing decals, you can replace the projected cursor with the decal, and instead of the brush instance just place the decal.
  • You can simulate erasing by painting in the same color of the initial base of the mesh.
  • This method is meant to be used by end users, or to produce image file that match with the painted elements.
  • Any model can be painted as long as it has a mesh collider and a UVs mapped properly, you only need the mesh to use a material we will create.
  • This method works best when the UVs of the object are not mirrored and have a big space between elements (To prevent color overlapping when painting)

Conclusion:

Painting our mesh in this way will allow us to have a lot of freedom and will have very clean results for the end users, however is not very efficient so its mostly focused to be the main feature when present.

Also after merging our texture it wont allow us to rollback the changes made so far.

However the fact that we can place any kind of decal or sprite in our texture allow for very fun customizing of our mesh, and also saving this changes to be used in other scenes allow it to be used as a tool of a more complex system.

Downloads:

Unity AssetStore (Free): Link

GitHub Project: Link

--

--