参考资料:https://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-2-the-first-triangle/

一、着色器

在openGL固定渲染管线的版本,不存在着色器这一概念,或者说,着色器是不可以编程的。在可编程管线中,着色器分为顶点着色器和片段着色器两个阶段,分别是用来对图元的顶点和片源进行操作。

1.1 编译着色器

  • 在最简配置下,您得有两个着色器:一个叫顶点着色器(vertex shader),它将作用于每个顶点上;另一个叫片段着色器(fragment shader),它将作用于每一个采样点。我们采用4倍抗锯齿,因此每个像素有四个采样点。

  • 着色器编程使用GLSL(GL Shading Language),属于OpenGL的一部分。与C、Java不同,GLSL必须在运行时编译,这意味着每次启动程序时,所有的着色器将重新编译。

  • 这两个着色器通常单独存放在文件里。本例中有shader.vertshader.frag两个着色器。扩展名无关紧要,也可以是.txt或者.glsl。

  • 以下是加载着色器的代码。没必要完全理解,因为在程序中这些操作一般只需执行一次,结合注释能看懂就够了

编译着色器的函数如下,主要功能就是编译顶点着色器和片段着色器,创建着色器程序,然后链接编译好的这两个着色器,最后返回生成好的着色器程序句柄。

void MyGLWidget::loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path) {  
    //创建顶点着色器  
    vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);  
  
    //读取shader.vert  
    std::string vertex_shader_code;  
    std::ifstream vertex_shader_stream(vertex_shader_path,std::ios::in);  
    if(vertex_shader_stream.is_open()){  
        std::stringstream ss;  
        ss << vertex_shader_stream.rdbuf();  
  
        vertex_shader_code = ss.str();  
        vertex_shader_stream.close();  
    }  
    else{  
        std::cout << "current path = " << std::filesystem::current_path() << std::endl;  
        if (!std::filesystem::exists(vertex_shader_path)) {  
            std::cerr << "File not found: " << vertex_shader_path << std::endl;  
        }  
  
        std::cerr << "read vertex_shader fail!" << std::endl;  
        return;  
    }  
  
    //加载vertex着色器程序代码  
    const char* vertex_shader_code_pointer = vertex_shader_code.c_str();  
    glShaderSource(vertex_shader_id,1,&vertex_shader_code_pointer,nullptr);  
  
    //编译vertex shader  
    glCompileShader(vertex_shader_id);  
  
    //查看vertex编译结果  
    GLint Result = GL_FALSE;  
    int infoLogLength;  
    glGetShaderiv(vertex_shader_id,GL_COMPILE_STATUS,&Result);  
    glGetShaderiv(vertex_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char> vertex_shader_error_message(infoLogLength+1);  
        glGetShaderInfoLog(vertex_shader_id, infoLogLength, nullptr, &vertex_shader_error_message[0]);  
        std::cout << "vertex shader:" << &vertex_shader_error_message[0] << std::endl;  
    }  
  
    //创建片段着色器  
    fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);  
  
    //读取shader.frag  
    std::string fragment_shader_code;  
    std::ifstream fragment_shader_stream(fragment_shader_path,std::ios::in);  
    if(fragment_shader_stream.is_open()){  
        std::stringstream ss;  
        ss << fragment_shader_stream.rdbuf();  
  
        fragment_shader_code = ss.str();  
        fragment_shader_stream.close();  
    }  
    else{  
        std::cout << "current path = " << std::filesystem::current_path() << std::endl;  
        if (!std::filesystem::exists(vertex_shader_path)) {  
            std::cerr << "File not found: " << vertex_shader_path << std::endl;  
        }  
  
        std::cerr << "read fragment_shader fail!" << std::endl;  
    }  
  
    //加载fragment着色器程序代码  
    const char* fragment_shader_code_pointer = fragment_shader_code.c_str();  
    glShaderSource(fragment_shader_id,1,&fragment_shader_code_pointer,nullptr);  
  
    //编译fragment shader  
    glCompileShader(fragment_shader_id);  
  
    //查看fragment编译结果  
    glGetShaderiv(fragment_shader_id,GL_COMPILE_STATUS,&Result);  
    glGetShaderiv(fragment_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char> fragment_shader_error_message(infoLogLength+1);  
        glGetShaderInfoLog(fragment_shader_id, infoLogLength, nullptr, &fragment_shader_error_message[0]);  
        std::cout << "fragment shader compile error:" << &fragment_shader_error_message[0] << std::endl;  
    }  
  
    //创建着色器程序  
    shader_program_id = glCreateProgram();  
  
    //附加着色器到程序  
    glAttachShader(shader_program_id,vertex_shader_id);  
    glAttachShader(shader_program_id,fragment_shader_id);  
  
    //链接shader  
    glLinkProgram(shader_program_id);  
  
    //检查程序链接结果  
    glGetProgramiv(shader_program_id,GL_LINK_STATUS,&Result);  
    glGetProgramiv(shader_program_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char>program_error_message(infoLogLength + 1);  
        glGetProgramInfoLog(shader_program_id,infoLogLength,nullptr,&program_error_message[0]);  
        std::cout << "program link:" << &program_error_message[0] << std::endl;  
    }  
  
  
    //删除着色器编译结果  
    glDeleteShader(vertex_shader_id);  
    glDeleteShader(fragment_shader_id);  
}

链接好之后,之前编译的结果就可以删除了,不再需要了

//删除着色器编译结果  
glDeleteShader(vertex_shader_id);  
glDeleteShader(fragment_shader_id);  

1.2 顶点着色器

先写顶点着色器。 第一行告诉编译器我们将用OpenGL 3语法。

#version 330 core

第二行声明输入数据:

layout(location = 0) in vec3 vertexPosition_modelspace;

下面详细解释这一行:

  • 在GLSL中“vec3”代表一个三维向量。类似但不等同于之前声明三角形的glm::vec3。最重要的是,如果我们在C++中使用三维向量,那么在GLSL中也要相应地使用三维向量。
  • layout(location = 0)“指向存储vertexPosition_modelspace属性(attribute)的缓冲。每个顶点有多种属性:位置,一种或多种颜色,一个或多个纹理坐标等等。OpenGL并不清楚什么是颜色,它只能识别vec3这样的数据类型。因此我们必须将glvertexAttribPointer函数的第一个参数值赋给layout,以此告知OpenGL每个缓冲对应的是哪种属性数据。第二个参数“0”并不重要,也可以换成12(但是不能超过glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v)),关键是C++和GLSL两边数值必须保持一致。
  • vertexPosition_modelspace”这个变量名可以任取,其中保存的是顶点位置,顶点着色器每次运行时都会用到。
  • in”表明是这是输入数据。不久我们将会看到“out”关键字。

每个顶点都会调用main函数(和C语言一样):

void main(){

这里的main函数只是简单地将缓冲里的值作为顶点位置。因此如果位置是(1,1),那么三角形有一个顶点位于屏幕的右上角。 在下一课中我们将看到怎样对输入位置做一些更有趣的计算。

gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
}

gl_Position是仅有的几个内置变量之一:您必须对其赋值。其他操作都是可选的,我们将在第四课中看到究竟有哪些“其他操作”。

1.3 片段着色器

这就是我们的第一个片段着色器,它仅仅简单将每个片段的颜色设为红色。(记住,我们采用了4倍抗锯齿,因此每个像素有4个片段)

#version 330 core
out vec3 color;

void main(){
color = vec3(1,0,0);
}

vec3(1,0,0)代表红色。因为在计算机屏幕上,颜色由红、绿、蓝三元组表示。因此(1,0,0)代表纯红色,无绿、蓝分量。

二、使用着色器

首先,在initializeGL函数中,加载我们的着色器程序,并且保存返回的句柄

//加载着色器  
loadShader("/Users/liuhang/CLionProjects/opengl-learning/opengl1-2-shader/shader/shader.vert",  
           "/Users/liuhang/CLionProjects/opengl-learning/opengl1-2-shader/shader/shader.frag");

然后,在paintGL中使用保存的着色器程序

glUseProgram(shader_program_id);

其余代码和前面章节保持一致,运行效果如下:

在这里插入图片描述

二、完整代码

my_glwidget.h

//  
// Created by liuhang on 2025/9/16.  
//  
  
#ifndef OPENGL_LEARNING_MY_GLWIDGET_H  
#define OPENGL_LEARNING_MY_GLWIDGET_H  
  
#include<QOpenGLWidget>  
#include<QOpenGLFunctions>  
  
class MyGLWidget : public QOpenGLWidget,protected QOpenGLFunctions  
{  
public:  
    explicit MyGLWidget(QWidget* parent = nullptr);  
    ~MyGLWidget() override;  
  
  
protected:  
    void initializeGL() override;  
    void paintGL() override;  
    void resizeGL(int w,int h) override;  
  
private:  
    void loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path);  
  
private:  
    GLuint vertex_array_id;  
    GLuint vertex_buffer_id;  
  
    GLuint vertex_shader_id;  
    GLuint fragment_shader_id;  
  
    GLuint shader_program_id;  
};  
  
  
#endif //OPENGL_LEARNING_MY_GLWIDGET_H

my_glwidget.cpp

//  
// Created by liuhang on 2025/9/16.  
//  
  
#include "my_glwidget.h"  
#include<string>  
#include<fstream>  
#include<sstream>  
#include<iostream>  
#include<filesystem>  
  
static const float vertex_buffer_data[] ={  
        -1.0f, -1.0f, 0.0f,  
        1.0f, -1.0f, 0.0f,  
        0.0f,  1.0f, 0.0f  
};  
  
  
  
MyGLWidget::MyGLWidget(QWidget *parent): QOpenGLWidget(parent)  
{  
  
}  
  
MyGLWidget::~MyGLWidget() {  
    makeCurrent();  
  
    glDeleteVertexArrays(1,&vertex_array_id);  
    glDeleteBuffers(1,&vertex_buffer_id);  
  
    doneCurrent();  
}  
  
void MyGLWidget::initializeGL() {  
    initializeOpenGLFunctions();  
  
    glClearColor(0.2f,0.3f,0.3f,1.0f);  
  
    glGenVertexArrays(1,&vertex_array_id);  
    glBindVertexArray(vertex_array_id);  
  
    glGenBuffers(1,&vertex_buffer_id);  
    glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer_id);  
    glBufferData(GL_ARRAY_BUFFER,sizeof(vertex_buffer_data),vertex_buffer_data,GL_STATIC_DRAW);  
  
    //启用layout = 0为当前绑定的VAO  
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);  
  
    glEnableVertexAttribArray(0);  
  
    //加载着色器  
    loadShader("/Users/liuhang/CLionProjects/opengl-learning/opengl1-2-shader/shader/shader.vert",  
               "/Users/liuhang/CLionProjects/opengl-learning/opengl1-2-shader/shader/shader.frag");  
  
    //解绑VAO  
    glBindVertexArray(0);  
}  
  
void MyGLWidget::paintGL() {  
    glClear(GL_COLOR_BUFFER_BIT);  
    glBindVertexArray(vertex_array_id);  
  
    //使用着色器程序  
    glUseProgram(shader_program_id);  
    glDrawArrays(GL_TRIANGLES,0,3);  
}  
  
void MyGLWidget::resizeGL(int w, int h) {  
    glViewport(0, 0, w, h);  
}  
  
void MyGLWidget::loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path) {  
    //创建顶点着色器  
    vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);  
  
    //读取shader.vert  
    std::string vertex_shader_code;  
    std::ifstream vertex_shader_stream(vertex_shader_path,std::ios::in);  
    if(vertex_shader_stream.is_open()){  
        std::stringstream ss;  
        ss << vertex_shader_stream.rdbuf();  
  
        vertex_shader_code = ss.str();  
        vertex_shader_stream.close();  
    }  
    else{  
        std::cout << "current path = " << std::filesystem::current_path() << std::endl;  
        if (!std::filesystem::exists(vertex_shader_path)) {  
            std::cerr << "File not found: " << vertex_shader_path << std::endl;  
        }  
  
        std::cerr << "read vertex_shader fail!" << std::endl;  
        return;  
    }  
  
    //加载vertex着色器程序代码  
    const char* vertex_shader_code_pointer = vertex_shader_code.c_str();  
    glShaderSource(vertex_shader_id,1,&vertex_shader_code_pointer,nullptr);  
  
    //编译vertex shader  
    glCompileShader(vertex_shader_id);  
  
    //查看vertex编译结果  
    GLint Result = GL_FALSE;  
    int infoLogLength;  
    glGetShaderiv(vertex_shader_id,GL_COMPILE_STATUS,&Result);  
    glGetShaderiv(vertex_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char> vertex_shader_error_message(infoLogLength+1);  
        glGetShaderInfoLog(vertex_shader_id, infoLogLength, nullptr, &vertex_shader_error_message[0]);  
        std::cout << "vertex shader:" << &vertex_shader_error_message[0] << std::endl;  
    }  
  
    //创建片段着色器  
    fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);  
  
    //读取shader.frag  
    std::string fragment_shader_code;  
    std::ifstream fragment_shader_stream(fragment_shader_path,std::ios::in);  
    if(fragment_shader_stream.is_open()){  
        std::stringstream ss;  
        ss << fragment_shader_stream.rdbuf();  
  
        fragment_shader_code = ss.str();  
        fragment_shader_stream.close();  
    }  
    else{  
        std::cout << "current path = " << std::filesystem::current_path() << std::endl;  
        if (!std::filesystem::exists(vertex_shader_path)) {  
            std::cerr << "File not found: " << vertex_shader_path << std::endl;  
        }  
  
        std::cerr << "read fragment_shader fail!" << std::endl;  
    }  
  
    //加载fragment着色器程序代码  
    const char* fragment_shader_code_pointer = fragment_shader_code.c_str();  
    glShaderSource(fragment_shader_id,1,&fragment_shader_code_pointer,nullptr);  
  
    //编译fragment shader  
    glCompileShader(fragment_shader_id);  
  
    //查看fragment编译结果  
    glGetShaderiv(fragment_shader_id,GL_COMPILE_STATUS,&Result);  
    glGetShaderiv(fragment_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char> fragment_shader_error_message(infoLogLength+1);  
        glGetShaderInfoLog(fragment_shader_id, infoLogLength, nullptr, &fragment_shader_error_message[0]);  
        std::cout << "fragment shader compile error:" << &fragment_shader_error_message[0] << std::endl;  
    }  
  
    //创建着色器程序  
    shader_program_id = glCreateProgram();  
  
    //附加着色器到程序  
    glAttachShader(shader_program_id,vertex_shader_id);  
    glAttachShader(shader_program_id,fragment_shader_id);  
  
    //链接shader  
    glLinkProgram(shader_program_id);  
  
    //检查程序链接结果  
    glGetProgramiv(shader_program_id,GL_LINK_STATUS,&Result);  
    glGetProgramiv(shader_program_id,GL_INFO_LOG_LENGTH,&infoLogLength);  
    if(infoLogLength > 0){  
        std::vector<char>program_error_message(infoLogLength + 1);  
        glGetProgramInfoLog(shader_program_id,infoLogLength,nullptr,&program_error_message[0]);  
        std::cout << "program link:" << &program_error_message[0] << std::endl;  
    }  
  
  
    //删除着色器编译结果  
    glDeleteShader(vertex_shader_id);  
    glDeleteShader(fragment_shader_id);  
}

shader.vert

#version 330 core  
  
layout(location = 0) in vec3 vertexPosion_modelspace;  
  
void main(){  
    gl_Position = vec4(vertexPosion_modelspace,1.0);  
}

shader.frag

#version 330 core  
  
out vec3 fragment_color;  
  
void main(){  
    fragment_color = vec3(1.0,0.0,0.0);  
}
Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐