Unity Mesh从obj文件到GPU

OBJ格式

        从一个简单的立方体模型入手,Blender导出设置如下

        将obj文件用文本编辑器打开,这里将各个部分数据做了注释标注:

# 注释,以#开头
# Blender v2.93.5 OBJ File: ''
# www.blender.org

# 模型名称
o Cube

# v代表vertex,立方体的8个顶点,后面三列数据是模型空间的xyz
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000

# vt代表vertex texture coordinate,是顶点对应的纹理uv坐标,一共14个
vt 0.875000 0.500000
vt 0.625000 0.750000
vt 0.625000 0.500000
vt 0.375000 1.000000
vt 0.375000 0.750000
vt 0.625000 0.000000
vt 0.375000 0.250000
vt 0.375000 0.000000
vt 0.375000 0.500000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.625000 0.250000
vt 0.875000 0.750000
vt 0.625000 1.000000

# vn代表vertex normal,顶点法线,有6个。
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000

# 关闭平滑着色
s off

# 导出四边面的结果,6个四边面
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6

# 导出三角面的结果,12个三角面
f 5/1/1 3/2/1 1/3/1
f 3/2/2 8/4/2 4/5/2
f 7/6/3 6/7/3 8/8/3
f 2/9/4 8/10/4 6/11/4
f 1/3/5 4/5/5 2/9/5
f 5/12/6 2/9/6 6/7/6
f 5/1/1 7/13/1 3/2/1
f 3/2/2 7/14/2 8/4/2
f 7/6/3 5/12/3 6/7/3
f 2/9/4 4/5/4 8/10/4
f 1/3/5 3/2/5 4/5/5
f 5/12/6 1/3/6 2/9/6

        各个部分的含义注释已经标注好了,这里着重看一下面部分。导出三角面和四边面时结果不同,这很容易理解,立方体有6个正方形面,每个正方形对应2个三角面,也就是12个三角面。

        面数据每行的格式为:

f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...

        每个面可以包含三个(三角面)或更多元素(四边面或者多边形面),每个元素的顶点是必须的,后面的vt(纹理坐标)和vn(法线)是可选的。这些值都是索引(从1开始),也就是相对于前面给出的v,vt,vn数据各自的起点的偏移。

        纹理坐标为什么是14个?这是因为Blender默认的UV分配是这样的,注意这里有些顶点是重复的,比如最左侧的顶点和最右侧的顶点其实是同一个。

        法线坐标是6个,这也很好理解,一共有6个面,每个面的法线是一样的。 

Unity导入

        将obj文件导入到Unity中,可以看到其格式如下:

        可以看到顶点数发生了变化,原本只有8个顶点,现在变成了24个,每个顶点有其自身的normal、tangent和uv,占用空间变成了24*(12+12+16+8)/1024=1.125KB。索引数据一共是12个三角面,每个面三个索引,每个索引两个字节,一共12*3*2=72B。

        为什么会这样呢?是因为Unity在导入的时候会自动拆分顶点。在之前的法线和UV坐标数据中可以看到,顶点和这些数据存在一对多的关系,以正方体为例,每个顶点对应3个方向的法线,在UV数据中,有14-8=6个重复顶点。而对于GPU来说,顶点的每一个属性值必须是一一对应的,这种一对多的方式是GPU不能理解的,所以在GPU必须进行拆分。拆分自然需要按照最多的类型,每个顶点有三个法线,就需要一拆为三,所以变成8*3=24个顶点。

         可不可以不拆分数据呢?也是可以的,但是意味着每个顶点只能对应一个法线和UV,只能有一个法线意味着顶点处一定是平滑着色的,意味着不能存在硬边。只有一个UV问题就更大了,同一个顶点必须对应同一个坐标,在展UV时会相当麻烦。这种方法在特殊情况下是有用的,比如一个大平面,但一般情况不太适合。

        比如通过脚本创建Mesh来测试这种情况,手动设置顶点位置和坐标:

Vector3[] verts = new Vector3[8];
Vector3Int[] triangles = new Vector3Int[12];

verts[0].Set(-.5f, 0, -.5f);
verts[1].Set(.5f, 0, -.5f);
verts[2].Set(.5f, 0, .5f);
verts[3].Set(-.5f, 0, .5f);
verts[4].Set(-.5f, 1, -.5f);
verts[5].Set(.5f, 1, -.5f);
verts[6].Set(.5f, 1, .5f);
verts[7].Set(-.5f, 1, .5f);

triangles[0].Set(0, 4, 5);
triangles[1].Set(0, 5, 1);
triangles[2].Set(1, 5, 6);
triangles[3].Set(1, 6, 2);
triangles[4].Set(2, 6, 7);
triangles[5].Set(2, 7, 3);
triangles[6].Set(3, 7, 4);
triangles[7].Set(3, 4, 0);
triangles[8].Set(4, 7, 6);
triangles[9].Set(4, 6, 5);
triangles[10].Set(0, 1, 2);
triangles[11].Set(0, 2, 3);

List<int> trianglesIndex;
foreach (Vector3Int triangle in triangles)
{
	trianglesIndex.Add(triangle.x + idx);
	trianglesIndex.Add(triangle.y + idx);
	trianglesIndex.Add(triangle.z + idx);
}
		
Mesh mesh = new Mesh();
mesh.SetVertices(verts);
mesh.triangles = trianglesIndex.ToArray();
mesh.SetUVs(0, new Vector2[verts.Count]);
mesh.SetTangents(new Vector4[verts.Count]);
mesh.RecalculateNormals();

         这样创建的mesh如下,只有8个顶点:

GPU侧

        顶点数据(包括位置、法线、UV等)存储在Vertex Buffer,索引数据存储在Index Buffer。

        值得一提的是,实际测试发现用手动创建mesh的情况下,原本8个顶点,到GPU侧还是变成了32个顶点。这可能是因为IndexBuffer和VertexBuffer数据生成过程做了处理,可以通过SetIndexBufferData,SetVertexBufferData来手动修改。

猜你喜欢

转载自blog.csdn.net/paserity/article/details/129961949
今日推荐