我正在尝试将 .obj 文件加载到我的 Android 应用程序中并使用 OpenGL 2 显示它。
您可以在此处找到该文件:编辑:我删除了该文件,您可以使用包含下面提到的值的任何 .obj 文件进行测试。
stackoverflow 上有很多类似的问题,但我没有找到不需要大型库的简单解决方案。
该文件仅包含以下值类型:
我尝试了 libgdx,它工作正常,但对于我的需要来说有点矫枉过正。
我在没有 LWJGL 的情况下尝试了 oObjLoader https://github.com/seanrowens/oObjLoader。解析似乎有效,但是如何在简单的场景中显示这些值?
下一步是将图像作为纹理附加到对象上。但现在我很乐意按原样显示文件。
我愿意接受不同的解决方案,例如预转换文件,因为在应用程序中它只会是这个。
谢谢!
状态更新 基本的加载和显示现在可以工作,如我自己的答案所示。
我最终编写了一个新的解析器,它可以像这样使用来构建 FloatBuffers 以在渲染器中使用:
ObjLoader objLoader = new ObjLoader(context, "Mug.obj");
numFaces = objLoader.numFaces;
// Initialize the buffers.
positions = ByteBuffer.allocateDirect(objLoader.positions.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
positions.put(objLoader.positions).position(0);
normals = ByteBuffer.allocateDirect(objLoader.normals.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
normals.put(objLoader.normals).position(0);
textureCoordinates = ByteBuffer.allocateDirect(objLoader.textureCoordinates.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
textureCoordinates.put(objLoader.textureCoordinates).position(0);
这是解析器:
public final class ObjLoader {
public final int numFaces;
public final float[] normals;
public final float[] textureCoordinates;
public final float[] positions;
public ObjLoader(Context context, String file) {
Vector<Float> vertices = new Vector<>();
Vector<Float> normals = new Vector<>();
Vector<Float> textures = new Vector<>();
Vector<String> faces = new Vector<>();
BufferedReader reader = null;
try {
InputStreamReader in = new InputStreamReader(context.getAssets().open(file));
reader = new BufferedReader(in);
// read file until EOF
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(" ");
switch (parts[0]) {
case "v":
// vertices
vertices.add(Float.valueOf(parts[1]));
vertices.add(Float.valueOf(parts[2]));
vertices.add(Float.valueOf(parts[3]));
break;
case "vt":
// textures
textures.add(Float.valueOf(parts[1]));
textures.add(Float.valueOf(parts[2]));
break;
case "vn":
// normals
normals.add(Float.valueOf(parts[1]));
normals.add(Float.valueOf(parts[2]));
normals.add(Float.valueOf(parts[3]));
break;
case "f":
// faces: vertex/texture/normal
faces.add(parts[1]);
faces.add(parts[2]);
faces.add(parts[3]);
break;
}
}
} catch (IOException e) {
// cannot load or read file
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
//log the exception
}
}
}
numFaces = faces.size();
this.normals = new float[numFaces * 3];
textureCoordinates = new float[numFaces * 2];
positions = new float[numFaces * 3];
int positionIndex = 0;
int normalIndex = 0;
int textureIndex = 0;
for (String face : faces) {
String[] parts = face.split("/");
int index = 3 * (Short.valueOf(parts[0]) - 1);
positions[positionIndex++] = vertices.get(index++);
positions[positionIndex++] = vertices.get(index++);
positions[positionIndex++] = vertices.get(index);
index = 2 * (Short.valueOf(parts[1]) - 1);
textureCoordinates[normalIndex++] = textures.get(index++);
// NOTE: Bitmap gets y-inverted
textureCoordinates[normalIndex++] = 1 - textures.get(index);
index = 3 * (Short.valueOf(parts[2]) - 1);
this.normals[textureIndex++] = normals.get(index++);
this.normals[textureIndex++] = normals.get(index++);
this.normals[textureIndex++] = normals.get(index);
}
}
}
尝试一下我在 Github 上找到的这个项目。 https://github.com/WenlinMao/android-3d-model-viewer
这是 OpenGL ES 2.0 的演示。它是一个带有 3D 引擎的 Android 应用程序,可以加载 Wavefront OBJ、STL、DAE 和 glTF 文件。该应用程序基于 andresoviedo 的项目,可以在此处找到该项目,并具有加载和渲染 glTF 格式的附加功能。
此应用程序的目的是学习和分享如何使用 OpenGLES 和 Android 进行绘图。由于这是我的第一个 Android 应用程序,因此很可能存在错误;但我会尝试继续改进应用程序并添加更多功能。
这个项目是开源的,并且包含可以解决您的问题的类!
当您有“obj”内容时尝试此代码以获取所有
Vertices
Indices
Normals
Texture Coords
我希望它能提供一些帮助,这是代码:
void readObj(String objContents) {
final float[] vertices;
final float[] normals;
final float[] textureCoords;
final short[] indices;
Vector<Float> verticesTemp = new Vector<>();
Vector<Float> normalsTemp = new Vector<>();
Vector<Float> textureCoordsTemp = new Vector<>();
Vector<String> facesTemp = new Vector<>();
String[] lines = objContents.split("\n");
for (String line : lines) {
String[] parts = line.split(" ");
switch (parts[0]) {
case "v":
verticesTemp.add(Float.parseFloat(parts[1]));
verticesTemp.add(Float.parseFloat(parts[2]));
verticesTemp.add(Float.parseFloat(parts[3]));
break;
case "vn":
normalsTemp.add(Float.parseFloat(parts[1]));
normalsTemp.add(Float.parseFloat(parts[2]));
normalsTemp.add(Float.parseFloat(parts[3]));
break;
case "vt":
textureCoordsTemp.add(Float.parseFloat(parts[1]));
textureCoordsTemp.add(Float.parseFloat(parts[2]));
break;
case "f":
facesTemp.add(parts[1]);
facesTemp.add(parts[2]);
facesTemp.add(parts[3]);
break;
}
}
vertices = new float[verticesTemp.size()];
normals = new float[normalsTemp.size()];
textureCoords = new float[textureCoordsTemp.size()];
indices = new short[facesTemp.size()];
for (int i = 0, l = verticesTemp.size(); i < l; i++) {
vertices[i] = verticesTemp.get(i);
}
for (int i = 0, l = normalsTemp.size(); i < l; i++) {
normals[i] = normalsTemp.get(i);
}
for (int i = 0, l = textureCoordsTemp.size(); i < l; i++) {
textureCoords[i] = textureCoordsTemp.get(i);
}
for (int i = 0, l = facesTemp.size(); i < l; i++) {
indices[i] = (short) (Short.parseShort(facesTemp.get(i).split("/")[0]) - 1);
}
// now all vertices, normals, textureCoords and indices are ready
}
我将扩展用户16386527的答案。因为为了使纹理在对象上正确显示,您需要进行一些更改。代码是 kotlin 语言,但转换为 Java 应该不是问题。
val verticesTemp: MutableList<Float> = mutableListOf()
val normalsTemp: MutableList<Float> = mutableListOf()
val textureCoordsTemp: MutableList<Float> = mutableListOf()
val facesTemp: MutableList<String> = mutableListOf()
while (sc.hasNext()) {
val ln = sc.nextLine()
val split = ln.split(" ")
when (split[0]) {
"v" -> {
verticesTemp.add(split[1].toFloat())
verticesTemp.add(split[2].toFloat())
verticesTemp.add(split[3].toFloat())
}
"vn" -> {
normalsTemp.add(split[1].toFloat())
normalsTemp.add(split[2].toFloat())
normalsTemp.add(split[3].toFloat())
}
"vt" -> {
textureCoordsTemp.add(split[1].toFloat())
textureCoordsTemp.add(-split[2].toFloat()) // Flip the texture if needed
}
"f" -> {
if (split.size == 5) {
// triangle 1
facesTemp.add(split[1])
facesTemp.add(split[2])
facesTemp.add(split[3])
// triangle 2
facesTemp.add(split[1])
facesTemp.add(split[3])
facesTemp.add(split[4])
} else {
facesTemp.add(split[1])
facesTemp.add(split[2])
facesTemp.add(split[3])
}
}
}
}
sc.close()
val vertices = mutableListOf<Float>()
val normals = mutableListOf<Float>()
val textureCoords = mutableListOf<Float>()
val indices = mutableListOf<Short>()
for (face in facesTemp) {
val indicesArray = face.split(" ")
for (indexInfo in indicesArray) {
val index = indexInfo.split("/")
val vertexIndex = index[0].toInt() - 1
val textureIndex = index[1].toInt() - 1
val normalIndex = index[2].toInt() - 1
// Extract vertex position, texture coordinate, and normal data
vertices.add(verticesTemp[vertexIndex * 3])
vertices.add(verticesTemp[vertexIndex * 3 + 1])
vertices.add(verticesTemp[vertexIndex * 3 + 2])
textureCoords.add(textureCoordsTemp[textureIndex * 2])
textureCoords.add(textureCoordsTemp[textureIndex * 2 + 1])
normals.add(normalsTemp[normalIndex * 3])
normals.add(normalsTemp[normalIndex * 3 + 1])
normals.add(normalsTemp[normalIndex * 3 + 2])
// Add the index for the current vertex data
indices.add(indices.size.toShort())
}
}
// Convert mutable lists to arrays
val verticesArray = vertices.toFloatArray()
val normalsArray = normals.toFloatArray()
val textureCoordsArray = textureCoords.toFloatArray()
val indicesArray = indices.toShortArray()