从单色纹理生成网格
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 为您生成网格。
这是一种方法:
如果它是资产并且您想修改它,请将纹理资产设置为选中 Read/Write enabled
。如果纹理是在 运行 时创建的(因此不是资产),则可以跳过此步骤。
用Texture2D.GetPixels
获取像素数据。这将为您提供 Color[] pixels
:
形式的像素数组
public Texture2D tex;
...
Color[] pixels = tex.GetPixels();
遍历每个索引并将像素替换为具有清晰像素的任意数量的蓝色(例如白色像素):
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;
}
使用修改后的像素数组设置纹理像素数据:
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);
}
}
}
我编写了一个能够绘制并生成该图的精灵的代码。所以我得到了一个白色背景的精灵和我的画(颜色不同)。
我的问题:如何在运行时去除白色背景?(使用 C# 代码)
我的问题是:我想使用绘图生成网格,但是白色背景我有 4 vertices(精灵的第四个角)我想从我在精灵上绘制的真实形状中获取所有顶点(远远超过 4 个顶点)
我目前的想法是将绘图转换为具有透明背景,然后使用 unity 的精灵打包器从中生成网格。
我的项目:这是一个游戏,我们可以在其中创建自己的游戏电路:用户绘制一个黑白精灵 —> 我将其转换为带有碰撞器的网格并生成新的游戏电路。
我已经精简到可以清除所有白色像素,但我认为使用该技术我不会得到很多顶点。
感谢您的帮助,
阿克塞尔
一种方法是根据您自己的条件直接生成网格。这样做的好处是您可以非常精确地控制您希望像素边界看起来像什么,并且您有更好的信息来对网格进行自己的三角剖分。缺点是你必须自己做所有这些。
实现这一点的一种方法是使用 Marching Squares 算法从像素数据生成等带(您可以使用 blue/green/alpha 通道获取等值,具体取决于背景是白色还是透明),然后从每个具有部分等带的 2x2 像素地面生成一块网格。
要从图像中获取像素数据,您可以使用 Texture2D.GetPixels
。然后,您可以对该信息使用行进方块算法来确定如何表示网格中每个 2x2 像素簇。然后,您将使用该信息来查找代表该四边形像素的每个三角形的顶点。
将每个四边形像素转换为三角形后,将这些三角形的顶点排列成一个数组(确保从可见侧按顺时针方向对每个三角形的顶点进行排序)并使用 Mesh.SetVertices
使用这些顶点创建网格。
另一种方法是将任何非红色像素的 alpha 设置为零,然后让 Unity 的 sprite packer 为您生成网格。
这是一种方法:
如果它是资产并且您想修改它,请将纹理资产设置为选中
Read/Write enabled
。如果纹理是在 运行 时创建的(因此不是资产),则可以跳过此步骤。用
形式的像素数组Texture2D.GetPixels
获取像素数据。这将为您提供Color[] pixels
:public Texture2D tex; ... Color[] pixels = tex.GetPixels();
遍历每个索引并将像素替换为具有清晰像素的任意数量的蓝色(例如白色像素):
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; }
使用修改后的像素数组设置纹理像素数据:
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);
}
}
}