从单色纹理生成网格

Generate mesh from one-color texture

我编写了一个能够绘制并生成该图的精灵的代码。所以我得到了一个白色背景的精灵和我的画(颜色不同)。

我的问题:如何在运行时去除白色背景?(使用 C# 代码)

我的问题是:我想使用绘图生成网格,但是白色背景我有 4 vertices(精灵的第四个角)我想从我在精灵上绘制的真实形状中获取所有顶点(远远超过 4 个顶点)

我目前的想法是将绘图转换为具有透明背景,然后使用 unity 的精灵打包器从中生成网格。

我的项目:这是一个游戏,我们可以在其中创建自己的游戏电路:用户绘制一个黑白精灵 —> 我将其转换为带有碰撞器的网格并生成新的游戏电路。

我已经精简到可以清除所有白色像素,但我认为使用该技术我不会得到很多顶点。

感谢您的帮助, 阿克塞尔

一种方法是根据您自己的条件直接生成网格。这样做的好处是您可以非常精确地控制您希望像素边界看起来像什么,并且您有更好的信息来对网格进行自己的三角剖分。缺点是你必须自己做所有这些。

实现这一点的一种方法是使用 Marching Squares 算法从像素数据生成等带(您可以使用 blue/green/alpha 通道获取等值,具体取决于背景是白色还是透明),然后从每个具有部分等带的 2x2 像素地面生成一块网格。

要从图像中获取像素数据,您可以使用 Texture2D.GetPixels。然后,您可以对该信息使用行进方块算法来确定如何表示网格中每个 2x2 像素簇。然后,您将使用该信息来查找代表该四边形像素的每个三角形的顶点。

将每个四边形像素转换为三角形后,将这些三角形的顶点排列成一个数组(确保从可见侧按顺时针方向对每个三角形的顶点进行排序)并使用 Mesh.SetVertices使用这些顶点创建网格。

另一种方法是将任何非红色像素的 alpha 设置为零,然后让 Unity 的 sprite packer 为您生成网格。

这是一种方法:

  1. 如果它是资产并且您想修改它,请将纹理资产设置为选中 Read/Write enabled。如果纹理是在 运行 时创建的(因此不是资产),则可以跳过此步骤。

  2. Texture2D.GetPixels获取像素数据。这将为您提供 Color[] pixels:

    形式的像素数组
    public Texture2D tex;  
    
    ...
    
    Color[] pixels = tex.GetPixels();
    
  3. 遍历每个索引并将像素替换为具有清晰像素的任意数量的蓝色(例如白色像素):

    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. 使用修改后的像素数组设置纹理像素数据:

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

这种方法的缺点是我不知道您是否可以使用 Unity spritepacker 将在 运行 时间创建的纹理打包到精灵图集上。如果不能,则此方法需要使用不同的工具在 运行 时间从精灵生成网格。

好的,我做了一些东西:

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);
    // }
}

有用 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/

但我不知道为什么,如果 png 在开始时有白色背景,它就不能很好地工作......: 使用 svg 从一开始就没问题,没有我的代码。

但在 sprite 编辑器中我可以生成自定义物理形状:

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);
        }
    }
}