首页 文章

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

提问于
浏览
1

简介

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

源代码

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类的相关方法是 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

我取得了什么

  • 看一下this视频

  • 在GitHub上查看this页面以获取OBJLoader的说明

  • 查看this存储库中的源代码(OBJLoader尚未包含在内,但您可以查看其他类,如GameEntity或Mesh,因为这两个类是顶点数据被发送到的类)从.obj文件中提取) .

视频首先显示OBJLoader类的源代码 . 然后,它显示多维数据集上的纹理未正确映射(除了多维数据集的背面和左面) . 然后,它显示了一个文件,其中我已经分析了数组中保存纹理坐标信息的每个索引被写入的次数 . 最后,显示应该映射的纹理 .

问题

如视频中所示,立方体中六个面中的四个与纹理不正确地映射 .

我知道: - 纹理坐标正确地从.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);

我不确定是否: - 纹理在最终的float数组中正确排序,名为 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(2维向量,基本上只是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) .

当找到以f开头的行时,每次调用 processVertex 方法三次,以处理描述该多边形面元素的每个顶点(一次用于 8/4/2 ,一次用于 6/5/2 ,一次用于 5/6/2 ) . 每次,一组数据作为String数组(在正斜杠的位置拆分)传递给方法,然后是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;
}

请注意,顶点法线数据可以忽略,因为它是无关紧要的 .

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

然后,相应的纹理坐标(表示为Vector2f)从 textures List中获取,通过读取作为参数传递的字符串数组中的第二个数字并减去一个(例如,如果 8/4/2 作为参数传递,则Vector3f在索引3处传递 textures 将获得名单): 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 . 如果float数组中的索引为空,则它保持值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;
    }

不管怎样,上述情况比以前更糟糕 . 现在,立方体甚至不是渲染为立方体,而是作为一个单独的三角形和面部之间的空白 . 请帮忙 :(

2 回答

  • 0

    你似乎非常接近发现这个问题 . 是的,一些数据被覆盖,因为obj顶点可以在不同的面上具有多个法线或纹理矢量 . 问题是openGL中的顶点属性不是这种情况 .

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

    V1 - > t1; v1 - > t2; v1 - > t2 . 所以你说V1 - > t1,很简单 . 但是你会在同一个顶点看到另一个坐标 . 所以你克隆v1,得到v2 - > t2(v1 == v2) . 现在来v1-> t2,你不应该创建一个新的顶点v3,因为v2已经存在且完全适合 .

    因此,要正确地执行此操作,您需要跟踪克隆并查看它们是否适合组合索引/坐标 .

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

    我做了一个工作(ish)的obj解析器,但它有点混乱,所以给你代码将是一种伤害 . 我希望我至少把你送上了正确的轨道 .

    EDIT 这里's how I' d如果我要重新制作解析器,请执行此操作 . 每次我向我的脸添加一个顶点时,我都会调用以下函数:

    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);
            }
        }
    }
    
  • 2

    该问题是由于复制单个顶点的纹理坐标引起的 . 这是由于Blender中纹理接缝的性质,模型已经创建 . 对此的一个快速解决方案是选择模型纹理接缝的所有边缘,然后分割(复制)边缘(网格 - >边缘 - >边缘分割) .

相关问题