【OpenGL】shader 着色器
OpenGL着色器编程入门摘要:本文介绍了OpenGL可编程管线中的着色器基础。现代OpenGL需要两个基本着色器:顶点着色器(处理每个顶点)和片段着色器(处理每个采样点)。着色器使用GLSL语言编写,运行时动态编译。文章详细展示了如何从文件加载着色器代码(.vert和.frag文件)、编译着色器、创建着色器程序并链接的过程。顶点着色器示例演示了GLSL 3.3核心语法,通过layout声明输入顶
参考资料: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);
}

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