OpenGL ES 加载3D模型
前面绘制的矩形、立方体确实确实让人看烦了,并且实际生活中的物体是非常复杂的,我们不可能像前面哪样指定顶点来绘制,因此本篇博客就说明通过OpenGL ES加载一个3D模型。这样复杂物体的设计工作就可以交给专业的设计师来做了,进行3D建模的工具比如3dmax、maya等。
设计师通过这些软件构建出来漂亮的3D模型,并且可以通过软件导出有关该模型的各种数据信息文件,比如顶点坐标,法向量,纹理坐标等信息。模型文件有很多类型,不同的类型的模型文件其实就是按照不同的文件格式来保存有关3D模型的信息的。
一个开源C++库Assimp用来加载模型,Assimp可以导入几十种不同格式的模型文件(同样也可以导出部分模型格式)可以通过Assimp获取所有我们需要的模型数据信息,目前还没有java版的Assimp库,不过也可以将这个库编译到android上使用,不过对于简单的型很,只需要了解obj文件的格式也可以自己动手写代码来加载。
#
# object Teapot01
#
v 15.7604 27.2033 -0.2686
v 14.5306 27.2033 5.9599
v 14.3264 28.0401 5.8730
...
# 529 vertices
vn -0.9667 -0.2558 -0.0000
vn -0.8930 -0.2563 -0.3699
vn -0.8934 0.2560 -0.3691
...
# 530 vertex normals
vt 0.9541 0.9784 0.0000
vt 0.8991 0.9784 0.0000
vt 0.8991 0.9729 0.0000
...
# 664 texture coords
g Teapot01
f 1/1/1 2/2/2 3/3/3
f 3/3/3 4/4/4 1/1/1
f 4/4/4 3/3/3 5/5/5
...
# 992 faces
注释行以符号“#”为开头
v:几何体顶点(Geometric vertices)
vt:贴图坐标点(Texture vertices)
vn:顶点法线(Vertex normals)
g:组名称(Group name),类似于Assimp库里面的mesh的概念。
f :面(Face),对于OpenGL ES来说都是三角形。由空格分开的三组数据分别表示三角形的三个点,每组数中的三个值用/分开,表示定点的坐标索引、纹理所因和法向量索引。计算行号时,各种前缀是独立计算的,索引为对应前缀的类型数据开始的行数,从1开始。
手动解析的obj文件的大致思路就是逐行读取,将顶点、法向量和纹理分别保存到三个数组中,接下来的主要在解析g标签下面,解析到每行以f开始时,解析每个顶点的坐标、法向量和纹理坐标的索引,在刚才的三个数组中去索引即可,注意下标减一,因为索引的下标是从1开始的,大致代码如下。
/**
* 加载obj文件至数组,包括顶点坐标、顶点法向量和纹理坐标
* 返回的数组在使用glVertexAttribPointer函数时注意利用stride参数
* @param objFile
* @return
*/
public static float[] loadFromFile(String objFile) {
ArrayList<Float> vertexList = new ArrayList<Float>();
ArrayList<Float> textureList = new ArrayList<Float>();
ArrayList<Float> normalList = new ArrayList<Float>();
ArrayList<Float> finalList = new ArrayList<Float>();
try {
FileReader fr = new FileReader(new File(objFile));
BufferedReader br = new BufferedReader(fr);
String line = null;
while ((line = br.readLine()) != null) {
String[] temp = line.split("[ ]+");
if (temp[0].trim().equals("v")) {
vertexList.add(Float.parseFloat(temp[1]));
vertexList.add(Float.parseFloat(temp[2]));
vertexList.add(Float.parseFloat(temp[3]));
} else if (temp[0].trim().equals("vn")) {
normalList.add(Float.parseFloat(temp[1]));
normalList.add(Float.parseFloat(temp[2]));
normalList.add(Float.parseFloat(temp[3]));
} else if (temp[0].trim().equals("vt")) {
textureList.add(Float.parseFloat(temp[1]));
textureList.add(Float.parseFloat(temp[2]));
} else if (temp[0].trim().equals("f")) {
for (int i = 1; i < temp.length; i++) {
String[] temp2 = temp[i].split("/");
int indexVetex = Integer.parseInt(temp2[0]) - 1;
finalList.add(vertexList.get(3 * indexVetex));
finalList.add(vertexList.get(3 * indexVetex + 1));
finalList.add(vertexList.get(3 * indexVetex + 2));
int indexNormal = Integer.parseInt(temp2[1]) - 1;
finalList.add(vertexList.get(3 * indexNormal));
finalList.add(vertexList.get(3 * indexNormal + 1));
finalList.add(vertexList.get(3 * indexNormal + 2));
int indexTexture = Integer.parseInt(temp2[2]) - 1;
finalList.add(vertexList.get(3 * indexTexture));
finalList.add(vertexList.get(3 * indexTexture + 1));
finalList.add(vertexList.get(3 * indexTexture + 2));
}
}
}
float [] result = new float[finalList.size()];
for (int i = 0; i < finalList.size(); i++) {
result[i] = finalList.get(i);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
不过我还是在github上发现了一个java实现的解析obj文件的库obj2opengl,所以不要把时间花费在这些处理字符串的细节上,直接用好了,关于它的详细信息可以参考README文件。
使用这个库的主要
RawOpenGLModel openGLModel = new Obj2OpenJL().convert("file");
OpenGLModelData openGLModelData = openGLModel.normalize().center().getDataForGLDrawElements();
openGLModelData.getVertices();
openGLModelData.getNormals();
openGLModelData.getTextureCoordinates();
效果图