Отображение текстуры на воксельной модели в Open GL

Я слежу за серией видео об Open GL на YouTube с использованием LWJGL, до сих пор мне удалось визуализировать 3D-модели и правильно их текстурировать, используя формат obj. Я хочу использовать в своей игре модели на основе вокселей, поэтому я зашел в программу MagicaVoxel, экспортировал текстурированный пример в формате obj, но текстура отображается неправильно. Действительно, некоторые цвета кажутся правильно отображенными, но на других лицах есть вся текстура.

Вот изображение ожидаемого результата:

Ожидаемый результат

и фактический результат:

Фактический результат

Я думаю, что проблема связана с текстурой и способом ее интерполяции opengl, текстура представляет собой строку размером 1 * 256 с цветами, а в файле obj только желаемые цвета помещаются в координаты uv.

Я сделал более простой пример, чтобы помочь понять, что происходит: всего 3 куба, выровненных друг с другом, и текстура длиной 3 пикселя с 3 разными цветами, вот код файла obj, и текстура слишком мала, чтобы ее можно было увидеть но на самом деле это всего лишь 3 цветных пикселя.

# normals
vn -1 0 0
vn 1 0 0
vn 0 0 1
vn 0 0 -1
vn 0 -1 0
vn 0 1 0

# texcoords
vt 0.25 0.5
vt 0.5 0.5
vt 0.75 0.5

# verts
v -0.1 0 0
v -0.1 0 -0.1
v -0.1 0.1 0
v -0.1 0.1 -0.1
v 0.2 0 0
v 0.2 0 -0.1
v 0.2 0.1 0
v 0.2 0.1 -0.1
v -0.1 0 0
v -0.1 0.1 0
v 0 0 0
v 0 0.1 0
v 0.1 0 0
v 0.1 0.1 0
v 0.2 0 0
v 0.2 0.1 0
v -0.1 0 -0.1
v -0.1 0.1 -0.1
v 0 0 -0.1
v 0 0.1 -0.1
v 0.1 0 -0.1
v 0.1 0.1 -0.1
v 0.2 0 -0.1
v 0.2 0.1 -0.1
v -0.1 0 0
v 0 0 0
v 0.1 0 0
v 0.2 0 0
v -0.1 0 -0.1
v 0 0 -0.1
v 0.1 0 -0.1
v 0.2 0 -0.1
v -0.1 0.1 0
v 0 0.1 0
v 0.1 0.1 0
v 0.2 0.1 0
v -0.1 0.1 -0.1
v 0 0.1 -0.1
v 0.1 0.1 -0.1
v 0.2 0.1 -0.1

# faces
f 3/2/1 2/2/1 1/2/1
f 4/2/1 2/2/1 3/2/1
f 5/1/2 6/1/2 7/1/2
f 7/1/2 6/1/2 8/1/2
f 11/2/3 10/2/3 9/2/3
f 12/2/3 10/2/3 11/2/3
f 13/3/3 12/3/3 11/3/3
f 14/3/3 12/3/3 13/3/3
f 15/1/3 14/1/3 13/1/3
f 16/1/3 14/1/3 15/1/3
f 17/2/4 18/2/4 19/2/4
f 19/2/4 18/2/4 20/2/4
f 19/3/4 20/3/4 21/3/4
f 21/3/4 20/3/4 22/3/4
f 21/1/4 22/1/4 23/1/4
f 23/1/4 22/1/4 24/1/4
f 29/2/5 26/2/5 25/2/5
f 30/3/5 27/3/5 26/3/5
f 30/2/5 26/2/5 29/2/5
f 31/1/5 28/1/5 27/1/5
f 31/3/5 27/3/5 30/3/5
f 32/1/5 28/1/5 31/1/5
f 33/2/6 34/2/6 37/2/6
f 34/3/6 35/3/6 38/3/6
f 37/2/6 34/2/6 38/2/6
f 35/1/6 36/1/6 39/1/6
f 38/3/6 35/3/6 39/3/6
f 39/1/6 36/1/6 40/1/6 

Как вы можете видеть для каждой грани, 3 UV-координаты, взятые для 3 вершин одинаковы, но в OpenGL это результат (они должны быть красными, синими и желтыми):

Результат 3 кубика

Вот мой код чтения файлов OBJ (на Java), который я вызываю для создания vao и прочего для рендеринга:

public static RawModel loadObjModel(String fileName, Loader loader) {
        FileReader fr = null;
        try {
            fr = new FileReader(new File("res/" + fileName + ".obj"));
        } catch (FileNotFoundException e) {
            System.err.println("Couldn't load file!");
            e.printStackTrace();
        }
        BufferedReader reader = new BufferedReader(fr);
        String line;
        List<Vector3f> vertices = new ArrayList<Vector3f>();
        List<Vector2f> textures = new ArrayList<Vector2f>();
        List<Vector3f> normals = new ArrayList<Vector3f>();
        List<Integer> indices = new ArrayList<Integer>();
        float[] verticesArray = null;
        float[] normalsArray = null;
        float[] textureArray = null;
        int[] indicesArray = null;
        try {

            while (true) {
                line = reader.readLine();
                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);
                } else if (line.startsWith("f ")) {
                    textureArray = new float[vertices.size() * 2];
                    normalsArray = new float[vertices.size() * 3];
                    break;
                }
            }

            while (line != null) {
                if (!line.startsWith("f ")) {
                    line = reader.readLine();
                    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);
                line = reader.readLine();
            }
            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, indicesArray, textureArray);

    }

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

Вот мой фрагментный шейдер:

#version 400 core

in vec2 pass_textureCoords;

out vec4 out_colour;

uniform sampler2D textureSampler;

void main(void){

    out_colour = texture(textureSampler,pass_textureCoords);
}

Вот мой вершинный шейдер:

#version 400 core

in vec3 position;
in vec2 textureCoords;

out vec2 pass_textureCoords;

uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;

void main(void){

    gl_Position = projectionMatrix * viewMatrix * transformationMatrix * vec4(position.xyz,1.0);
    pass_textureCoords = textureCoords;
}

А вот мой метод рендеринга, вызываемый каждым кадром:

public void render(Entity entity,StaticShader shader) {

        TexturedModel texturedModel = entity.getTexturedModel();
        RawModel model = texturedModel.getRawModel();
        GL30.glBindVertexArray(model.getVaoID());
        
        GL20.glEnableVertexAttribArray(0);
        GL20.glEnableVertexAttribArray(1);
        
        Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), entity.getRotation(), entity.getScale());
        shader.loadTransformationMatrix(transformationMatrix);
        
        GL13.glActiveTexture(GL13.GL_TEXTURE0);



        GL11.glBindTexture(GL11.GL_TEXTURE_2D, texturedModel.getTexture().getID());
        
        GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(),GL11.GL_UNSIGNED_INT,0);
        
        GL20.glDisableVertexAttribArray(0);
        GL20.glDisableVertexAttribArray(1);

        GL30.glBindVertexArray(0);
    }

Я не понимаю, что мне не хватает, и снова это уже сработало для модели блендера с большой текстурой:

рабочая модель


person Thibault Abry    schedule 26.08.2020    source источник


Ответы (1)


Ваш загрузчик файлов Wavefront OBJ действительно работает только в очень частном случае, а именно, когда никакие координаты текстуры или нормали не являются общими для любых двух вершин, так что спецификации v, vn и vt имеют соответствие 1: 1 друг другу. Однако, как правило, это не так. Если не учитывать тот факт, что ваш загрузчик файлов OBJ Wavefront также работает только тогда, когда все строки 'f' идут после всех строк v, vt и vn (что также не всегда так), у вас все еще есть несколько других проблем. Итак, основная проблема на данный момент заключается в том, что вы предполагаете соответствие 1: 1 между строками «v» и «vt», что не так. Как правило, вы не можете просто использовать индекс 'v' (как указано через первое значение с разделителем '/' в строке 'f') в качестве индекса буфера элемента OpenGL, потому что OpenGL имеет только один индекс одного элемента для индексации в позицию массивы текстур и нормалей размещаются единообразно, тогда как формат файла Wavefront OBJ имеет три разных индекса, каждый для положения, текстур и нормалей отдельно. Итак, что вам следует сделать, так это переосмыслить / переписать загрузчик файлов OBJ Wavefront таким образом, чтобы он в основном собирал всю информацию о положении (v), координатах текстуры (vt) и нормалях (vn), а затем всякий раз, когда вы сталкиваетесь с face (f) вы добавляете информацию о положении, текстуре и нормалях по указанным индексам (в строке 'f') в ваши окончательные буферы результатов. Вы не можете просто использовать один индекс, полученный из индекса позиции строки 'f'. Вы либо вообще не используете индексы, либо просто используете непрерывно увеличивающийся индекс в качестве буфера элементов OpenGL.

Но сначала я настоятельно рекомендую прочитать актуальную спецификацию формата файла Wavefront OBJ, например: http://paulbourke.net/dataformats/obj/

person Kai Burjack    schedule 26.08.2020
comment
Другая возможность (если вы используете LWJGL 3) - просто использовать привязки Assimp, чтобы позволить Assimp обрабатывать загрузку файла OBJ Wavefront. - person Kai Burjack; 26.08.2020
comment
спасибо за ответ, я попробую изменить свой загрузчик obj, потому что, к сожалению, я использую LWJGL 2. - person Thibault Abry; 26.08.2020
comment
Я заставил это работать, мне не хватало того, что каждая вершина ДОЛЖНА быть уникальной, например, у нее не может быть двух разных uv-координат. Поэтому я оставил индексный буфер (я просто считаю от 0 до n) и продублировал все вершины для каждой грани, что требует немного больше памяти и времени на вычисления, но на данный момент проще. - person Thibault Abry; 26.08.2020