文章目录
- 一、概述
- 二、Shader 代码文件的基本格式
- 三、Shader的向量语法介绍
- 四、Shader之间的数据传输
- 五、Shader与C++的数据传输uniform
- 六、完整示例
一、概述
在 OpenGL 中,Shader(着色器)使用 GLSL(OpenGL Shading Language) 编写,GLSL 是一种类似 C 语言的着色器编程语言。着色器的基本语法包括 版本声明、变量声明、主函数 等。
二、Shader 代码文件的基本格式
每个 Shader 代码文件的基本格式如下:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
void main()
{
out_variable_name = weird_stuff_we_processed;
}
1. 版本声明
#version version_number
作用:指定 GLSL 版本号。
示例:
- #version 330 core (OpenGL 3.3)
- #version 450 core (OpenGL 4.5)
不同的 OpenGL 版本支持不同的 GLSL 语法特性,使用适当的版本号可以确保着色器代码正确编译。
2. 输入变量(in)
in type in_variable_name;
in type in_variable_name;
作用:定义输入变量,接收数据。
适用范围:
- 顶点着色器(Vertex Shader):接收 CPU 传递的顶点数据(如位置、颜色、法线等)。
- 片段着色器(Fragment Shader):接收 来自顶点着色器的插值数据。
示例(顶点着色器输入):
in vec3 aPos; // 顶点坐标
in vec3 aColor; // 顶点颜色
示例(片段着色器输入):
in vec3 vertexColor; // 顶点着色器传递的颜色
3. 输出变量(out)
out type out_variable_name;
作用:
- 顶点着色器(Vertex Shader):将数据传递到片段着色器。
- 片段着色器(Fragment Shader):输出最终的像素颜色。
示例(顶点着色器的输出):
out vec3 vertexColor; // 传递颜色到片段着色器
示例(片段着色器的输出):
out vec4 FragColor; // 最终的片段颜色
4. Uniform(全局变量)
uniform type uniform_name;
作用:
- uniform 变量是全局变量,在 CPU 侧由 OpenGL 代码设置,可用于变换矩阵、光照参数、时间变量等。
- uniform 变量的值在 所有顶点/片段之间保持一致,不会在不同的顶点或片段间变化。
示例(用于变换矩阵):
uniform mat4 model; // 模型变换矩阵
uniform mat4 view; // 视图变换矩阵
uniform mat4 projection; // 投影变换矩阵
示例(用于纹理):
uniform sampler2D texture1; // 纹理
5. main() 主函数
void main()
{
out_variable_name = weird_stuff_we_processed;
}
作用:
- main() 函数是 GLSL 的入口点,每个 顶点(顶点着色器)或 像素(片段着色器)都会执行一次。
- out_variable_name = weird_stuff_we_processed; 表示计算并输出结果。
完整示例
(1)顶点着色器
#version 330 core
layout(location = 0) in vec3 aPos; // 顶点坐标
layout(location = 1) in vec3 aColor; // 顶点颜色
out vec3 vertexColor; // 传递给片段着色器的颜色
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vertexColor = aColor; // 传递颜色
}
解析:
- in 变量 aPos:接收 CPU 传来的顶点坐标数据。
- in 变量 aColor:接收 CPU 传来的顶点颜色数据。
- out 变量 vertexColor:将颜色传递到片段着色器。
- uniform 变量 model, view, projection:用于变换矩阵,调整顶点位置。
(2) 片段着色器
#version 330 core
in vec3 vertexColor; // 从顶点着色器接收的颜色
out vec4 FragColor; // 最终颜色输出
void main()
{
FragColor = vec4(vertexColor, 1.0);
}
解析:
- in 变量 vertexColor:接收 顶点着色器传递的颜色。
- out 变量 FragColor:输出最终的片段颜色。
总结:
三、Shader的向量语法介绍
在 GLSL(OpenGL Shading Language)中,向量(Vector)是图形渲染中最常用的数据类型之一,主要用于存储坐标、颜色、法线、纹理坐标等信息。GLSL 提供了vec2、vec3、vec4 这三种向量类型,以及多种操作方式。
1. 向量类型
GLSL 主要支持以下浮点型向量:
此外,还有整型和布尔型向量:
2. 向量初始化
GLSL 支持多种方式初始化向量:
(1) 直接赋值
vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(0.5, 0.2, 0.7);
vec4 c = vec4(1.0, 0.0, 0.5, 1.0);
(2) 复制构造
vec3 v1 = vec3(0.5); // v1 = (0.5, 0.5, 0.5)
vec4 v2 = vec4(v1, 1.0); // v2 = (0.5, 0.5, 0.5, 1.0)
(3) 通过其它向量构造
vec2 v = vec2(0.3, 0.6);
vec4 newVec = vec4(v, 1.0, 0.0); // newVec = (0.3, 0.6, 1.0, 0.0)
3. 向量分量访问(Swizzling)
GLSL 允许使用 .x, .y, .z, .w 访问或重新排列向量的分量,类似于 struct 的成员访问。
(1) 访问单个分量
vec4 color = vec4(0.2, 0.5, 0.7, 1.0);
float red = color.x; // red = 0.2
float alpha = color.w; // alpha = 1.0
(2) 访问多个分量(Swizzling)
vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
vec2 v2 = pos.xy; // v2 = (1.0, 2.0)
vec3 v3 = pos.zyx; // v3 = (3.0, 2.0, 1.0)
vec4 v4 = pos.xxyy; // v4 = (1.0, 1.0, 2.0, 2.0)
(3) 组合不同分量
vec3 color = vec3(1.0, 0.5, 0.3);
vec4 newColor = vec4(color.yxz, 1.0); // newColor = (0.5, 1.0, 0.3, 1.0)
4. 向量运算
GLSL 允许对向量进行各种运算,如加法、减法、乘法、除法、点积、叉积等。
(1) 逐元素加法、减法
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.5, 0.2, 0.7);
vec3 sum = a + b; // (1.5, 2.2, 3.7)
vec3 diff = a - b; // (0.5, 1.8, 2.3)
(2) 逐元素乘法、除法
vec3 c = vec3(2.0, 3.0, 4.0);
vec3 d = c * 2.0; // (4.0, 6.0, 8.0) - 每个分量都乘以 2
vec3 e = c / 2.0; // (1.0, 1.5, 2.0) - 每个分量都除以 2
(3) 点积(Dot Product)
float dotProduct = dot(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0));
// dotProduct = (1*4 + 2*5 + 3*6) = 32
作用:计算两个向量的相似程度,常用于光照计算。
(4) 叉积(Cross Product)
vec3 crossProduct = cross(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0));
// crossProduct = (0.0, 0.0, 1.0)
作用:计算垂直于两个向量的法向量,用于法线计算。
5. 常用数学函数
GLSL 提供了一些常见的数学函数来处理向量。
(1) 向量长度
float len = length(vec3(3.0, 4.0, 0.0)); // len = 5.0
(2) 归一化(Normalization)
vec3 normVec = normalize(vec3(3.0, 4.0, 0.0));
// normVec = (0.6, 0.8, 0.0)
作用:将向量变为单位向量,方向不变,但长度为 1。
(3) 线性插值(Lerp)
vec3 lerpVec = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), 0.5);
// lerpVec = (0.5, 0.0, 0.5)
作用:在两个向量之间进行插值,mix(a, b, t) 计算 a * (1-t) + b * t。
总结:
四、Shader之间的数据传输
1. 顶点着色器 (Vertex Shader)
#version 330 core
layout (location = 0) in vec3 aPos; // 顶点位置,attribute 变量
out vec4 vertexColor; // 输出到片段着色器的颜色数据
void main()
{
gl_Position = vec4(aPos, 1.0); // 将 vec3 转换为 vec4,赋值给 OpenGL 的 gl_Position
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 设置颜色,深红色
}
输入 (Input):
- layout (location = 0) in vec3 aPos;
- aPos 是从 OpenGL 的 VBO (Vertex Buffer Object) 传入的顶点位置数据。
- layout(location = 0) 绑定顶点数据到 attribute 位置 0。
处理 (Processing)
- gl_Position = vec4(aPos, 1.0);
- gl_Position 是固定的 OpenGL 变量,用于存储变换后的顶点坐标,最终传递给光栅化阶段。
- aPos 是 vec3,而 gl_Position 需要 vec4,所以用 vec4(aPos, 1.0) 进行转换。
输出 (Output)
- out vec4 vertexColor;
- vertexColor 是一个 out 变量,它存储颜色信息,并传递给片段着色器。
- vertexColor = vec4(0.5, 0.0, 0.0, 1.0);比如颜色值 (0.5, 0.0, 0.0, 1.0),代表深红色 (Dark Red)。
- 这个颜色会插值传递给片段着色器。
2. 片段着色器 (Fragment Shader)
#version 330 core
out vec4 FragColor; // 输出最终颜色
in vec4 vertexColor; // 从顶点着色器传入的颜色数据
void main()
{
FragColor = vertexColor; // 直接使用插值后的 vertexColor
}
输入 (Input)
- in vec4 vertexColor;
- 这个变量与顶点着色器中的 out vec4 vertexColor对应,用于接收插值后的颜色数据。
处理 (Processing)
这里没有额外的计算,直接使用 vertexColor。
输出 (Output)
- out vec4 FragColor;
- FragColor 是最终的片段颜色,输出到屏幕。
3. 顶点着色器与片段着色器的连接
顶点着色器的 out vec4 vertexColor变量自动插值后传递给片段着色器的 in vec4 vertexColor;
插值 (Interpolation) 机制:
- 顶点着色器的 vertexColor 值会在片元之间进行线性插值。
- 如果顶点着色器的颜色是 (0.5, 0.0, 0.0, 1.0),则片段着色器接收到的颜色会根据顶点插值。
4. 传输流程总结
VBO (aPos) → 顶点着色器 (gl_Position, vertexColor) → 片段着色器 (vertexColor → FragColor) → 颜色缓冲区
五、Shader与C++的数据传输uniform
在 OpenGL 中,uniform 变量用于在 C++ 代码和 Shader 之间传递数据。与 in/out 不同,uniform 是全局变量,在着色器的所有 invocations(调用)中都保持相同的值。
1. uniform 在 GLSL (Shader) 中的使用
在 GLSL 着色器中,我们可以声明 uniform 变量,例如:
#version 330 core
uniform mat4 model; // 4x4 模型矩阵
uniform mat4 view; // 4x4 视图矩阵
uniform mat4 projection; // 4x4 投影矩阵
in vec3 aPos; // 顶点位置
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
解析:
- uniform mat4 model; → 模型变换矩阵(物体自身的变换)
- uniform mat4 view; → 视图变换矩阵(摄像机的变换)
- uniform mat4 projection; → 投影变换矩阵(投影方式)
- gl_Position = projection * view * model * vec4(aPos, 1.0);最终计算出的顶点位置,经过 3D 变换后传入 OpenGL 渲染管线。
2. 在 C++ 中传递 uniform 数据
在 OpenGL 的 C++ 代码中,我们需要:
- 获取 uniform 变量的 location
- 传递数据给 uniform
步骤 1:获取 uniform 变量的位置
在 C++ 中,我们可以使用 glGetUniformLocation 获取着色器中 uniform 变量的位置:
GLuint shaderProgram = ...; // 已编译和链接的 Shader 程序
GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
GLint viewLoc = glGetUniformLocation(shaderProgram, "view");
GLint projectionLoc = glGetUniformLocation(shaderProgram, "projection");
例如:glGetUniformLocation(shaderProgram, “model”);获取 model 变量在 Shader 中的地址,如果 model 变量不存在或者优化掉了,会返回 -1。
步骤 2:传递数据给 uniform
(1)传递 4x4 矩阵
glm::mat4 model = glm::mat4(1.0f); // 单位矩阵
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
// 传递矩阵数据
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
解析:
glUniformMatrix4fv(location, count, transpose, value)
- location → glGetUniformLocation() 获取的 uniform 变量地址
- count → 传递的矩阵个数(一般是 1)
- transpose → 是否需要转置(一般为 GL_FALSE,OpenGL 期望列优先存储)
- value → 指向矩阵数据的指针(glm::value_ptr(matrix))
(2)传递 float 类型的 uniform
Shader 代码:
uniform float time; // 时间变量
C++ 代码:
GLint timeLoc = glGetUniformLocation(shaderProgram, "time");
float currentTime = glfwGetTime();
glUniform1f(timeLoc, currentTime);
(3)传递 vec3(三维向量)
Shader 代码:
uniform vec3 lightColor;
C++ 代码:
GLint lightColorLoc = glGetUniformLocation(shaderProgram, "lightColor");
glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 传递白色光
或者使用 glm::vec3:
glm::vec3 lightColor = glm::vec3(1.0f, 1.0f, 1.0f);
glUniform3fv(lightColorLoc, 1, glm::value_ptr(lightColor));
(4)传递 bool 和 int
Shader 代码:
uniform bool useTexture;
uniform int lightMode;
C++ 代码:
GLint useTextureLoc = glGetUniformLocation(shaderProgram, "useTexture");
GLint lightModeLoc = glGetUniformLocation(shaderProgram, "lightMode");
// 传递 bool(OpenGL 没有 glUniform1b,所以用 int 代替)
glUniform1i(useTextureLoc, true);
// 传递 int
glUniform1i(lightModeLoc, 2);
(5)总结
六、完整示例
vertex_shader.vert
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos, 1.0);
}
fragment_shader.frag
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main()
{
FragColor = ourColor;
}
代码如下:
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
GLuint VBO = 0;
GLuint VAO = 0;
GLuint shaderProgram = 0;
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
}
void rend() {
glUseProgram(shaderProgram); //需要先调用才能生效
float _time = glfwGetTime();
float _green = sin(_time) * 0.5f + 0.5f;
int _location = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(_location, 0.0f, _green, 0.0f, 1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
}
void initModel() {
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO); //获取vbo的index
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定vbo的index,给vbo分配显存空间,传输数据
//告诉shader数据解析格式
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); //激活锚点
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void initShader(const char* _vertexPath, const char* _fragmPath) {
std::string _vertexCode("");
std::string _fragCode("");
std::ifstream _vShaderFile;
std::ifstream _fShaderFile;
_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
_vShaderFile.open(_vertexPath);
_fShaderFile.open(_fragmPath);
std::stringstream _vShaderStream, _fShaderStream;
_vShaderStream << _vShaderFile.rdbuf();
_fShaderStream << _fShaderFile.rdbuf();
_vertexCode = _vShaderStream.str();
_fragCode = _fShaderStream.str();
} catch (const std::exception&) {
std::string errStr = "read shader fail";
std::cout << errStr << std::endl;
}
const char* _vShaderStr = _vertexCode.c_str();
const char* _fShaderStr = _fragCode.c_str();
//shader的编译链接
unsigned int _vertexID = 0, _fragID = 0;
char _inforLog[512] = { 0 };
int _successFlag = 0;
//编译
_vertexID = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(_vertexID, 1, &_vShaderStr, NULL);
glCompileShader(_vertexID);
glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);
if (_successFlag) {
glGetShaderInfoLog(_vertexID, 512, NULL, _inforLog);
std::string errstr(_inforLog);
std::cout << _inforLog << std::endl;
}
_fragID = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(_fragID, 1, &_fShaderStr, NULL);
glCompileShader(_fragID);
glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);
if (_successFlag) {
glGetShaderInfoLog(_vertexID, 512, NULL, _inforLog);
std::string errstr(_inforLog);
std::cout << _inforLog << std::endl;
}
//链接
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, _vertexID);
glAttachShader(shaderProgram, _fragID);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);
if (!_successFlag) {
glGetProgramInfoLog(shaderProgram, 512, NULL, _inforLog);
std::string errStr(_inforLog);
std::cout << _inforLog << std::endl;
}
glDeleteShader(_vertexID);
glDeleteShader(_fragID);
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "OPenGL Core", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, 800, 600);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
initModel();
initShader("vertex_shader.vert", "fragment_shader.frag");
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
rend();
glfwSwapBuffers(window);
glfwPollEvents();
}
// 删除 VAO 和 VBO
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// 清理
glfwTerminate();
return 0;
}
输出结果如下所示: