文章目录
前言:
在OpenGL开发中,着色器(Shader)是用于控制图形渲染管线各个阶段的小程序。它们是用GLSL(OpenGL Shading Language)编写的,GLSL是一种类似于C的语言。着色器在GPU上执行,负责处理顶点、片段等数据,最终生成图像。
一、着色器
1、什么是着色器?
概念:
着色器(Shader)是运行在 GPU 上的小程序,用于处理图形渲染过程中的特定任务。传统的 OpenGL 渲染流程中,CPU 需要承担大量的图形计算任务,而引入着色器后,将这些计算任务转移到 GPU 上,利用 GPU 的并行计算能力,大大提高了渲染效率。
工作原理:
当 OpenGL 进行图形渲染时,会将顶点数据(如顶点坐标、颜色、纹理坐标等)传递给着色器进行处理。着色器根据预设的算法对这些数据进行计算和转换,最终生成像素的颜色值,用于显示在屏幕上。整个渲染过程可以分为多个阶段,每个阶段由不同类型的着色器负责处理。
2、着色器类型
2.1、顶点着色器(Vertex Shader)
-
功能:顶点着色器是处理顶点数据的第一个阶段。它接收顶点的原始数据(如顶点坐标、法线、颜色等),并对这些数据进行变换和处理,例如将顶点坐标从模型空间转换到裁剪空间。
-
示例代码:
cpp#version 330 core layout (location = 0) in vec3 aPos; // 输入的顶点位置 layout (location = 1) in vec3 aColor; // 输入的顶点颜色 out vec3 ourColor; // 输出的颜色,传递给片段着色器 void main() { gl_Position = vec4(aPos, 1.0); // 将顶点位置转换为齐次坐标 ourColor = aColor; // 传递颜色数据 }
2.2、片段着色器(Fragment Shader)
-
功能:片段着色器负责计算每个像素的最终颜色。它接收顶点着色器传递过来的数据(如颜色、纹理坐标等),并根据这些数据计算出每个像素的颜色值。
-
示例代码 :
cpp#version 330 core in vec3 ourColor; // 从顶点着色器接收的颜色数据 out vec4 FragColor; // 输出的最终像素颜色 void main() { FragColor = vec4(ourColor, 1.0); // 将颜色数据转换为 RGBA 格式 }
3、着色器属性
在基于 Qt 进行 OpenGL 开发时,理解 OpenGL 着色器语言(GLSL)中的
layout
、in
、out
等属性是非常重要的,这些属性用于定义着色器之间的数据传递和属性的存储位置。下面详细介绍这些属性。
3.1、layout
属性
-
功能:
layout
属性主要用于指定着色器输入输出变量的存储布局,比如在顶点着色器中指定顶点属性的位置,或者在片段着色器中指定颜色输出的位置等。通过layout
属性,我们可以精确地控制数据在内存中的存储和访问方式,方便 OpenGL 正确地读取和处理数据。 -
示例及解释:
glsl#version 330 core // 顶点着色器中,使用 layout 指定顶点位置属性的位置为 0 layout (location = 0) in vec3 aPos; // 使用 layout 指定顶点颜色属性的位置为 1 layout (location = 1) in vec3 aColor; out vec3 ourColor; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; }
在上述顶点着色器代码中,
layout (location = 0)
表明aPos
这个顶点位置属性在顶点数据中的索引为 0,layout (location = 1)
则表示aColor
顶点颜色属性的索引为 1。在 C++ 代码中,我们可以根据这些索引来设置顶点属性指针,示例如下:cpp// 设置顶点位置属性指针 glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0); // 设置顶点颜色属性指针 glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
3.2、in
属性
-
功能:
in
属性用于声明着色器的输入变量。在不同类型的着色器中,in
变量的来源不同。例如,在顶点着色器中,in
变量通常是从 CPU 传递过来的顶点属性数据;在片段着色器中,in
变量一般是从顶点着色器经过光栅化插值后传递过来的数据。 -
示例及解释:
glsl#version 330 core // 顶点着色器 layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; out vec3 ourColor; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; } // 片段着色器 in vec3 ourColor; out vec4 FragColor; void main() { FragColor = vec4(ourColor, 1.0); }
在顶点着色器中,
aPos
和aColor
是从 CPU 传递过来的顶点属性,使用in
关键字声明。而ourColor
是输出变量,会传递给片段着色器。在片段着色器中,ourColor
作为输入变量接收来自顶点着色器的数据,然后将其用于计算最终的像素颜色。
3.3、out
属性
-
功能:
out
属性用于声明着色器的输出变量。这些变量会传递给下一个阶段的着色器进行处理。例如,顶点着色器的输出变量会经过光栅化插值后传递给片段着色器。 -
示例及解释:
glsl#version 330 core // 顶点着色器 layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; // 声明输出变量,传递给片段着色器 out vec3 ourColor; void main() { gl_Position = vec4(aPos, 1.0); ourColor = aColor; } // 片段着色器 // 接收来自顶点着色器的输出变量 in vec3 ourColor; // 声明片段着色器的输出变量,即最终的像素颜色 out vec4 FragColor; void main() { FragColor = vec4(ourColor, 1.0); }
在顶点着色器中,
ourColor
使用out
关键字声明,它会将顶点的颜色信息传递给片段着色器。在片段着色器中,FragColor
使用out
关键字声明,它表示最终的像素颜色,会被写入帧缓冲区。
3.4、总结
layout
属性用于指定变量的存储位置,方便 OpenGL 正确解析数据。in
属性用于声明着色器的输入变量,接收来自上一个阶段的数据。out
属性用于声明着色器的输出变量,将数据传递给下一个阶段的着色器。
通过合理使用这些属性,我们可以实现不同着色器之间的数据传递和交互,从而完成复杂的图形渲染任务。
4、示例
创建一个顶点着色器与片段着色器,并使用
QOpenGLShaderProgram
类对象应用着色器,如下:
- 顶点着色器
cpp
// vertex_shader.glsl
#version 330 core
layout (location = 0) in vec3 aPos; // 顶点位置
layout (location = 1) in vec3 aColor; // 顶点颜色
out vec3 ourColor; // 传递给片段着色器的颜色
void main()
{
gl_Position = vec4(aPos, 1.0); // 设置顶点位置
ourColor = aColor; // 传递颜色
}
- 片段着色器
cpp
#version 330 core
in vec3 ourColor; // 从顶点着色器传入的颜色
out vec4 FragColor; // 输出颜色
void main()
{
FragColor = vec4(ourColor, 1.0); // 使用传入的颜色
}
- 示例代码
cpp
#include <QOpenGLFunctions_3_3_Core>
#include "OpenGLWidget.h"
// 顶点数据结构
struct Vertex {
float position[3]; // 位置
float color[3]; // 颜色
};
// 顶点数据
Vertex vertices[] = {
{ { -0.5f, -0.5f, 0.0f },{ 1.0, 0.0, 0.0 } },
{ { 0.5f, -0.5f, 0.0f },{ 0.0, 1.0, 0.0 } },
{ { 0.0f, 0.5f, 0.0f },{ 0.0, 0.0, 1.0 } }
};
// 构造函数
OpenGLWidget::OpenGLWidget(QWidget* parent) : QOpenGLWidget(parent)
{
}
// 析构函数
OpenGLWidget::~OpenGLWidget()
{
}
void OpenGLWidget::initializeGL()
{
// 初始化OpenGL相关的接口
initializeOpenGLFunctions();
// 顶点数据:坐标 + 颜色
GLfloat 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.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部 (蓝色)
};
// 创建并绑定VAO
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
// 创建并绑定m_vbo
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点属性指针
// 位置属性 (location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性 (location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// 解绑VAO
glBindVertexArray(0);
// 编译着色器
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/OpenGL_Demo_Shader/vertex_shader.glsl");
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/OpenGL_Demo_Shader/fragment_shader.glsl");
m_shaderProgram.link();
}
void OpenGLWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h); // 设置视口大小
}
void OpenGLWidget::paintGL()
{
// 清除颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置背景颜色
glClear(GL_COLOR_BUFFER_BIT);
// 使用着色器程序
m_shaderProgram.bind();
// 绑定VAO
glBindVertexArray(m_vao);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
// 解绑VAO
glBindVertexArray(0);
}
- 运行结果
