You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LWJGL中OpenGL 2D纹理渲染至4顶点的问题排查

LWJGL纹理渲染错误排查与修复

当前使用LWJGL开发项目,为包含位置、颜色、UV信息的8个顶点立方体渲染纹理时,单个2D面的纹理朝向始终不正确,怀疑问题出在顶点或元素顺序上,相关情况如下:

需要渲染的16x16纹理(已缩放至256x256,红色调试线分隔构成GL_TRIANGLE_STRIP的两个三角形):
带红色调试线的方块纹理

当前渲染效果:
当前渲染效果


负责渲染的核心代码

private void renderMenu() {
    // Define the vertices and colors of the cube
    float[] vertices = {
            //Position              Color                       Texture
            // Front face
            -0.5f, -0.5f,  0.5f,    1.0f, 0.0f, 0.0f, 1.0f,     1, 1,
            0.5f, -0.5f,  0.5f,     0.0f, 1.0f, 0.0f, 1.0f,     0, 0,
            -0.5f,  0.5f,  0.5f,    1.0f, 0.0f, 1.0f, 1.0f,     1, 0,
            0.5f,  0.5f,  0.5f,     1.0f, 1.0f, 0.0f, 1.0f,     0, 1};
            
    int[] elementArray = {
            2, 1, 0,
            0, 1, 3 
    };

    //Loading texture image
    String path = "src/main/resources/textures/test_texture16.png";
    IntBuffer width = BufferUtils.createIntBuffer(1);
    IntBuffer height = BufferUtils.createIntBuffer(1);
    IntBuffer channels = BufferUtils.createIntBuffer(1);
    ByteBuffer image = stbi_load(path, width, height, channels, 0);

    //Binding texture ID
    int texId = glGenTextures();
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texId);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glGenerateMipmap(GL_TEXTURE_2D);

    if (image != null) {
        //Loads image to GPU
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width.get(0), height.get(0),
                0, GL_RGBA, GL_UNSIGNED_BYTE, image);

        //Frees memory containing image
        stbi_image_free(image);
    } else {
        logger.warning("Texture could not be loaded from " + path);
    }

    /*==================================
    Buffer binding and loading
    ====================================*/

    // Create VAO
    int vaoID = glGenVertexArrays();
    glBindVertexArray(vaoID);

    // Create a float buffer of vertices
    FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertices.length);
    vertexBuffer.put(vertices).flip();

    // Create VBO upload the vertex buffer
    int vboID = glGenBuffers();
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);

    // Create the indices and upload
    IntBuffer elementBuffer = BufferUtils.createIntBuffer(elementArray.length);
    elementBuffer.put(elementArray).flip();

    // Create EBO upload the element buffer
    int eboID = glGenBuffers();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBuffer, GL_STATIC_DRAW);

    // Bind the VAO that we're using
    glBindVertexArray(vaoID);

    /*=====================================
    Vertex attribute definitions for shader
    ======================================*/
    int posSize = 3;
    int colorSize = 4;
    int uvSize = 2;
    int floatSizeBytes = 4;
    int vertexSizeBytes = (posSize + colorSize) * floatSizeBytes;

    //Position
    glVertexAttribPointer(0, posSize, GL_FLOAT, false, vertexSizeBytes, 0);
    glEnableVertexAttribArray(0);

    //Color
    glVertexAttribPointer(1, colorSize, GL_FLOAT, false, vertexSizeBytes, posSize * Float.BYTES);
    glEnableVertexAttribArray(1);

    //Texture
    glVertexAttribPointer(2, uvSize, GL_FLOAT, false, vertexSizeBytes, (posSize + colorSize) * Float.BYTES);
    glEnableVertexAttribArray(2);

    /*==================================
    View Matrix setup
    ====================================*/

    // Set up the model-view matrix for rotation
    modelViewMatrix = new Matrix4f();
    modelViewMatrix.translate(new Vector3f(0.0f, 0.0f, -2.0f));
    modelViewMatrix.rotate(angle, new Vector3f(0.0f, 1.0f, 0.0f));

    // Set up the model-view-projection matrix
    Matrix4f modelViewProjectionMatrix = new Matrix4f(projectionMatrix).mul(modelViewMatrix);

    // Pass the model-view-projection matrix to the shader as a uniform
    int mvpMatrixLocation = glGetUniformLocation(shaderProgram.getProgramId(), "modelViewProjectionMatrix");
    glUniformMatrix4fv(mvpMatrixLocation, false, modelViewProjectionMatrix.get(new float[16]));

    // Enable the vertex attribute pointers
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    /*==================================
    Drawing
    ====================================*/
    glDrawElements(GL_TRIANGLE_STRIP, elementArray.length, GL_UNSIGNED_INT, 0);

    //Unbind everything
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

顶点着色器代码

#version 330 core

layout(location = 0) in vec3 position;
layout(location = 1) in vec4 aColor;
layout (location = 2) in vec2 aTexCoords;

uniform mat4 modelViewProjectionMatrix;

out vec4 fColor;
out vec2 fTexCoords;

void main() {
    gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);
    fColor = aColor;
    fTexCoords = aTexCoords;
}

问题排查与修复方案

1. 顶点步长计算错误

原代码计算vertexSizeBytes时,仅包含了位置(3个float)和颜色(4个float)的字节数,漏掉了UV坐标(2个float),导致UV属性读取偏移错误。修正为:

int vertexSizeBytes = (posSize + colorSize + uvSize) * floatSizeBytes;

2. 绘制模式与元素数组不匹配

使用GL_TRIANGLE_STRIP绘制时,元素数组不需要6个索引(对应2个独立三角形),4个索引即可绘制完整四边形。两种修正方式:

  • 调整元素数组为GL_TRIANGLE_STRIP适配格式:
    int[] elementArray = { 0, 1, 2, 3 };
    
  • 保持元素数组不变,将绘制模式改为GL_TRIANGLES:
    glDrawElements(GL_TRIANGLES, elementArray.length, GL_UNSIGNED_INT, 0);
    

3. UV坐标与顶点位置对应关系错误

OpenGL纹理原点在左下角,原UV坐标与顶点位置的对应关系颠倒,导致纹理朝向错误。调整后的顶点UV部分:

float[] vertices = {
        //Position              Color                       Texture
        // Front face
        -0.5f, -0.5f,  0.5f,    1.0f, 0.0f, 0.0f, 1.0f,     0.0f, 0.0f, // 左下 → 纹理左下
        0.5f, -0.5f,  0.5f,     0.0f, 1.0f, 0.0f, 1.0f,     1.0f, 0.0f, // 右下 → 纹理右下
        -0.5f,  0.5f,  0.5f,    1.0f, 0.0f, 1.0f, 1.0f,     0.0f, 1.0f, // 左上 → 纹理左上
        0.5f,  0.5f,  0.5f,     1.0f, 1.0f, 0.0f, 1.0f,     1.0f, 1.0f  // 右上 → 纹理右上
};

4. 纹理加载顺序错误

glGenerateMipmap需在glTexImage2D之后调用,否则mipmap生成无效。修正后的纹理加载部分:

if (image != null) {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width.get(0), height.get(0),
            0, GL_RGBA, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D); // 移至glTexImage2D之后
    stbi_image_free(image);
}

同时,强制加载RGBA格式避免通道不匹配:

ByteBuffer image = stbi_load(path, width, height, channels, 4);

5. 冗余代码与属性启用遗漏

  • 去掉多余的glEnable(GL_TEXTURE1),未使用纹理单元1无需启用;
  • 绘制前需启用UV的顶点属性数组,补充:
    glEnableVertexAttribArray(2);
    

内容的提问来源于stack exchange,提问作者Matt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:07:05