为什么在尝试从波前 .obj 文件加载纹理时使用索引渲染 (glDrawElements) 时纹理显示不正确?

Why are textures displayed incorrectly when using indexed rendering (glDrawElements) when trying to load them from a wavefront .obj file?

简介

我正在构建一个简单的 wavefront .obj 文件解析器。我设法让它读取文件,存储文件的内容(顶点位置、顶点坐标、顶点法线(尚未使用)和多边形面元素信息(例如 5/2/3))。然后将此数据传递给 class(称为 GameEntity),并使用 glDrawElements 从那里使用数据将特定实体(在本例中为立方体)渲染到渲染循环内的屏幕 模式 GL_TRIANGLES。但是,纹理渲染不正确

源代码

OBJLoader.java

public class OBJLoader {    
    /**
     * This method loads a model represented by a wavefront .obj file from resources/models using
     * the specified name of the file (without the .obj extension) and a full path to the texture used
     * by the model. It passes the information (vertex positions, texture coordinates, indices)
     * obtained from the .obj file to the GameEntity constructor.
     * @param fileName
     * @param texturePath
     * @return
     * @throws Exception
     */
    public static GameEntity loadObjModel(String fileName, String texturePath) throws Exception {
        double start = System.nanoTime();

        List<Vector3f> vertices = null;
        List<Vector2f> textures = null;
        List<Vector3f> normals = null;
        List<Integer> indices = null;

        String line;

        float[] vertexPosArray = null;
        float[] texturesArray = null;
        float[] normalsArray = null;
        int[] indicesArray = null;

        try {
            FileReader fr = new FileReader(new File("resources/models/" + fileName + ".obj"));
            BufferedReader br = new BufferedReader(fr);
            vertices = new ArrayList<>();
            textures = new ArrayList<>();
            normals = new ArrayList<>();
            indices = new ArrayList<>();

            while((line = br.readLine()) != null) {

                if (!line.equals("") || !line.startsWith("#")) {
                    String[] splitLine = line.split(" ");

                    switch(splitLine[0]) {
                    case "v":
                        Vector3f vertex = new Vector3f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]), Float.parseFloat(splitLine[3]));
                        vertices.add(vertex);
                        System.out.println("[OBJLoader.loadObjModel]: Vertex " + vertex.toString() + " has been added to vertices from " + fileName);
                        break;
                    case "vt":
                        Vector2f texture = new Vector2f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]));
                        textures.add(texture);
                        System.out.println("[OBJLoader.loadObjModel]: Texture coordinate [" + texture.x +  ", " + texture.y  + "] has been added to textures from " + fileName);
                        break;
                    case "vn":
                        Vector3f normal = new Vector3f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]), Float.parseFloat(splitLine[3]));
                        normals.add(normal);
                        System.out.println("[OBJLoader.loadObjModel]: Normal " + normal + " has been added to normals from " + fileName);
                        break;
                    }
                }
            }

            int numVertices = vertices.size();
            System.out.println("[OBJLoader.loadObjModel]: numVertices = " + numVertices);
            texturesArray = new float[numVertices*2];
            System.out.println("[OBJLoader.loadObjModel]: length of texturesArray = " + texturesArray.length);
            normalsArray = new float[numVertices*3];

            br.close(); //find a better way to start a file again
            br = new BufferedReader(new FileReader("resources/models/" + fileName + ".obj"));

            while((line = br.readLine()) != null) {
                if (line.startsWith("f")) {
                    System.out.println("    [OBJLoader.loadObjModel]: Found line starting with f!"); 
                    String[] splitLine = line.split(" ");

                    //f should be omitted, therefore not starting at index 0
                    String[] v1 = splitLine[1].split("/");
                    String[] v2 = splitLine[2].split("/");
                    String[] v3 = splitLine[3].split("/");

                    System.out.println("        v1 | " + v1[0] + ", " + v1[1] + ", " + v1[2]);
                    System.out.println("        v2 | " + v2[0] + ", " + v2[1] + ", " + v2[2]);
                    System.out.println("        v3 | " + v3[0] + ", " + v3[1] + ", " + v3[2]);

                    processVertex(v1, indices, textures, normals, texturesArray, normalsArray);
                    processVertex(v2, indices, textures, normals, texturesArray, normalsArray);
                    processVertex(v3, indices, textures, normals, texturesArray, normalsArray);
                }
            }
            br.close();

        } catch (Exception e) {
            System.err.println("[OBJLoader.loadObjModel]: Error loading obj model!");
            e.printStackTrace();
        }

        vertexPosArray = new float[vertices.size()*3];
        indicesArray = new int[indices.size()];

        int i = 0;
        for(Vector3f vertex : vertices) {
            vertexPosArray[i++] = vertex.x;
            vertexPosArray[i++] = vertex.y;
            vertexPosArray[i++] = vertex.z;
        }

        for(int j = 0; j<indices.size(); j++) {
            indicesArray[j] = indices.get(j);
        }

        double end = System.nanoTime();
        double delta = (end - start) / 1000_000;
        System.out.println("[OBJLoader.loadObjModel]: Vertices array of " + fileName + ": ");
        System.out.println("[OBJLoader.loadObjModel]: It took " + delta + " milliseconds to load " + fileName);

        System.out.println("[OBJLoader.loadObjModel]: Ordered vertex position array: " + ArrayUtils.getFloatArray(vertexPosArray));
        System.out.println("[OBJLoader.loadObjModel]: Ordererd texture coordinates array: " + ArrayUtils.getFloatArray(texturesArray));
        System.out.println("[OBJLoader.loadObjModel]: Ordererd indices array: " + ArrayUtils.getIntArray(indicesArray));

        return new GameEntity(vertexPosArray, indicesArray, texturesArray, texturePath);
    }

    /**
     * The input to this method is vertex data as a String array, which is used to determine how to
     * arrange texture coordinate and normal vector data (this data is associated with each vertex position)
     * into the correct order in the texture and normals array
     * @param vertexData
     * @param indices
     * @param textrues
     * @param normals
     * @param textureArray
     * @param normalsArray
     */
    private static void processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures,
            List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
        int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
        System.out.println("[OBJLoader.processVertex]: currentVertexPointer = " + currentVertexPointer);
        indices.add(currentVertexPointer);
        System.out.println("[OBJLoader.processVertex]: Adding " + currentVertexPointer + " to indices!");

        Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
        textureArray[currentVertexPointer*2] = currentTex.x;
        textureArray[currentVertexPointer*2 + 1] = 1.0f - currentTex.y;
        System.out.println("[OBJLoader.processVertex]: Added vt " + currentTex.x + " to index " + currentVertexPointer*2 + 
                " and vt " + (1.0f - currentTex.y) + " to index " + (currentVertexPointer*2+1) + " of the textureArray");

        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;
    }
}

GameEntity 构造函数:

    /**
 * Creates a new textured GameEntity
 * @param vPositions The vertex coordinates of a model
 * @param indices The indices of a model (in which order should the vertices be bound by OpenGL?)
 * @param textureCoordinates The coordinates of a texture (which texture coordinate should be applied to which vertex?)
 * @param texturePath The path of the texture 
 * @throws Exception
 */
public GameEntity(float[] vPositions, int[] indices, float[] textureCoordinates, String texturePath) throws Exception{
    System.out.println("[GameEntity.GameEntity]: Creating a new model texture...");
    modelTexture = new Texture(texturePath);
    System.out.println("[GameEntity.GameEntity]: Creating new mesh based on parameters... ");
    mesh = new Mesh(vPositions, indices, textureCoordinates, modelTexture);

    System.out.println("[GameEntity.GameEntity]: Initializing position, scale and rotation instance fields... ");
    position = new Vector3f(0, 0, 0);
    scale = 1;
    rotation = new Vector3f(0, 0, 0);
}

只注意顶点位置、索引和纹理坐标(以及创建的纹理)被发送到 Mesh 构造函数的事实:

网格构造器

/**
     * This constructor creates a renderable object (instance of Mesh with its texture) out of input parameters by storing them
     * in the vao of that Mesh instance
     * @param vertices The vertex positions of a model
     * @param indices The indices to tell OpenGL how to connect the vertices
     * @param texCoords Texture coordinates (used for texture mapping)
     * @param texture A Texture object
     */
    public Mesh(float[] vertices, int[] indices, float[] texCoords, renderEngine.Texture texture) {
        System.out.println("[Mesh.Mesh]: Creating a new textured Mesh instance... ");

        verticesBuffer = null;
        textureBuffer = null;
        indicesBuffer = null;

        try {
            this.texture = texture;
            vertexCount = indices.length;

            vbos = new ArrayList<>();
            vaos = new ArrayList<>();
            textures = new ArrayList<>();

            System.out.println("[Mesh] Creating and binding the vao (vaoID)");
            vaoID = glGenVertexArrays();
            vaos.add(vaoID);
            glBindVertexArray(vaoID);

            setupVerticesVbo(vertices);

            setupIndicesBuffer(indices);

            setupTextureVbo(texCoords);

            textures.add(texture);

            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        }
    }

Mesh class 的相关方法是 setupIndicesBuffersetupTextureVbo:

    private void setupIndicesBuffer(int[] indices)  {
        indicesVboID = glGenBuffers();
        vbos.add(indicesVboID);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVboID);
        indicesBuffer = BufferUtilities.storeDataInIntBuffer(indices);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
    }

    /**
 * This method sets up the texture vbo for a mesh object (buffers data to it and assigns it to attribute list
 * index 1 of the vao)
 * 
 * @param colours - an array of colours of the vertices of a model
 */
private void setupTextureVbo(float[] textures) {
    System.out.println("[Mesh] Creating texture vbo (textureVboID)...");
    textureVboID = glGenBuffers();
    vbos.add(textureVboID);

    System.out.println("   - [Mesh] Creating texture buffer (textureBuffer)...");
    textureBuffer = BufferUtilities.storeDataInFloatBuffer(textures);

    System.out.println("   - [Mesh] Binding textureVboID to GL_ARRAY_BUFER...");
    glBindBuffer(GL_ARRAY_BUFFER, textureVboID);

    System.out.println("   - [Mesh] Buffering data from textureBuffer to GL_ARRAY_BUFFER...");
    glBufferData(GL_ARRAY_BUFFER, textureBuffer, GL_STATIC_DRAW);

    System.out.println("   - [Mesh] Sending texture vbo to index 1 of the active vao...");
    glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
}

cube.obj

# Blender v2.78 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
o Cube
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 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.2766 0.2633
vt 0.5000 0.4867
vt 0.2766 0.4867
vt 0.7234 0.4867
vt 0.9467 0.2633
vt 0.9467 0.4867
vt 0.0533 0.4867
vt 0.0533 0.2633
vt 0.2766 0.0400
vt 0.5000 0.2633
vt 0.0533 0.7100
vt 0.7234 0.2633
vt 0.0533 0.0400
vt 0.2766 0.7100
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
vn 1.0000 -0.0000 0.0000
vn 0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn 0.0000 0.0000 -1.0000
s off
f 2/1/1 4/2/1 1/3/1
f 8/4/2 6/5/2 5/6/2
f 5/7/3 2/1/3 1/3/3
f 6/8/4 3/9/4 2/1/4
f 3/10/5 8/4/5 4/2/5
f 1/3/6 8/11/6 5/7/6
f 2/1/1 3/10/1 4/2/1
f 8/4/2 7/12/2 6/5/2
f 5/7/3 6/8/3 2/1/3
f 6/8/4 7/13/4 3/9/4
f 3/10/5 7/12/5 8/4/5
f 1/3/6 4/14/6 8/11/6

我的成就

视频首先显示 OBJLoader class 的源代码。然后,它显示了立方体上的纹理是如何被错误映射的(立方体的背面和左侧面除外)。然后,它显示了一个文件,我在其中分析了保存纹理坐标信息的数组中的每个索引被写入了多少次。最后,显示了应该映射的纹理。

问题

如视频中所示,立方体的六个面中有四个面的纹理贴图不正确。

我知道: - 纹理坐标从 .obj 文件中正确读取并存储在 textures ArrayList 中。可以在 switch 子句中找到相关代码:

case "vt":
        Vector2f texture = new Vector2f(Float.parseFloat(splitLine[1]), Float.parseFloat(splitLine[2]));
        textures.add(texture);
        break;

我 50/50 确定: - 索引从 .obj 文件中正确确定,因为如果它们未正确确定,则根本不会绘制立方体。与此相关的代码可以在 processVertex 方法中找到:

int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
indices.add(currentVertexPointer);

我不确定是否: - 纹理在名为 texturesArray 的最终浮点数组中正确排序。与此步骤相关的代码可以在 processVertex 方法中找到:

Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
textureArray[currentVertexPointer*2] = currentTex.x;
textureArray[currentVertexPointer*2 + 1] = 1.0f - currentTex.y;

它应该如何工作:

首先,纹理坐标将从 .obj 文件中读取并存储为名为 textures 的 ArrayList 中的 Vector2f(二维向量,基本上只是存储 x 和 y 值) .

但是,为了让 OpenGL 正常工作,应该重新排列这些纹理坐标,以便它们与相应的顶点相匹配(至少这是我从多个教程中读到的)。这是通过读取所谓的多边形面元素来完成的。

这些多边形面元素由以 f 开头的每一行描述。每条这样的线描述了三个顶点。每个顶点由顶点位置、纹理坐标和法向量表示。此类行的示例:f 8/4/2 6/5/2 5/6/2。仔细看看 8/4/2 顶点表示。这表明该顶点的位置等于 .obj 文件中指定的第八个顶点位置 (-1.000000 1.000000 -1.000000),纹理坐标等于文件中指定的第四个纹理坐标 (0.7234 0.4867) 和第二个法向量(0.0000 1.0000 0.0000).

processVertex 方法在找到以 f 开头的行时调用三次,以处理描述该多边形面元素的每个顶点(一次用于 8/4/2, 一次用于 6/5/2,一次用于 5/6/2)。每次,将一组数据作为字符串数组(在正斜杠的位置拆分)传递给该方法,然后是 List textures、List normals、float[] textureArray和 float[] normalsArray.

private static void processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures,
        List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
    int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
    System.out.println("[OBJLoader.processVertex]: currentVertexPointer = " + currentVertexPointer);
    indices.add(currentVertexPointer);
    System.out.println("[OBJLoader.processVertex]: Adding " + currentVertexPointer + " to indices!");

    //something probably wrong here 
    Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
    textureArray[currentVertexPointer*2] = currentTex.x;
    textureArray[currentVertexPointer*2 + 1] = 1.0f - currentTex.y;
    System.out.println("[OBJLoader.processVertex]: Added vt " + currentTex.x + " to index " + currentVertexPointer*2 + 
            " and vt " + (1.0f - currentTex.y) + " to index " + (currentVertexPointer*2+1) + " of the textureArray");

    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;
}

请注意,顶点法线数据可以忽略,因为它不相关。

当前顶点索引首先通过从传递的字符串数组中的第一个数字减去一个来确定(例如,如果将 8/4/2 作为参数传递,则 7 将分配给 currentVertexPointer)。减一的原因是 wavefront .obj 文件中的图像从 1 开始,而 Java 中的索引从 0 开始。然后,这个数字被添加到 indices 列表中。

然后,通过读取作为参数传递的字符串数组中的第二个数字并减去一个(例如,如果将 8/4/2 作为参数传递,将获得 textures 列表中索引 3 处的 Vector3f: Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);

现在得到了当前正在处理的顶点对应的纹理坐标(由currentVertexPointer表示),现在必须将数据相应地存储在textureArray中,然后传递给 GameEntity 以构建可渲染对象(关于此的详细信息将不会讨论...底线:此数组中的排序很重要,因为它会影响纹理映射到模型的方式).

为了相应地存储纹理坐标,第一个纹理坐标(us 被存储在索引处textureArray 的大小是 currentVertexPointer 的两倍,因为每个顶点都有两个纹理坐标:textureArray[currentVertexPointer*2] = currentTex.x;。第二个纹理坐标(vt 存储在 textureArray 的索引处,它比第一个纹理的索引大一个坐标:textureArray[currentVertexPointer*2 + 1] = 1.0f - currentTex.y;.

请注意,由于波前文件和 OpenGL 的纹理坐标空间之间存在差异,因此从 1.0f 中减去第二个纹理坐标。

我的观察

每次将新纹理分配给 textureArray 中的新(或现有)索引时,我都进行了跟踪,发现某些索引被覆盖,这可能是问题的原因

分析数据后,我得到了这样的文件,它在右侧的 textureArray 中显示填充的索引以及在执行期间分配给这些索引的各种元素:

 Index | values that get assigned to the index during execution of the program
    0 | 0.2766 ... 0.2766 (third f-call) ... 0.2766 (sixth f-call) ... 0.2766 (14th f-call)
    1 | 0.5133 ... 0.5133 (third f-call) ... 0.5133 (sixth f-call) ... 0.5133 (14th f-call)
    2 | 0.2766 ... 0.2766 (third f-call) ... 0.2766 (fourth f-call) ... 0.2766 (seventh f-call) ... 0.2766 (ninth f-call)
    3 | 0.7367 ... 0.7367 (third f-call) ... 0.7367 (fourth f-call) ... 0.7367 (seventh f-call) ... 0.7367 (ninth f-call)
    4 | 0.2766 ... 0.5 (fifth f-call) ... 0.5 (seventh f-call) ... 0.2766 (twelveth f-call) ... 0.5 (13th f-call)
    5 | 0.96 ... 0.7367 (fifth f-call) ... 0.7367 (seventh f-call) ... 0.96 (twelveth f-call) ... 0.7367 (13th f-call)
    6 | 0.5 ... 0.5 (fifth f-call) ... 0.5 (seventh f-call) ... 0.2766 (14th f-call)
    7 | 0.5133 ... 0.5133 (fifth f-call) ... 0.5133 (seventh f-call) ... 0.29000002 (14th f-call)
    8 | 0.9467 ... 0.0533 (third f-call) ... 0.0533 (sixth f-call) ... 0.0533 (ninth f-call)
    9 | 0.5133 ... 0.5133 (third f-call) ... 0.5133 (sixth f-call) ... 0.5133 (ninth f-call)
    10 | 0.9467 ... 0.0533 (fourth f-call) ... 0.9467 (eighth f-call) ... 0.0533 (ninth f-call) ... 0.0533 (twelveth f-call)
    11 | 0.7367 ... 0.7367 (fourth f-call) ... 0.7367 (eighth f-call) ... 0.7367 (ninth f-call) ... 0.7367 (twelveth f-call)
    12 | 0.7234 ... 0.0533 (twelveth f-call) ... 0.7234 (13th f-call)
    13 | 0.7367 ... 0.96 (twelveth f-call) ... 0.7367 (13th f-call)
    14 | 0.7234 ... 0.7234 (fifth f-call) ... 0.0533 (sixth f-call) ... 0.7234 (eighth f-call) ... 0.7234 (13th f-call) ... 0.0533 (14th f-call)
    15 | 0.5133 ... 0.5133 (fifth f-call) ... 0.29000002 (sixth f-call) ... 0.5133 (eighth f-call) ... 0.5133 (13th f-call) ... 0.29000002 (14th f-call)

    All of the indexes in the texturesArray have been accessed and assigned values several time.

    Indexes with unchanged values (ie, indexes which have been assigned the same value every time):
    0, 1, 2, 3, 9, 11

    Indexes with changed value (ie, indexes which have been assigned different values):
    4, 5, 6, 7, 8, 10, 12, 13, 14

很明显,大部分索引都被不同的纹理坐标数据覆盖了。用相同的纹理坐标数据覆盖的索引是 0、1、2、3、9 和 11。因此我推测,由于这些索引被相同的值覆盖的面,背面和左面被正确映射,而其他索引被不同的值覆盖。

如何解决问题?

咦,这已经很长了,不是吗?谢谢你一直以来的付出,我真的很感激。

编辑 #1

在@florentt 的第一个回答之后,我已经完成将以下代码集成到 processVertex 方法中:

    private static void processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures,
        List<Vector3f> normals, float[] textureArray, float[] normalsArray) {

    int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
    System.out.println("[OBJLoader.processVertex]: currentVertexPointer = " + currentVertexPointer);
    indices.add(currentVertexPointer);

    //THIS IS NEW
    Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
    if ((textureArray[currentVertexPointer*2] + textureArray[currentVertexPointer*2+1])== 0 ) { //if the index hasn't been populated yet, store it
        textureArray[currentVertexPointer*2] = currentTex.x;
        textureArray[currentVertexPointer*2+1] = 1.0f - currentTex.y;
    } else {
        //create a new vertex (index?) and associate it with second coordinate u
        //create a new vertex (index?) and associate it with texture coordinate v
    }
    //END OF NEW CODE

    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;
}

他报告说,问题是由一个顶点与几个不同的顶点坐标(属于不同的面)相关联这一事实引起的。每个这样的顶点都应该被复制并分配一个相应的纹理坐标。我在 processVertex 方法中添加了一个 if 子句,它检查特定纹理坐标集的索引在 texturesArray 中是否为空。如果浮点数组中的索引为空,则其值为 0。要计算此索引和连续索引是否为空(每个顶点有两个纹理坐标),则这些索引处的值之和必须为 0,如果它们是都是空的。如果这两个索引尚未填充纹理坐标,则为它们分配可以从当前处理的多边形面元素(即8/4/2)获得的纹理坐标。

但是,当索引已经被填充时,我不知道该怎么做。我知道应该复制位置向量并分配相应的纹理坐标(访问方式与上述相同),但这不会改变从 .obj 文件中读取的原始位置向量的整个 ArrayList 吗?在 processVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures, List<Vector3f> normals, float[] textureArray, float[] normalsArray) 方法的情况下,这些重复的向量应该存储在哪里?您是否应该只复制这个位置向量的索引,然后为这个索引分配一个纹理坐标?那么应该如何将纹理坐标存储到这个新索引中?

我的第一个尝试是引入以下 if-else 语句:

if ((textureArray[currentVertexPointer*2] + textureArray[currentVertexPointer*2+1])== 0 ) { //if the index hasn't been populated yet, store it
        textureArray[currentVertexPointer*2] = currentTex.x;
        textureArray[currentVertexPointer*2+1] = 1.0f - currentTex.y;
    } else {
        int duplicateVertexPointer = currentVertexPointer;
        indices.add(duplicateVertexPointer);
        textureArray[duplicateVertexPointer*2] = currentTex.x;
        textureArray[duplicateVertexPointer*2+1] = currentTex.y;
    }

无论如何,上面的效果比以前更糟。现在,立方体甚至不再呈现为立方体,而是呈现为一个单独的三角形和面,中间为空。请帮助:(

看来您很快就要找出问题所在了。是的,一些数据被覆盖了,因为一个对象顶点可以在不同的面上有多个法线或纹理向量。问题是 openGL 中的顶点属性不是这种情况。

你需要做的是检查你的顶点是否已经有一个纹理坐标,如果有你创建一个新的顶点并将第二个纹理关联到它。小心它可能有点乱。 假设您有 v1,一个顶点。在不同的面,它有几个纹理坐标。

V1 -> t1; v1 -> t2; v1 -> t2。 所以你说 V1 -> t1,简单。但是随后您会在同一顶点上看到另一个坐标。所以你克隆 v1,然后得到 v2 -> t2(v1 == v2)。 现在v1->t2,你不应该创建一个新的顶点v3,因为v2已经存在并且非常适合。

所以要正确地做到这一点,您需要跟踪克隆并查看它们是否符合组合 index/coordinate。

当你同时拥有法线坐标和纹理坐标时,它会变得更加混乱,因为两个面可以共享纹理坐标但不能共享法线坐标,反之亦然,所有组合都存在。所以有时你会有一个适合特定组合但不是所有组合的克隆。

我做了一个 obj 解析器,它可以工作(大概)但是有点乱,所以给你代码是一种伤害。我希望我至少让你走上了正确的轨道。

编辑如果我再次制作解析器,我会这样做。 每次我在脸上添加一个顶点时,我都会调用以下函数:

vector<vertex> vertices; //All the vertices in my object, this includes the position, texture coordinate and normal
vector<vector<int>> synonyms; // For each vertex, the array of vertices which are the same (cloned vertices)
vector<int> normalIndex;
vector<int> UV index;
int normalIn;
int textureIn;
int vertexIn; //each elements in one point of my face declaration in the obj file vertexIn/textureIn/normalIn

funtion(all of the above)
{

    vector<int> synonymsVertex = synonyms[vertexIn]; //We get the synonyms of the vertex we want to add
    for(int vertexClone : synonymsVertex)
    {
        vertex vertexObj = vertices[vertexClone];
        //In the case the combination doesn't exist, we clone the vertex and add it to the list
        if(vertexObj.normal != normalIn || vertexObj.UV != textureIn)
        {
             vertex newVertex(vertexObj, normalIn, textureIn);
             vertices.push_back(newVertex);
             synonymsVertex.push_back(vertices.size - 1);
        }
    }
}