Last time we were cutting shapes in meshes, but the performance of it was mediocre at best, and let’s not start on limitations of it. This time we will check how we can do it by using textures.
My guess here is that by making cuts in texture, we will get better performance. I also hope results that we will be able to present using texture rather than mesh cutting will be better as well.
But that’s a theory, so let’s not waste any more time and let’s check it!
The idea ?
So the idea here will be pretty simple. We will create a texture for a quad and will paint it by changing alpha on parts of the texture.
We can also reuse some parts of the input, that we made in the previous post.
Coding ??
First of all we need to create some base variables for our TextureController. We will define how many pixels per unit we want, and we will calculate the rest.
using UnityEngine; /// <summary> /// This class control the texture and removing of its parts. /// </summary> [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class TextureController : MonoBehaviour { // Amount of pixels per unit on the scene. public int pixelsPerUnit = 50; // Size of single pixel on the texture. private float pixelSize; // Texture width. private int textureWidth; // Texture height. private int textureHeight; // Mesh width. private float meshWidth; // Mesh height private float meshHeight; // Reference to the MeshRenderer. private MeshRenderer meshRenderer; /// <summary> /// Unity method called on object creation. /// </summary> private void Awake() { // Setting reference to the Mesh Renderer. meshRenderer = GetComponent<MeshRenderer>(); // Gathering values for variables. meshWidth = transform.localScale.x; meshHeight = transform.localScale.y; // Calculating texture variables. pixelSize = 1f / pixelsPerUnit; textureWidth = Mathf.RoundToInt(meshWidth / pixelSize); textureHeight = Mathf.RoundToInt(meshHeight / pixelSize); } /// <summary> /// Unity method called on the first frame. /// </summary> void Start() { GenerateTexture(); } /// <summary> /// Generating texture for the mesh. /// </summary> private void GenerateTexture() { } }
Now, we have to generate texture from code and assign it to the material on our quad.
Problems ?
There are two ways to assign a color to the texture. We can iterate over each pixel and use Texture2D.SetPixel() to set color. We can also create the whole array of colors and we can assign that to the texture by using Texture2D.SetPixels().
This got me curious, so I’ve checked which will be better to use. After making both versions I become clear that using an array of colors is a better choice here, so we will stick to that.
using UnityEngine; /// <summary> /// This class control the texture and removing of its parts. /// </summary> [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class TextureController : MonoBehaviour { ... /// <summary> /// Generating texture for the mesh. /// </summary> private void GenerateTexture() { // Texture initialization. var newTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.RGBA32, false); // Setting texture variables. newTexture.alphaIsTransparency = true; newTexture.filterMode = FilterMode.Point; // Creating array of colors for the texture. var colors = new Color[textureWidth * textureHeight]; for (int y = 0; y < textureHeight; y++) { for (int x = 0; x < textureWidth; x++) { colors[y * textureWidth + x] = Color.white; } } // Setting colors in the texture and applying changes. newTexture.SetPixels(colors); newTexture.Apply(false); // Assigning texture to the material. meshRenderer.material.mainTexture = newTexture; } }
Another thing that we needed to make is to disable mipmaps in our texture. They have a terrible habit of interfering with the rendering in the scene. At least it was a case in my project. ?
At that point, we should have a texture that we generated ourselves. This won’t work yet with cutting, but it’s a good start.
Now let’s get to the input as we will need to make some changes to the RemovalPointer to make it work with the TextureController.
using UnityEngine; /// <summary> /// This class control the texture and removing of its parts. /// </summary> [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class TextureController : MonoBehaviour { ... /// <summary> /// Erases the texture part. /// </summary> /// <param name="position">Position in world space.</param> /// <param name="range">Removing range.</param> public void EraseTexturePart(Vector2 position, float range) { } }
using UnityEngine; /// <summary> /// Removal Pointer. /// Calculates world position of user input and passes it to the texture. /// </summary> public class RemovalPointer : MonoBehaviour { // Reference to the input layer to attach actions. public InputLayer inputLayer; // Reference to the mesh controller. public TextureController texture; // Reference to the main camera. public Camera main; // Removal radius. public float hitRadius = 2; /// <summary> /// Unity method called when component is enabled. /// </summary> private void OnEnable() { inputLayer.PointerDown += OnPointerDown; inputLayer.DragBegin += OnDragBegin; inputLayer.Drag += OnDrag; inputLayer.DragEnd += OnDragEnd; } /// <summary> /// Unity method called when component is disabled. /// </summary> private void OnDisable() { inputLayer.PointerDown -= OnPointerDown; inputLayer.DragBegin -= OnDragBegin; inputLayer.Drag -= OnDrag; inputLayer.DragEnd -= OnDragEnd; } /// <summary> /// Action on begin drag. /// </summary> /// <param name="screenPosition">Screen position.</param> private void OnDragBegin(Vector2 screenPosition) { RemovePartsOfTextureAt(screenPosition); } /// <summary> /// Action on drag. /// </summary> /// <param name="screenPosition">Screen position.</param> private void OnDrag(Vector2 screenPosition) { RemovePartsOfTextureAt(screenPosition); } /// <summary> /// Action on end drag. /// </summary> /// <param name="screenPosition">Screen position.</param> private void OnDragEnd(Vector2 screenPosition) { RemovePartsOfTextureAt(screenPosition); } /// <summary> /// Action on pointer down. /// </summary> /// <param name="screenPosition">Screen position.</param> private void OnPointerDown(Vector2 screenPosition) { RemovePartsOfTextureAt(screenPosition); } /// <summary> /// Removes parts of the texture at Screen Position. /// </summary> /// <param name="screenPosition">Screen position.</param> private void RemovePartsOfTextureAt(Vector2 screenPosition) { // Calculating world position. var worldPoint = main.ScreenToWorldPoint(screenPosition); var hitPoint = new Vector2(worldPoint.x, worldPoint.y); // Passing data to the texture to remove its parts. texture.EraseTexturePart(hitPoint, hitRadius); } }
Great! Now it’s just a matter of finishing the erasing method! We will do it similarly as we did with the mesh removing, but of course, this time with the texture.
using UnityEngine; /// <summary> /// This class control the texture and removing of its parts. /// </summary> [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class TextureController : MonoBehaviour { ... /// <summary> /// Erases the texture part. /// </summary> /// <param name="position">Position in world space.</param> /// <param name="range">Removing range.</param> public void EraseTexturePart(Vector2 position, float range) { // Converting world position to the pixel indexes. var meshStart = new Vector2(-meshWidth / 2, -meshHeight / 2); var translatedPosition = position - meshStart; translatedPosition /= pixelSize; // Converting range to the pixel indexes. var translatedRange = range / pixelSize; // Calculating square in which pixels are being removed. // Width int startX = Mathf.CeilToInt(Mathf.Max(0, translatedPosition.x - translatedRange)); int endX = Mathf.FloorToInt(Mathf.Min(textureWidth, translatedPosition.x + translatedRange)); int diffX = Mathf.Max(0, endX - startX); // Height int startY = Mathf.CeilToInt(Mathf.Max(0, translatedPosition.y - translatedRange)); int endY = Mathf.FloorToInt(Mathf.Min(textureHeight, translatedPosition.y + translatedRange)); int diffY = Mathf.Max(0, endY - startY); // Getting reference to the texture. var texture = meshRenderer.material.mainTexture as Texture2D; // Getting array of colors from the texture to change. var colors = texture.GetPixels(startX, startY, diffX, diffY); var centerPos = translatedPosition; for (int y = 0; y < diffY; y++) { for (int x = 0; x < diffX; x++) { // Checking if element is in removing range. var pointPos = new Vector2(startX + x, startY + y); if (Vector2.Distance(pointPos, centerPos) > translatedRange) { continue; } // Removing color from the array. colors[y * diffX + x] = new Color(0, 0, 0, 0); } } // Applying changes back to the texture. texture.SetPixels(startX, startY, diffX, diffY, colors); texture.Apply(false); } }
Awesome! As we have that done, I think it’s time to see the result!
The result ?
I think it looks better than what we had with the mesh! It also works much faster than mesh!
What do you think about it? Will it be useful for you? Let me know in the comment section below!
If you want to get notified on future content, sign up for the newsletter!
As always, the whole project is available at my public repository. ?
And I hope to see you next time! ?