我的 obj-parser 可能有什么问题?
What could be wrong with my obj-parser?
给定以下渲染对象(具有完全白色纹理的龙)并使用基本纹理停止。可能是什么错误?我的指数错了吗?是不是有些顶点或面错了?
:
我的对象渲染器 class 可能出了什么问题?我遵循了教程,不幸的是我的模型看起来不像所需的模型。龙应该是完全白色的,没有任何黑线,摊位纹理看起来不对(不应该有白线)。
这是源代码(使用 "v "、"vt "、"vn "、"f " 的基本 .obj 渲染):
try {
while ((line = reader.readLine()) != null && !line.startsWith("f ")) {
String[] currentLine = line.split(" ");
if (line.startsWith("v ")) {
Vector3f vertex =
new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]),
Float.parseFloat(currentLine[3]));
vertices.add(vertex);
} else if (line.startsWith("vt ")) {
Vector2f texture =
new Vector2f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]));
textures.add(texture);
} else if (line.startsWith("vn ")) {
Vector3f normal =
new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]),
Float.parseFloat(currentLine[3]));
normals.add(normal);
}
}
textureArray = new float[vertices.size() * 2];
normalsArray = new float[vertices.size() * 3];
do {
if (line == null || !line.startsWith("f ")) {
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processVertex(vertex1, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex2, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex3, indices, textures, normals, textureArray, normalsArray);
} while ((line = reader.readLine()) != null);
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size() * 3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for (Vector3f vertex : vertices) {
verticesArray[vertexPointer++] = vertex.x;
verticesArray[vertexPointer++] = vertex.y;
verticesArray[vertexPointer++] = vertex.z;
}
for (int i = 0; i < indices.size(); i++) {
indicesArray[i] = indices.get(i);
}
return loader.loadToVao(verticesArray, textureArray, normalsArray, indicesArray);
}
private static void processVertex(final String[] vertexData, final List<Integer> indices,
final List<Vector2f> textures, final List<Vector3f> normals, final float[] textureArray,
final float[] normalsArray) {
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
normalsArray[currentVertexPointer * 3] = currentNorm.x;
normalsArray[currentVertexPointer * 3 + 1] = currentNorm.y;
normalsArray[currentVertexPointer * 3 + 2] = currentNorm.z;
}
用于组合顶点坐标、纹理坐标和法线的逻辑看起来有缺陷。为什么这行不通的最明显迹象在这里:
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
想象一下如果有多个顶点坐标相同但纹理坐标不同会发生什么。假设您有以下索引三元组作为不同面孔的一部分:
f ... 4/7/13 ...
f ... 4/9/13 ...
这是两个顶点坐标相同但纹理坐标不同的顶点。由于第一个索引相同,因此在 textureArray
中分配值时,您将使用相同的 currentVertexPointer
索引,即使这是需要不同纹理坐标的两个不同顶点。
要理解的关键点是,由于 OpenGL 对所有顶点属性使用相同的索引,因此您需要一个顶点用于 每个唯一 顶点坐标、纹理坐标和法线的组合.
一种解释方式是 v
记录不给你顶点。他们给你顶点 postitions。您为每个顶点提取 3 个属性,它们都被相同对待:
v
记录:职位。
vt
记录:纹理坐标。
vn
记录:法线。
您可以通过组合这 3 个属性来构建顶点。 f
记录告诉你如何组合它们。
构建 OpenGL 顶点的最简单方法是为您在解析面时遇到的每个索引三元组生成一个新顶点。如果你想让你的代码 运行 发生相对较小的变化,你可以像对待 textureArray
和 normalsArray
那样对待你的 verticesArray
。你的 processVertex()
函数看起来像这样:
Vector3f currentVertex = vertices.get(Integer.parseInt(vertexData[0]) - 1);
verticesArray[currentVertexPointer * 3] = currentVertex.x;
verticesArray[currentVertexPointer * 3 + 1] = currentVertex.y;
verticesArray[currentVertexPointer * 3 + 2] = currentVertex.z;
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
这将需要进行更多相关更改。代替重写你的整个代码,这里至少有一些提示:
- 如现在所用,
currentVertexPointer
需要为每个顶点递增。
- 您没有提前设置
verticesArray
、textureArray
和 normalsArray
的上限,因为大小取决于面数。您需要动态调整这些数组的大小,或者使用一个容器来为您处理这些问题。
- 不再需要用于构建
verticesArray
的后续代码,因为它现在是与其他数组一起构建的。
- 对于这种方法,您真的不需要索引。你可以用
glDrawArrays()
. 画画
更有效的解决方案是为 position/texture/normal 的每个唯一组合共享顶点。请参阅我的回答 Why is my OBJ parser rendering meshes like this? and OpenGL - Index buffers difficulties 以获取有关如何操作的说明。
除此之外,您的解析器也非常有限。 OBJ 并不是真正的标准格式,这完全取决于您希望如何处理来自各种来源的文件。您的代码未处理的方面包括:
- 与其他记录混合的人脸记录。您的代码假定它们都在末尾,这不是必需的。
- 负指数。
- 具有 3 个以上顶点的面(参见 Converting quadriladerals in an OBJ file into triangles?)。
给定以下渲染对象(具有完全白色纹理的龙)并使用基本纹理停止。可能是什么错误?我的指数错了吗?是不是有些顶点或面错了?
我的对象渲染器 class 可能出了什么问题?我遵循了教程,不幸的是我的模型看起来不像所需的模型。龙应该是完全白色的,没有任何黑线,摊位纹理看起来不对(不应该有白线)。
这是源代码(使用 "v "、"vt "、"vn "、"f " 的基本 .obj 渲染):
try {
while ((line = reader.readLine()) != null && !line.startsWith("f ")) {
String[] currentLine = line.split(" ");
if (line.startsWith("v ")) {
Vector3f vertex =
new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]),
Float.parseFloat(currentLine[3]));
vertices.add(vertex);
} else if (line.startsWith("vt ")) {
Vector2f texture =
new Vector2f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]));
textures.add(texture);
} else if (line.startsWith("vn ")) {
Vector3f normal =
new Vector3f(Float.parseFloat(currentLine[1]), Float.parseFloat(currentLine[2]),
Float.parseFloat(currentLine[3]));
normals.add(normal);
}
}
textureArray = new float[vertices.size() * 2];
normalsArray = new float[vertices.size() * 3];
do {
if (line == null || !line.startsWith("f ")) {
continue;
}
String[] currentLine = line.split(" ");
String[] vertex1 = currentLine[1].split("/");
String[] vertex2 = currentLine[2].split("/");
String[] vertex3 = currentLine[3].split("/");
processVertex(vertex1, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex2, indices, textures, normals, textureArray, normalsArray);
processVertex(vertex3, indices, textures, normals, textureArray, normalsArray);
} while ((line = reader.readLine()) != null);
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
verticesArray = new float[vertices.size() * 3];
indicesArray = new int[indices.size()];
int vertexPointer = 0;
for (Vector3f vertex : vertices) {
verticesArray[vertexPointer++] = vertex.x;
verticesArray[vertexPointer++] = vertex.y;
verticesArray[vertexPointer++] = vertex.z;
}
for (int i = 0; i < indices.size(); i++) {
indicesArray[i] = indices.get(i);
}
return loader.loadToVao(verticesArray, textureArray, normalsArray, indicesArray);
}
private static void processVertex(final String[] vertexData, final List<Integer> indices,
final List<Vector2f> textures, final List<Vector3f> normals, final float[] textureArray,
final float[] normalsArray) {
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
normalsArray[currentVertexPointer * 3] = currentNorm.x;
normalsArray[currentVertexPointer * 3 + 1] = currentNorm.y;
normalsArray[currentVertexPointer * 3 + 2] = currentNorm.z;
}
用于组合顶点坐标、纹理坐标和法线的逻辑看起来有缺陷。为什么这行不通的最明显迹象在这里:
int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
想象一下如果有多个顶点坐标相同但纹理坐标不同会发生什么。假设您有以下索引三元组作为不同面孔的一部分:
f ... 4/7/13 ...
f ... 4/9/13 ...
这是两个顶点坐标相同但纹理坐标不同的顶点。由于第一个索引相同,因此在 textureArray
中分配值时,您将使用相同的 currentVertexPointer
索引,即使这是需要不同纹理坐标的两个不同顶点。
要理解的关键点是,由于 OpenGL 对所有顶点属性使用相同的索引,因此您需要一个顶点用于 每个唯一 顶点坐标、纹理坐标和法线的组合.
一种解释方式是 v
记录不给你顶点。他们给你顶点 postitions。您为每个顶点提取 3 个属性,它们都被相同对待:
v
记录:职位。vt
记录:纹理坐标。vn
记录:法线。
您可以通过组合这 3 个属性来构建顶点。 f
记录告诉你如何组合它们。
构建 OpenGL 顶点的最简单方法是为您在解析面时遇到的每个索引三元组生成一个新顶点。如果你想让你的代码 运行 发生相对较小的变化,你可以像对待 textureArray
和 normalsArray
那样对待你的 verticesArray
。你的 processVertex()
函数看起来像这样:
Vector3f currentVertex = vertices.get(Integer.parseInt(vertexData[0]) - 1);
verticesArray[currentVertexPointer * 3] = currentVertex.x;
verticesArray[currentVertexPointer * 3 + 1] = currentVertex.y;
verticesArray[currentVertexPointer * 3 + 2] = currentVertex.z;
Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer * 2] = currentTex.x;
textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
这将需要进行更多相关更改。代替重写你的整个代码,这里至少有一些提示:
- 如现在所用,
currentVertexPointer
需要为每个顶点递增。 - 您没有提前设置
verticesArray
、textureArray
和normalsArray
的上限,因为大小取决于面数。您需要动态调整这些数组的大小,或者使用一个容器来为您处理这些问题。 - 不再需要用于构建
verticesArray
的后续代码,因为它现在是与其他数组一起构建的。 - 对于这种方法,您真的不需要索引。你可以用
glDrawArrays()
. 画画
更有效的解决方案是为 position/texture/normal 的每个唯一组合共享顶点。请参阅我的回答 Why is my OBJ parser rendering meshes like this? and OpenGL - Index buffers difficulties 以获取有关如何操作的说明。
除此之外,您的解析器也非常有限。 OBJ 并不是真正的标准格式,这完全取决于您希望如何处理来自各种来源的文件。您的代码未处理的方面包括:
- 与其他记录混合的人脸记录。您的代码假定它们都在末尾,这不是必需的。
- 负指数。
- 具有 3 个以上顶点的面(参见 Converting quadriladerals in an OBJ file into triangles?)。