一起绘制时,OpenGL 纹理出现在错误的位置

OpenGL textures appear in wrong places when drawn together

我是 OpenGL 的新手,正在尝试绘制两个具有不同纹理的正方形。我使用 lwjgl 3 作为 OpenGL 的接口,但我相信对于使用其他语言的 OpenGL 的人来说,OpenGL 调用应该看起来很熟悉。我的主循环如下所示:

    while (glfwWindowShouldClose(windowId) == GLFW_FALSE) {
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shaderProgramId);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

        // DRAW TEXTURE 1
        specifyVertexAttributes(shaderProgramId);
        glBindTexture(GL_TEXTURE_2D, texture1.getId());
        glBindBuffer(GL_ARRAY_BUFFER, vbo1);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        // DRAW TEXTURE 2
        specifyVertexAttributes(shaderProgramId);
        glBindTexture(GL_TEXTURE_2D, texture2.getId());
        glBindBuffer(GL_ARRAY_BUFFER, vbo2);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(windowId);

        glfwPollEvents();
    }

当我注释掉绘制纹理 2 的代码时,纹理 1 绘制在正确的位置:

当我注释掉绘制纹理 1 的代码时,纹理 2 绘制在正确的位置:

但是当我尝试绘制两个纹理时,它们交换了位置:

我意识到上面的代码片段可能不足以诊断此问题。我创建了一个独立的 java class,其中包含我为绘制这些纹理所做的所有 OpenGL 调用:StandaloneMultiTextureExample。包含该文件的 repo 也使用 gradle 构建。对于任何愿意帮助检查 repo 和 运行 那个例子 class.

的人来说应该很容易

编辑:StandaloneMultiTextureExample.java 的副本(不导入)

public class StandaloneMultiTextureExample {

    private final GLFWErrorCallback errorCallback = new LoggingErrorCallback();
    private final GLFWKeyCallback keyCallback = new ApplicationClosingKeyCallback();

    public void run() {
        if ( glfwInit() != GLFW_TRUE ) {
            throw new IllegalStateException("Unable to initialize GLFW");
        }

        glfwSetErrorCallback(errorCallback);
        int width = 225;
        int height = 200;
        long windowId = createWindow(width, height);
        glfwSetKeyCallback(windowId, keyCallback);

        glfwShowWindow(windowId);
        GL.createCapabilities();

        Texture texture1 = createTexture("multiTextureExample/texture1.png");
        Texture texture2 = createTexture("multiTextureExample/texture2.png");

        int shaderProgramId = createShaderProgram(
                "multiTextureExample/textureShader.vert",
                "multiTextureExample/textureShader.frag");

        IntBuffer elements = BufferUtils.createIntBuffer(2 * 3);
        elements.put(0).put(1).put(2);
        elements.put(2).put(3).put(0);
        elements.flip();

        int ebo = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements, GL_STATIC_DRAW);

        float x1 = 0;
        float y1 = 0;
        float x2 = x1 + texture1.getWidth();
        float y2 = y1 + texture1.getHeight();
        float x3 = 25;
        float x4 = x3 + texture2.getWidth();

        FloatBuffer texture1Vertices = BufferUtils.createFloatBuffer(4 * 7);
        texture1Vertices.put(x1).put(y1).put(1).put(1).put(1).put(0).put(0);
        texture1Vertices.put(x2).put(y1).put(1).put(1).put(1).put(1).put(0);
        texture1Vertices.put(x2).put(y2).put(1).put(1).put(1).put(1).put(1);
        texture1Vertices.put(x1).put(y2).put(1).put(1).put(1).put(0).put(1);
        texture1Vertices.flip();

        FloatBuffer texture2Vertices = BufferUtils.createFloatBuffer(4 * 7);
        texture2Vertices.put(x3).put(y1).put(1).put(1).put(1).put(0).put(0);
        texture2Vertices.put(x4).put(y1).put(1).put(1).put(1).put(1).put(0);
        texture2Vertices.put(x4).put(y2).put(1).put(1).put(1).put(1).put(1);
        texture2Vertices.put(x3).put(y2).put(1).put(1).put(1).put(0).put(1);
        texture2Vertices.flip();

        int vbo1 = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo1);
        glBufferData(GL_ARRAY_BUFFER, texture1Vertices, GL_STATIC_DRAW);

        int vbo2 = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vbo2);
        glBufferData(GL_ARRAY_BUFFER, texture2Vertices, GL_STATIC_DRAW);

        specifyUniformVariables(windowId, shaderProgramId);

        glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

        while (glfwWindowShouldClose(windowId) == GLFW_FALSE) {
            glClear(GL_COLOR_BUFFER_BIT);

            glUseProgram(shaderProgramId);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

            specifyVertexAttributes(shaderProgramId);
            glBindTexture(GL_TEXTURE_2D, texture1.getId());
            glBindBuffer(GL_ARRAY_BUFFER, vbo1);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

            specifyVertexAttributes(shaderProgramId);
            glBindTexture(GL_TEXTURE_2D, texture2.getId());
            glBindBuffer(GL_ARRAY_BUFFER, vbo2);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

            glfwSwapBuffers(windowId);

            glfwPollEvents();
        }
    }

    private void specifyUniformVariables(long windowId, int shaderProgramId) {
        int uniModel = getUniform(shaderProgramId, "model");
        FloatBuffer model = BufferUtils.createFloatBuffer(16);
        new Matrix4f().get(model);
        glUniformMatrix4fv(uniModel, false, model);

        FloatBuffer view = BufferUtils.createFloatBuffer(16);
        new Matrix4f().get(view);
        int uniView = getUniform(shaderProgramId, "view");
        glUniformMatrix4fv(uniView, false, view);

        WindowSize windowSize = getWindowSize(windowId);
        int uniProjection = getUniform(shaderProgramId, "projection");
        FloatBuffer projection = BufferUtils.createFloatBuffer(16);
        new Matrix4f().ortho2D(0, windowSize.getWidth(), 0, windowSize.getHeight()).get(projection);
        glUniformMatrix4fv(uniProjection, false, projection);
    }

    private void specifyVertexAttributes(int shaderProgramId) {
        int stride = 7 * Float.BYTES;

        int posAttrib = getAttribute(shaderProgramId, "position");
        glEnableVertexAttribArray(posAttrib);
        glVertexAttribPointer(posAttrib, 2, GL_FLOAT, false, stride, 0);

        int colAttrib = getAttribute(shaderProgramId, "color");
        glEnableVertexAttribArray(colAttrib);
        glVertexAttribPointer(colAttrib, 3, GL_FLOAT, false, stride, 2 * Float.BYTES);

        int texAttrib = getAttribute(shaderProgramId, "texcoord");
        glEnableVertexAttribArray(texAttrib);
        glVertexAttribPointer(texAttrib, 2, GL_FLOAT, false, stride, 5 * Float.BYTES);
    }

    public long createWindow(int width, int height) {
        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

        long windowId = glfwCreateWindow(width, height, "Hello World!", NULL, NULL);
        if (windowId == NULL) {
            throw new RuntimeException("Failed to create the GLFW window");
        }

        // Get the resolution of the primary monitor
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
            windowId,
            (vidmode.width() - width) / 2,
            (vidmode.height() - height) / 2
        );

        // Make the OpenGL context current
        glfwMakeContextCurrent(windowId);
        // Enable v-sync
        glfwSwapInterval(1);

        return windowId;
    }

    public WindowSize getWindowSize(long windowId) {
        IntBuffer width = BufferUtils.createIntBuffer(1);
        IntBuffer height = BufferUtils.createIntBuffer(1);
        GLFW.glfwGetFramebufferSize(windowId, width, height);
        return new WindowSize(width.get(), height.get());
    }

    public Texture createTexture(String textureResource) {
        int textureId = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, textureId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        ARGBImage image = new ImageService().loadClasspathImage(textureResource);
        glTexImage2D(
                GL_TEXTURE_2D,
                0,
                GL_RGBA8,
                image.getWidth(),
                image.getHeight(),
                0,
                GL_RGBA,
                GL_UNSIGNED_BYTE,
                image.getContents());

        return new Texture(textureId, image.getWidth(), image.getHeight());
    }

    public int createShaderProgram(String vertexResource, String fragmentResource) {
        int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        String vertexSource = getClasspathResource(vertexResource);
        glShaderSource(vertexShader, vertexSource);
        glCompileShader(vertexShader);
        validateShaderCompilation(vertexShader);

        int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        String fragmentSource = getClasspathResource(fragmentResource);
        glShaderSource(fragmentShader, fragmentSource);
        glCompileShader(fragmentShader);
        validateShaderCompilation(fragmentShader);

        int shaderProgramId = glCreateProgram();
        glAttachShader(shaderProgramId, vertexShader);
        glAttachShader(shaderProgramId, fragmentShader);
        glLinkProgram(shaderProgramId);
        validateShaderProgram(shaderProgramId);
        glUseProgram(shaderProgramId);

        return shaderProgramId;
    }

    private static String getClasspathResource(String resourceName) {
        URL url = Resources.getResource(resourceName);
        try {
            return Resources.toString(url, Charsets.UTF_8);
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    private static void validateShaderCompilation(int shader) {
        int status = glGetShaderi(shader, GL_COMPILE_STATUS);
        if (status != GL_TRUE) {
            throw new RuntimeException(glGetShaderInfoLog(shader));
        }
    }

    private static void validateShaderProgram(int shaderProgram) {
        int status = glGetProgrami(shaderProgram, GL_LINK_STATUS);
        if (status != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(shaderProgram));
        }
    }

    public int getUniform(int shaderProgramId, String uniformName) {
        int location = glGetUniformLocation(shaderProgramId, uniformName);
        if (location == -1) {
            throw new IllegalArgumentException("Could not find uniform: "
                    + uniformName + " for shaderProgramId: " + shaderProgramId);
        } else {
            return location;
        }
    }

    public int getAttribute(int shaderProgramId, String attribute) {
        int location = glGetAttribLocation(shaderProgramId, attribute);
        if (location == -1) {
            throw new IllegalArgumentException("Could not find attribute: "
                    + attribute + " for shaderProgramId: " + shaderProgramId);
        } else {
            return location;
        }
    }

    public static void main(String[] args) {
        new StandaloneMultiTextureExample().run();
    }

}

您在错误的时间绑定顶点缓冲区,从而错误地设置了顶点数组。

当您使用顶点数组对象 (VAO) 渲染 [1] 时,读取 GL_ARRAY_BUFFER 绑定的唯一时间是 glVertexAttribPointer 调用。调用 glVertexAttribPointer 获取当前绑定的 GL_ARRAY_BUFFER 对象的名称并将其与属性和 VAO 相关联;在那之后,GL_ARRAY_BUFFER 绑定根本不重要,绑定另一个数组缓冲区将不会以任何方式修改 VAO。

在您的代码中,您在调用 glBindBuffer 之前 调用 specifyVertexAttributes 来设置您的 VAO。这意味着 glVertexAttribPointer 保存的数组缓冲区是以前使用的。在您的前两个示例中,您在任何时间点只绑定一个数组缓冲区,它 "works" 因为前一帧的绑定缓冲区仍然存在并在下一帧中读取;如果您在第一帧暂停程序,它可能会是黑色的。

您的解决方案很简单;将 glBindBuffer 调用移至 specifyVertexAttributes 调用上方,以便您的 glVertexAttribPointer 调用读取正确的缓冲区。

请注意,这 不适用于 GL_ELEMENT_ARRAY_BUFFER 绑定;每当您绑定新绑定时,绑定都会保存在 VAO 中。

[1] 从技术上讲,您使用的是默认 VAO,它仅在兼容性上下文中受支持,但创建和绑定一直使用的全局 VAO 很容易。