【问题标题】:Why are textures displayed incorrectly when using indexed rendering (glDrawElements) when trying to load them from a wavefront .obj file?为什么在尝试从波前 .obj 文件加载纹理时使用索引渲染 (glDrawElements) 时纹理显示不正确?
【发布时间】:2018-11-21 04:47:30
【问题描述】:

简介

我正在构建一个简单的波前 .obj 文件解析器。我已经设法让它读取文件,存储它的内容(顶点位置、顶点坐标、顶点法线(尚未使用它们)和多边形面元素信息(例如 5/2/3))。然后将此数据传递给一个类(称为 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类的相关方法有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);
}

立方体.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);

我不确定是否: - 纹理在名为 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 文件中读取并作为 Vector2f(二维向量,基本上只是存储 x 和 y 值)存储在名为 textures 的 ArrayList 中。

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

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

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

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)。减去 1 的原因是波前 .obj 文件中的图像从 1 开始,而 Java 中的索引从 0 开始。然后,这个数字被添加到 indices 列表中。

然后,通过读取作为参数传递的String数组中的第二个数字并减去一个(例如,如果@ 987654344@作为参数传递,将获得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;

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

我的观察

我跟踪了每次将新纹理分配给 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&lt;Integer&gt; indices, List&lt;Vector2f&gt; textures, List&lt;Vector3f&gt; 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;
    }

不过,上述方法的效果比以前更差。现在,立方体甚至没有被渲染为立方体,而是作为一个单独的三角形和面,中间是空的。请帮忙:(

【问题讨论】:

    标签: java parsing opengl lwjgl wavefront


    【解决方案1】:

    您似乎非常接近找出问题所在。是的,一些数据正在被覆盖,因为一个 obj 顶点可以在不同的面上有多个法线或纹理向量。问题在于,openGL 中的顶点属性并非如此。

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

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

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

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

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

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

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

    【讨论】:

    • 你确实做到了!非常感谢您这么详细的回答,很高兴收到它!
    • 请看一下#edit 1。此外,是否可以让我访问您的代码,因为我相信这会很有帮助。最好的问候
    • 糟糕,忘记标记你了...@florentt
    • @Fish_In_A_Suit 如果你真的想在那里看到我的代码,那就是:github.com/fteppe/tetraEngine/blob/master/TetraRenderLib/…,但它是一团糟。看看 tinyobjloader。
    • @Fish_In_A_Suit 我添加了一些我认为可行的伪代码。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2012-11-28
      • 2020-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-06
      • 1970-01-01
      • 2020-12-17
      相关资源
      最近更新 更多