【OpenGL】shader 着色器
参考资料: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.vert
和shader.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);
}