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

OpenGL双着色器程序绘制双三角形问题:仅显示一个三角形

解决双三角形仅渲染一个的问题

嘿,我之前也踩过类似的OpenGL渲染坑!结合你说的用了两个VBO和着色器但只显示一个三角形的情况,大概率是着色器程序、VBO的切换逻辑没到位,或者顶点属性的绑定有问题。下面给你一步步拆解排查和修复方案:

核心问题排查方向

1. 渲染时没有正确切换着色器和VBO

很多新手容易犯的错:画完第一个三角形后,没有切换到第二个三角形的着色器程序和VBO,导致用第一个的配置去画第二个,结果要么覆盖,要么根本没画出来。

正确的渲染循环应该是每个物体单独激活对应着色器→绑定对应VBO→启用顶点属性→绘制→禁用属性,像这样:

// 绘制第一个三角形
glUseProgram(shaderProgram1); // 激活第一个着色器
glBindBuffer(GL_ARRAY_BUFFER, VBO1); // 绑定第一个VBO
glEnableVertexAttribArray(posLoc1); // 启用第一个着色器的位置属性
glVertexAttribPointer(posLoc1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(posLoc1);

// 绘制第二个三角形
glUseProgram(shaderProgram2); // 切换到第二个着色器
glBindBuffer(GL_ARRAY_BUFFER, VBO2); // 切换到第二个VBO
glEnableVertexAttribArray(posLoc2); // 启用第二个着色器的位置属性
glVertexAttribPointer(posLoc2, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(posLoc2);

2. 不要硬编码顶点属性位置

别假设in_position的属性位置是固定的!一定要用glGetAttribLocation分别获取每个着色器程序对应的属性位置:

// 第一个着色器链接成功后
GLint posLoc1 = glGetAttribLocation(shaderProgram1, "in_position");
// 第二个着色器链接成功后
GLint posLoc2 = glGetAttribLocation(shaderProgram2, "in_position");

如果返回-1,说明属性名写错了或者着色器没正确链接,这时候顶点数据根本传不到GPU,三角形自然不显示。

3. 确认VBO数据上传正确

检查你上传第二个VBO数据的代码,是不是绑定了正确的VBO再调用glBufferData:

// 上传第一个三角形数据
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);

// 上传第二个三角形数据
glBindBuffer(GL_ARRAY_BUFFER, VBO2);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

要是绑定错了VBO,两个三角形的顶点数据会写到同一个缓冲区里,结果就是两个三角形重叠成一个。

4. 修复Tab切换线框的逻辑

你的flag变量要加个状态锁,防止按住Tab时重复切换。优化后的输入处理函数:

void processInput(GLFWwindow *window) {
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    
    static bool tabWasPressed = false;
    if(glfwGetKey(window, GLFW_KEY_TAB) == GLFW_PRESS && !tabWasPressed) {
        tabWasPressed = true;
        flag = !flag;
        glPolygonMode(GL_FRONT_AND_BACK, flag ? GL_LINE : GL_FILL);
    }
    if(glfwGetKey(window, GLFW_KEY_TAB) == GLFW_RELEASE) {
        tabWasPressed = false;
    }
}

完整可运行的代码示例

我把这些修复点整合到你的代码框架里,补充了你没贴全的部分(比如片段着色器、资源清理等):

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

bool flag = false;
GLuint VBO1, VBO2;
GLuint shaderProgram1, shaderProgram2;
GLint posLoc1, posLoc2;

// 两个不重叠的三角形顶点数据
float vertices1[] = {
    -0.9f, -0.5f, 0.0f,
    -0.0f, -0.5f, 0.0f,
    -0.45f, 0.5f, 0.0f
};

float vertices2[] = {
    0.0f, -0.5f, 0.0f,
    0.9f, -0.5f, 0.0f,
    0.45f, 0.5f, 0.0f
};

// 红色片段着色器
const char* fragShaderRed = "#version 110\n"
                            "void main()\n"
                            "{\n"
                            "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                            "}\n";

// 蓝色片段着色器
const char* fragShaderBlue = "#version 110\n"
                             "void main()\n"
                             "{\n"
                             "    gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
                             "}\n";

// 编译单个着色器的工具函数
GLuint compileShader(GLenum type, const char* source) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);
    
    // 检查编译错误
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if(!success) {
        char infoLog[512];
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        std::cerr << "Shader compile failed:\n" << infoLog << std::endl;
    }
    return shader;
}

// 创建并链接着色器程序
GLuint createProgram(const char* vertSource, const char* fragSource) {
    GLuint vertShader = compileShader(GL_VERTEX_SHADER, vertSource);
    GLuint fragShader = compileShader(GL_FRAGMENT_SHADER, fragSource);
    
    GLuint program = glCreateProgram();
    glAttachShader(program, vertShader);
    glAttachShader(program, fragShader);
    glLinkProgram(program);
    
    // 检查链接错误
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if(!success) {
        char infoLog[512];
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cerr << "Program link failed:\n" << infoLog << std::endl;
    }
    
    glDeleteShader(vertShader);
    glDeleteShader(fragShader);
    return program;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow *window) {
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
    
    static bool tabPressed = false;
    if(glfwGetKey(window, GLFW_KEY_TAB) == GLFW_PRESS && !tabPressed) {
        tabPressed = true;
        flag = !flag;
        glPolygonMode(GL_FRONT_AND_BACK, flag ? GL_LINE : GL_FILL);
    }
    if(glfwGetKey(window, GLFW_KEY_TAB) == GLFW_RELEASE) {
        tabPressed = false;
    }
}

int main() {
    // 初始化GLFW
    if(!glfwInit()) {
        std::cerr << "GLFW init failed!" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
    
    GLFWwindow* window = glfwCreateWindow(800, 600, "Two Triangles", NULL, NULL);
    if(!window) {
        std::cerr << "Window create failed!" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    // 初始化GLEW
    if(glewInit() != GLEW_OK) {
        std::cerr << "GLEW init failed!" << std::endl;
        return -1;
    }
    
    // 创建两个VBO
    glGenBuffers(2, &VBO1);
    
    // 上传第一个三角形数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW);
    
    // 上传第二个三角形数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);
    
    // 顶点着色器(两个三角形共用)
    const char* vertShader = "#version 110 \n"
                             "attribute vec3 in_position; \n"
                             "void main() \n"
                             "{ \n"
                             "    gl_Position = vec4(in_position, 1.0); \n"
                             "} \n";
    
    // 创建两个着色器程序(不同片段着色器)
    shaderProgram1 = createProgram(vertShader, fragShaderRed);
    shaderProgram2 = createProgram(vertShader, fragShaderBlue);
    
    // 获取顶点属性位置
    posLoc1 = glGetAttribLocation(shaderProgram1, "in_position");
    posLoc2 = glGetAttribLocation(shaderProgram2, "in_position");
    
    // 渲染循环
    while(!glfwWindowShouldClose(window)) {
        processInput(window);
        
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // 绘制红色三角形
        glUseProgram(shaderProgram1);
        glBindBuffer(GL_ARRAY_BUFFER, VBO1);
        glEnableVertexAttribArray(posLoc1);
        glVertexAttribPointer(posLoc1, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glDisableVertexAttribArray(posLoc1);
        
        // 绘制蓝色三角形
        glUseProgram(shaderProgram2);
        glBindBuffer(GL_ARRAY_BUFFER, VBO2);
        glEnableVertexAttribArray(posLoc2);
        glVertexAttribPointer(posLoc2, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glDisableVertexAttribArray(posLoc2);
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    // 清理资源
    glDeleteBuffers(2, &VBO1);
    glDeleteProgram(shaderProgram1);
    glDeleteProgram(shaderProgram2);
    
    glfwTerminate();
    return 0;
}

最后再检查这几点

  • 确保两个三角形的顶点位置不重叠(重叠的话看起来就像一个)
  • 一定要保留着色器编译/链接的错误检查,不然出问题根本找不到原因
  • 绘制每个三角形时,严格遵循“激活着色器→绑定VBO→启用属性→绘制→禁用属性”的流程

这样应该就能同时显示两个不同颜色的三角形,按Tab也能正常切换线框/填充模式了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:59:26