OpenGL 着色器编译

一、基本了解

GLSL: OpenGL Shading language

GLSL着色器程序通常包含:

  1. 版本声明:#version 330 core
  2. 输入/输出变量
  3. 主函数:void main() { … }

二、数据类型

2.1、基本类型:

  1. float:浮点数
  2. int:整数
  3. bool:布尔值
  4. void:无返回值

2.2、容器类型:

  1. vecn:vec2, vec3, vec4:浮点向量(2/3/4分量)
  2. ivecn: ivec2, ivec3, ivec4:整数向量
  3. bvecn: bvec2, bvec3, bvec4:布尔向量

后面的n表示几个的意思,如ivecn,后面的n表示有n个正数类型

2.3、矩阵类型:

matn: mat2, mat3, mat4

分别表示:2x2, 3x3, 4x4矩阵

2.4、变量修饰符

  1. in:输入变量(顶点着色器从应用程序接收数据)
  2. out:输出变量(传递到下一个着色阶段)
  3. uniform:从应用程序传入的全局常量
  4. layout(location = X):指定变量布局和位置

2.5、内置变量

顶点着色器:
gl_Position:输出顶点位置
片段着色器:
gl_FragCoord:片段坐标

​ 输出通常自定义,如out vec4 FragColor

使用示例:

  • 顶点着色器
1
2
3
4
5
6
7
#version 330 core                   // GLSL版本声明
layout (location = 0) in vec3 aPos; // 接收顶点位置数据
out vec4 vertexColor;//传递给后边的片段着色器
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); // 设置顶点位置
vertexColor = vec4(0.5, 0.5, 0, 1);
}

layout(location = 0):显式指定顶点属性的位置索引,将着色器中的输入变量与OpenGL中的顶点属性绑定起来。一般来说,一个属性对应一个layout

OpenGL使用位置索引(location)来标识顶点属性。每个顶点属性(如位置、颜色、法线等)都需要一个唯一的索引值。

  1. X 是一个整数,表示顶点属性的位置索引
  2. 通过显式指定位置索引,可以避免OpenGL自动分配索引时的混淆,并确保应用程序和着色器之间的绑定一致。

aPos 是一个输入变量,表示顶点的三维位置。layout(location = 0) 将 aPos 绑定到位置索引 0。

我们在这里将location指定为0,那么在代码层面

1
2
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer 的第一个参数 0 对应 GLSL 中的 layout(location = 0)。表示顶点缓冲区中的数据将被传递到 GLSL 中的 aPos 变量。

多属性场景,前三个表示坐标,后三个表示颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main() {
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = aColor;
}

//片段着色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0f);
}

// 三角形顶点数据
m_vertices = {
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 顶点
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 右下
};

//一个属性对应一个layout, 注意步长和偏移,步长均为6,但颜色要偏移 3 * sizeof(float)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

由于glVertexAttribPointer和glEnableVertexAttribArray这样指定容易出现问题,所以可以通过 glBindAttribLocationglGetAttribLocation 手动查询和绑定位置索引。防止出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 查询着色器中的位置属性
m_shaderProgram.bind();
const GLint posLocation = m_shaderProgram.attributeLocation("aPos");
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(posLocation);

// 设置着色器中的位置属性
//注意,如果编译源码中已经设置了location,建议使用上面查询的方式,否则posLocation与源码不一致会导致无法绘制
const GLint posLocation = 0;
m_shaderProgram.bind();
m_shaderProgram.bindAttributeLocation("aPos", posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void *)0);
glEnableVertexAttribArray(posLocation);
  • 片段着色器
1
2
3
4
5
6
7
#version 330 core         // GLSL版本声明
out vec4 FragColor; // 定义输出颜色
in vec4 vertexColor;
void main() {
FragColor = vertexColor; // 颜色设置为顶点着色器传下来的值
//FragColor = vec4(vertexColor.xyz,1.0); xyz取vertexColor前三个值,xxy为前两个取第一个值,第三个取第二个值,类推
}

注意,传递参数时,需要确保类型和名字一致!!!,如顶点着色器传出的vertexColor要与片段着色器接收的vertexColor名字一致,都要叫做vertexColor,同时类型也需要一致,均为vec4

三、 常用函数

  1. 数学函数:sin, cos, pow, sqrt, mix, clamp
  2. 向量操作:点积.,叉积cross()
  3. 纹理采样:texture(sampler, texCoord)

四、uniform使用示例

通过定时器+uniform,将图形每隔1秒向右移动

  • 顶点着色器
1
2
3
4
5
6
7
8
9
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
uniform float offset;
void main() {
gl_Position = vec4(aPos.x + offset, aPos.y, aPos.z, 1.0);
ourColor = aColor;
}
  • 片段着色器
1
2
3
4
5
6
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0f);
}
  • moreAttributes.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include "MoreAttributes.h"
#include <QDebug>
// 构造函数
MoreAttributes::MoreAttributes(QWidget* parent)
: QOpenGLWidget(parent)
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, [&]()
{
makeCurrent();
// 每秒让图形向右偏移offset距离
static float offset = 0.0f;
offset += 0.01f;
// 让图形向右偏移
m_shaderProgram.bind();
m_shaderProgram.setUniformValue("offset", offset);
doneCurrent();
update();
});
m_timer->start(1000); // 1秒
}

// 析构函数:释放OpenGL对象
MoreAttributes::~MoreAttributes()
{
makeCurrent();
// 释放所有OpenGL资源
glDeleteVertexArrays(1, &m_VAO);
glDeleteBuffers(1, &m_VBO);
glDeleteBuffers(1, &m_EBO);
doneCurrent();
}

void MoreAttributes::initShader()
{
makeCurrent();
// 链接着色器程序
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
":/shaders/shape.vertex");
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
":/shaders/shape.fragment");
if (!m_shaderProgram.link())
{
qDebug() << "Shader program linking failed:" << m_shaderProgram.log();
return;
}
doneCurrent();
}

void MoreAttributes::initBufferObject()
{
makeCurrent();

glGenVertexArrays(1, &m_VAO);
glGenBuffers(1, &m_VBO);
glGenBuffers(1, &m_EBO);

doneCurrent();
}

void MoreAttributes::initializeGL()
{
// 初始化OpenGL函数
initializeOpenGLFunctions();
// 初始化着色器对象
initShader();
// 初始化对象
initBufferObject();
// 设置清屏颜色
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
}

void MoreAttributes::resizeGL(const int w, const int h)
{
glViewport(0, 0, w, h); // 设置视口大小
}

void MoreAttributes::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
m_shaderProgram.bind(); // 绑定着色器程序
glBindVertexArray(m_VAO);
glLineWidth(2.0f); // 设置线宽
glDrawElements(GL_LINES, m_indices.size(), GL_UNSIGNED_INT, NULL);
}

void MoreAttributes::drawTriangle()
{
makeCurrent();
// 三角形顶点数据
m_vertices = {
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 顶点
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 右下
};

// 三角形索引
m_indices = {0, 1, 1, 2, 2, 0};

// 更新缓冲区数据
glBindVertexArray(m_VAO);

// 更新顶点数据
glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(float),
m_vertices.constData(), GL_STATIC_DRAW);

// 更新索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int),
m_indices.constData(), GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

// 解绑VAO
glBindVertexArray(0);
// 完成绘制后刷新
doneCurrent();
update();
}

void MoreAttributes::drawQuads()
{
makeCurrent();
// 四边形顶点数据
m_vertices = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 左下
-0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, // 左上
};

// 四边形索引(使用两个三角形组成)
m_indices = {
0, 1, // 右上到右下
1, 2, // 右下到左下
2, 3, // 左下到左上
3, 0 // 左上到右上
};

// 更新缓冲区数据
glBindVertexArray(m_VAO);

// 更新顶点数据
glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(float),
m_vertices.constData(), GL_STATIC_DRAW);

// 更新索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int),
m_indices.constData(), GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 解绑VAO
glBindVertexArray(0);

doneCurrent();
// 完成绘制后刷新
update();
}

void MoreAttributes::onDrawTriangleClicked() { drawTriangle(); }

void MoreAttributes::onDrawQuadsClicked() { drawQuads(); }

  • moreAttributes.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#pragma once

#include "ui_MoreAttributes.h"
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>
#include <QTimer>
class MoreAttributes : public QOpenGLWidget,
protected QOpenGLFunctions_3_3_Core
{
Q_OBJECT

public:
explicit MoreAttributes(QWidget* parent = nullptr);
~MoreAttributes() override;

// 绘制三角形
void drawTriangle();
// 绘制四边形
void drawQuads();

public slots:
void onDrawTriangleClicked();
void onDrawQuadsClicked();

protected:
void initializeGL() override; // 初始化OpenGL
void resizeGL(int w, int h) override; // 窗口大小变化
void paintGL() override; // 绘制

private:
// 初始化VBO,VAO,EBO
void initBufferObject();
// 初始化着色器
void initShader();

private:
GLuint m_VAO, m_VBO, m_EBO; // OpenGL对象ID
QOpenGLShaderProgram m_shaderProgram; // 着色器程序
QVector<float> m_vertices = {}; // VBO顶点数据
QVector<unsigned int> m_indices = {}; // EBO索引数据
QTimer* m_timer = nullptr;

private:
Ui::MoreAttributesClass ui;
};

  • main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "MoreAttributes.h"
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>

int main(int argc, char *argv[]) {
QApplication a(argc, argv);

// 创建主窗口
QWidget mainWindow;
auto layout = new QVBoxLayout(&mainWindow);

// 创建 MoreAttributes 实例
auto glWidget = new MoreAttributes(&mainWindow);
layout->addWidget(glWidget);

// 创建按钮
auto triangleButton = new QPushButton("绘制三角形", &mainWindow);
auto quadsButton = new QPushButton("绘制四边形", &mainWindow);

// 设置按钮点击事件
QObject::connect(triangleButton, &QPushButton::clicked, glWidget,
&MoreAttributes::onDrawTriangleClicked);
QObject::connect(quadsButton, &QPushButton::clicked, glWidget,
&MoreAttributes::onDrawQuadsClicked);

// 添加按钮到布局
layout->addWidget(triangleButton);
layout->addWidget(quadsButton);

// 显示主窗口
mainWindow.setWindowTitle("OpenGL 测试");
mainWindow.resize(800, 600);
mainWindow.show();
return QApplication::exec();
}