Generate mesh from one-color texture

Viewed 3920

I make a code to be able to draw and generate a sprite of this drawing. So I get a sprite with white background and my drawing (which is in a different color).

My question : How could I remove the white background at runtime ?(with C# code)

My problem is : I want to generated mesh using the drawing, but with white background I have 4 vertices (the fourth corners of the sprite) and I want to get all the vertices from the real shape I draw on my sprite (so much more than 4 vertices)

My current idea is to convert the drawing into having a transparent background and then use unity's sprite packer to generate a mesh from that.

My project: It’s a game, where we can create his own game circuit : user draw a black and white sprite —> I convert it to a mesh with collider and generated the new game circuit.

I already thin to clean all white pixels, but I don't think I will get many vertices with that technic.

Thanks for help, Axel the real shape

4 Answers
using System.IO;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
public class scri : MonoBehaviour
{
    // For saving the mesh------------------------ 
    public KeyCode saveKey = KeyCode.F12;
    public string saveName = "SavedMesh";

    // Concerning mesher--------------------------
    public GameObject mesher; //require

    public List<Vector3> vertices;
    public  List<int> triangles;

    public Vector3 point0;
    public Vector3 point1;
    public Vector3 point2;
    public Vector3 point3;
    
    public int loop;
    public float size;

    public Mesh meshFilterMesh;
    public Mesh meshColliderMesh;

    // Sprite work
    public Color[] pixels;

    public Texture2D newTexture;  
    public Texture2D oldTexture;  //require

    private Sprite mySprite;
    private SpriteRenderer spriteRenderer;

    public int pathCount;

    public GameObject displayerComponent; //require

    public PolygonCollider2D polygonColliderAdded; //require

    void Start()
    {
        // Mesher
        vertices = new List<Vector3> (); 
        triangles = new List<int> (); 
        meshFilterMesh= mesher.GetComponent<MeshFilter>().mesh;
        meshColliderMesh= mesher.GetComponent<MeshCollider>().sharedMesh;
        size = 10; // lenght of the mesh in Z direction
        loop=0;

        // Sprite
        pixels = oldTexture.GetPixels();
        newTexture =new Texture2D(oldTexture.width,oldTexture.height,TextureFormat.ARGB32, false);
        spriteRenderer = gameObject.AddComponent<SpriteRenderer>();
        
        ConvertSpriteAndCreateCollider (pixels);
        BrowseColliderToCreateMesh (polygonColliderAdded);
        
    }

    void Update()
    {
        // Save if F12 press
        if (Input.GetKeyDown(saveKey)){SaveAsset();}
    }

    public void ConvertSpriteAndCreateCollider (Color[] pixels) {
        for (int i = 0 ; i < pixels.Length ; i++ ) 
        { 
            // delete all black pixel (black is the circuit, white is the walls)
            if ((pixels[i].r==0 && pixels[i].g==0 && pixels[i].b==0 && pixels[i].a==1)) {
                pixels[i] = Color.clear;
            }
        }
        // Set a new texture with this pixel list
        newTexture.SetPixels(pixels);
        newTexture.Apply();

        // Create a sprite from this texture
        mySprite = Sprite.Create(newTexture, new Rect(0, 0, newTexture.width, newTexture.height), new Vector2(10.0f,10.0f), 10.0f, 0, SpriteMeshType.Tight,new Vector4(0,0,0,0),false);

        // Add it to our displayerComponent
        displayerComponent.GetComponent<SpriteRenderer>().sprite=mySprite;

        // Add the polygon collider to our displayer Component and get his path count
        polygonColliderAdded = displayerComponent.AddComponent<PolygonCollider2D>();

    }

    // Method to browse the collider and launch makemesh
    public void BrowseColliderToCreateMesh (PolygonCollider2D polygonColliderAdded){
        //browse all path from collider
        pathCount=polygonColliderAdded.pathCount;
        for (int i = 0; i < pathCount; i++)
        {
            Vector2[] path = polygonColliderAdded.GetPath(i);

            // browse all path point
            for (int j = 1; j < path.Length; j++)
            {
                if (j != (path.Length - 1)) // if we aren't at the last point
                {
                point0 = new Vector3(path[j-1].x ,path[j-1].y ,0);
                point1 = new Vector3(path[j-1].x ,path[j-1].y ,size);
                point2 = new Vector3(path[j].x ,path[j].y ,size);
                point3 = new Vector3(path[j].x ,path[j].y ,0);
                    MakeMesh(point0,point1,point2,point3);

                } 
                else if(j == (path.Length - 1))// if we are at the last point, we need to close the loop with the first point
                {
                point0 = new Vector3(path[j-1].x ,path[j-1].y ,0);
                point1 = new Vector3(path[j-1].x ,path[j-1].y ,size);
                point2 = new Vector3(path[j].x ,path[j].y ,size);
                point3 = new Vector3(path[j].x ,path[j].y ,0);
                    MakeMesh(point0,point1,point2,point3);
                point0 = new Vector3(path[j].x ,path[j].y ,0);
                point1 = new Vector3(path[j].x ,path[j].y ,size);
                point2 = new Vector3(path[0].x ,path[0].y ,size); // First point
                point3 = new Vector3(path[0].x ,path[0].y ,0); // First point
                    MakeMesh(point0,point1,point2,point3);
                }
            }
        }
    }


    //Method to generate 2 triangles mesh from the 4 points 0 1 2 3 and add it to the collider
    public void MakeMesh (Vector3 point0,Vector3 point1,Vector3 point2, Vector3 point3){
        
        // Vertice add
        vertices.Add(point0);
        vertices.Add(point1);
        vertices.Add(point2);
        vertices.Add(point3);

        //Triangle order
        triangles.Add(0+loop*4);
        triangles.Add(2+loop*4);
        triangles.Add(1+loop*4);
        triangles.Add(0+loop*4);
        triangles.Add(3+loop*4);
        triangles.Add(2+loop*4);
        loop = loop + 1; 

        // create mesh 
        meshFilterMesh.vertices=vertices.ToArray();
        meshFilterMesh.triangles=triangles.ToArray();

        // add this mesh to the MeshCollider
        mesher.GetComponent<MeshCollider>().sharedMesh=meshFilterMesh;
    }

    // Save if F12 press
    public void SaveAsset() 
    {
        var mf = mesher.GetComponent<MeshFilter>();
        if (mf)
        {
            var savePath = "Assets/" + saveName + ".asset";
            Debug.Log("Saved Mesh to:" + savePath);
            AssetDatabase.CreateAsset(mf.mesh, savePath);
        }
    }
}

enter image description here

One approach is to generate the mesh directly on your own terms. The pro of this is that you can have very fine control of exactly what you want pixel boundaries to look like, and you have better information do your own triangulation of the mesh. The downside is that you have to do all of this yourself.

One way of implementing this is to use the Marching Squares algorithm to generate isobands from the pixel data (You can use the blue/green/alpha channel to get the isovalue depending on if the background is white or transparent), and then generate a piece of the mesh from each of the 2x2 pixel grounds that have a part of the isoband.

To get the pixel data from the image you can use Texture2D.GetPixels. Then you can use the marching squares algorithm on that information to determine how to represent every 2x2 cluster of pixels in the mesh. Then you would use that information to find the vertices of each triangle that represents that quad of pixels.

Once you convert each quad of pixels into triangles, arrange the vertices of those triangles into an array (make sure you order the vertices of each triangle in a clockwise direction from the visible side) and use Mesh.SetVertices to create a mesh with those vertices.

Another approach is to set the alpha of any non-red pixel to zero, and let Unity's sprite packer generate the mesh for you.

Here is one way to do that:

  1. If it is an asset and you want to modify it, set the texture asset to have Read/Write enabled checked. If the texture is created at runtime (and is therefore not an asset) this step can be skipped.

  2. Get the pixel data with Texture2D.GetPixels. This will get you an array of pixels in the form of Color[] pixels:

    public Texture2D tex;  
    
    ...
    
    Color[] pixels = tex.GetPixels();
    
  3. Iterate through each index and replace pixels with any amount of blue (such as white pixels) with clear pixels:

    for (int i = 0 ; i < pixels.Length ; i++ ) 
    {
        if (
            pixels[i].r != 1f 
            || pixels[i].g != 0f
            || pixels[i].b != 0f) 
            pixels[i] = Color.clear;
    }
    
  4. Set the texture pixel data with the modified pixel array:

    tex.SetPixels(pixels);
    tex.Apply();
    

The downside to this approach is that I do not know if you can use the Unity spritepacker to pack textures created at run time onto the sprite atlas. If it can not, then a different tool would be needed for this approach to generate meshes from sprites at run time.

Ok I've made something :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class scri : MonoBehaviour
{

    public Texture2D tex;  
    public Texture2D newText;  
    public Sprite sprite;
    public List<Color> colorList;

    private Sprite mySprite;
    private SpriteRenderer sr;

    // Start is called before the first frame update
    void Start()
    {
    sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
    newText =new Texture2D(tex.width,tex.height,TextureFormat.ARGB32, false);
    Color[] pixels = sprite.texture.GetPixels();
    for (int i = 0 ; i < pixels.Length ; i++ ) 
    { 
    Debug.Log(pixels[i]);
        if (pixels[i].r==1) {
            pixels[i] = Color.clear;
        }
    }
        newText.SetPixels(pixels);
        newText.Apply();
        mySprite = Sprite.Create(newText, new Rect(0.0f, 0.0f, newText.width, newText.height), new Vector2(0.5f, 0.5f), 100.0f);
        sr.sprite = mySprite;
    }

    // Update is called once per frame
    // void Update()
    // {
    //     Debug.Log(sprite.triangles.Length);
    //     Debug.Log(sprite.vertices.Length);
    // }
}

Usefull link : https://forum.unity.com/threads/setting-pixel-to-transparent-turns-out-black.172375/ https://docs.unity3d.com/ScriptReference/Sprite.Create.html https://forum.unity.com/threads/is-it-possible-to-convert-a-texture2d-from-one-format-to-another-in-standalone-run-time.327141/ https://forum.unity.com/threads/texture-setpixels.431177/

But I don't know why, if the png has a white background at the begining it doesn't work well ... : With svg it's ok since the begining without my code. enter image description here

But in sprite Editor I could generate a custom physic shape:enter image description here

Related