1. OpenGL图像管线
OpenGL(Open Graphics Library)是一个跨平台的、功能强大的图形渲染API,用于开发2D和3D图形应用程序。它由Khronos Group维护,广泛应用于游戏开发、图形设计、虚拟现实等领域。
1.0.1. OpenGL的特点:
- 跨平台:支持Windows、macOS、Linux等多个操作系统。 注:macOS上对OpenGL的支持不如Windows。
- 硬件加速:利用GPU进行高效的图形渲染。
- 开放标准:由Khronos Group管理,提供灵活的扩展机制。
- 实时渲染:适用于需要高性能图形渲染的应用。
1.0.2. OpenGL的主要功能:
- 图形绘制:支持点、线、三角形等基本图元的绘制。
- 着色器支持:通过GLSL(OpenGL Shading Language)实现自定义的顶点和片段着色器。
- 纹理映射:加载和应用纹理以增强图形的视觉效果。
- 变换与投影:支持模型变换、视图变换和投影变换。
- 光照与阴影:实现逼真的光照和阴影效果。
OpenGL的灵活性和高性能使其成为图形开发的核心工具之一。
2. OpenGL 与 Direct3D 开发语言上的对比
特性 | OpenGL | Direct3D |
---|---|---|
语言支持 | 支持多种语言,包括 C、C++、Python、Java 等 | 主要支持 C 和 C++,部分支持 .NET 语言 |
跨平台性 | 跨平台,支持 Windows、macOS、Linux 等 | 仅支持 Windows 和 Xbox 平台 |
API 风格 | 面向过程的函数调用,简单直接 | 面向对象的设计,使用 COM 接口 |
着色器语言 | 使用 GLSL(OpenGL Shading Language) | 使用 HLSL(High-Level Shading Language) |
开发工具链 | 工具链灵活,支持多种第三方工具和库 | 集成在 Visual Studio,工具链较为封闭 |
学习曲线 | API 简单,适合初学者,但高级功能较复杂 | 面向对象设计,初学者需要适应 COM 模型 |
社区支持 | 开源社区活跃,文档和教程丰富 | 由微软主导,官方文档和支持较完善 |
2.1. 总结
- OpenGL 更加灵活,适合跨平台开发,支持多种语言,适合需要兼容多平台的项目。
- Direct3D 更适合 Windows 和 Xbox 平台开发,工具链与 Visual Studio 集成,适合微软生态系统的开发者。
OpenGL 与普通的软件不同,其是运行在GPU上的。
2.2. 管线概览
以下是OpenGL图像管线的主要阶段:
在实践中,OpenGL图像管线经常涉及的阶段:
- 顶点处理:处理顶点数据,包括顶点坐标、颜色、纹理坐标等。
- 光栅化:将顶点数据转换为片段,进行光栅化处理,生成片段。
- 片段处理:处理片段数据,包括颜色、深度、模板等。
其中,顶点处理和片段处理是核心阶段,它们分别负责处理顶点和片段数据。光栅化阶段则负责将顶点数据转换为片段,进行光栅化处理。
我们需要编写顶点着色器和片段着色器来处理顶点和片段数据,光栅化阶段则由OpenGL自动完成。
2.3. 第一个OpenGL程序
我们不采用顶点着色器和片段着色器,而是直接使用OpenGL函数将屏幕绘制为全红。 以下是运行结果 :
代码:
/*
* 第2章 OpenGL图形管线示例程序
* 这个程序演示了OpenGL的基本设置和渲染循环
* 创建一个600x600像素的窗口并将其背景设置为红色
*/// GLEW是OpenGL的扩展加载库
#include <GL\glew.h>
// GLFW提供了创建窗口和处理用户输入的功能
#include <GLFW\glfw3.h>
// 用于输出错误信息
#include <iostream>using namespace std;/*** 初始化OpenGL的设置* @param window 目标窗口的指针*/
void init(GLFWwindow* window) { }/*** 渲染函数,负责实际的绘制操作* @param window 目标窗口的指针* @param currentTime 当前时间,可用于动画*/
void display(GLFWwindow* window, double currentTime) {// 设置清除缓冲区时要使用的颜色(红色)glClearColor(1.0, 0.0, 0.0, 1.0);// 使用上面设置的颜色清除颜色缓冲区glClear(GL_COLOR_BUFFER_BIT);
}/*** 主函数:程序入口点* 负责初始化GLFW和GLEW,创建窗口,并进入渲染循环*/
int main(void) {// 初始化GLFW库,如果失败则退出if (!glfwInit()) { exit(EXIT_FAILURE); }// 设置OpenGL版本为4.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 创建一个600x600像素的窗口GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 1", NULL, NULL);// 将新创建的窗口设置为当前OpenGL上下文glfwMakeContextCurrent(window);// 初始化GLEW库,用于管理OpenGL扩展,如果失败则退出if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }// 启用垂直同步,防止画面撕裂glfwSwapInterval(1);// 调用自定义的初始化函数init(window);// 渲染循环:直到窗口被关闭才退出while (!glfwWindowShouldClose(window)) {// 调用显示函数进行渲染,传入当前时间用于动画display(window, glfwGetTime());// 交换前后缓冲区,将渲染结果显示到屏幕上glfwSwapBuffers(window);// 处理窗口事件(如键盘输入、鼠标移动等)glfwPollEvents();}// 清理资源并退出glfwDestroyWindow(window);glfwTerminate();exit(EXIT_SUCCESS);
}
整个程序的流程如下:
- 初始化GLFW库,如果失败则退出。
- 设置OpenGL版本为4.3。
- 创建一个600x600像素的窗口。
- 将新创建的窗口设置为当前OpenGL上下文。
- 初始化GLEW库,用于管理OpenGL扩展,如果失败则退出。
- 启用垂直同步,防止画面撕裂。
- 调用自定义的初始化函数。
- 渲染循环:直到窗口被关闭才退出。
- 调用显示函数进行渲染,传入当前时间用于动画。
- 交换前后缓冲区,将渲染结果显示到屏幕上。
- 处理窗口事件(如键盘输入、鼠标移动等)。
- 清理资源并退出。
重点关注:
- init():自定义的初始化函数,负责初始化OpenGL的设置。目前为空
- display():自定义的渲染函数,负责实际的绘制操作。目前将屏幕设置为红色。该函数会不断被调用,直到窗口被关闭才退出。
2.4. 常用函数
函数名称 | 描述 | 示例 |
---|---|---|
glfwInit() | 初始化GLFW库 | if (!glfwInit()) { exit(EXIT_FAILURE); } |
glfwWindowHint() | 设置窗口提示 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); |
glfwCreateWindow() | 创建一个窗口 | GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 1", NULL, NULL); |
glfwMakeContextCurrent() | 将窗口的上下文设置为当前线程的上下文 | glfwMakeContextCurrent(window); |
glewInit() | 初始化GLEW库 | if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); } |
glfwSwapInterval() | 设置交换间隔,用于垂直同步 | glfwSwapInterval(1); |
glfwDestroyWindow() | 销毁窗口 | glfwDestroyWindow(window); |
glfwTerminate() | 终止GLFW库 | glfwTerminate(); |
exit() | 退出程序 | exit(EXIT_SUCCESS); |
glClearColor() | 设置清空屏幕所用的颜色 | glClearColor(1.0, 0.0, 0.0, 1.0); |
glClear() | 清空屏幕 | glClear(GL_COLOR_BUFFER_BIT); |
glfwSwapBuffers() | 交换前后缓冲区 | glfwSwapBuffers(window); |
glfwPollEvents() | 处理所有待处理的事件 | glfwPollEvents(); |
glfwWindowShouldClose() | 检查窗口是否应该关闭 | while (!glfwWindowShouldClose(window)) { /* ... */ } |
2.5. 第一个着色器程序
我们通过一个简单的着色器程序来演示OpenGL的图形管线。下图是在屏幕上显示一个点:
如果控制台的输出为乱码,在程序中添加以下代码:
system("chcp 65001 > nul"); // 设置为 UTF-8 编码
我们在上一节的基础上进行添加,软件思路:
- 创建、编译和链接着色器程序
- 创建、绑定和配置顶点数组对象
- 调用显示函数进行渲染
- 调用glPointSize()设置点的大小,由于OpenGL默认的点大小为1.0,只显示一个点,会看不到效果,所以需要设置一个较大的点大小
- 调用glDrawArrays()绘制点,传入参数为GL_POINTS,表示绘制点,传入参数为0,表示从第0个顶点开始绘制,传入参数为1,表示绘制1个点
GLSL 与普通编码的过程不同,GLSL编译发生在C++运行时,实际运行在GPU中,我们不太好观察其编译错误。因此,我们使用辅助函数来打印编译错误,详见代码中的以下函数 :
- checkOpenGLError():检查OpenGL错误
- printShaderLog():打印着色器的编译错误
- printProgramLog():打印着色器程序的链接错误
顶点着色器代码
// 指定GLSL版本为4.30
#version 430/*** 顶点着色器的主函数* 负责设置顶点的位置*/
void main(void)
{// 设置顶点位置// vec4参数分别表示:// x=0.0:在x轴上居中// y=0.0:在y轴上居中// z=0.5:在z轴上的深度值// w=1.0:齐次坐标的w分量gl_Position = vec4(0.0, 0.0, 0.5, 1.0);
}
以下是OpenGL顶点着色器中常见的变量,无需定义,可以直接使用:
变量名称 | 类型 | 描述 |
---|---|---|
gl_Position | vec4 | 输出的顶点位置,在裁剪空间中定义。 |
gl_PointSize | float | 输出的点的大小(像素),仅在点图元时使用。 |
gl_VertexID | int | 输入的顶点ID,表示当前顶点的索引。 |
gl_InstanceID | int | 输入的实例ID,表示当前实例的索引(如果启用了实例化渲染)。 |
gl_PrimitiveID | int | 输入的图元ID,表示当前图元的索引(在几何着色器中使用)。 |
gl_Layer | int | 输出的图层ID,用于分层渲染(在几何着色器中使用)。 |
gl_ViewportIndex | int | 输出的视口索引,用于多视口渲染(在几何着色器中使用)。 |
in 变量 | 自定义类型 | 从顶点缓冲对象(VBO)传递的输入顶点属性,如位置、颜色、法线等。 |
out 变量 | 自定义类型 | 传递到片段着色器的输出变量,如颜色、纹理坐标等。 |
2.5.1. 说明
in
和out
变量的具体类型和名称由开发者根据需求定义。gl_
前缀的变量是OpenGL内置的变量,具有特定的用途。
片段着色器代码
// 指定GLSL版本为4.30
#version 430// 定义输出变量color,表示最终的像素颜色
out vec4 color;/*** 片段着色器的主函数* 负责设置每个片段(像素)的颜色*/
void main(void)
{// 设置输出颜色// vec4参数分别表示:// r=0.0:红色分量为0// g=0.0:绿色分量为0// b=1.0:蓝色分量为1(纯蓝色)// a=1.0:完全不透明color = vec4(0.0, 0.0, 1.0, 1.0);
}
注意:
- out vec4 color 表示输出变量,其类型为vec4,表示一个4分量的向量,用于存储颜色值。
- 在渲染过程中,片段(或片元)代表的是经过光栅化处理的潜在像素。它们可能最终成为屏幕上的像素,但在某些情况下(如深度测试或模板测试失败),某些片段可能不会被显示出来。因此,片段是处于渲染管线更早期的阶段,而像素则是最终呈现在屏幕上的元素。
完整cpp代码
/*
* 第2章 OpenGL绘制点示例程序
* 这个程序演示了如何使用OpenGL的着色器程序绘制一个点
* 包含了着色器程序的加载、编译和链接过程
*/// OpenGL相关库
#include <GL\glew.h> // GLEW用于加载OpenGL函数
#include <GLFW\glfw3.h> // GLFW用于创建窗口和处理输入// 标准库
#include <iostream> // 用于控制台输出
#include <string> // 用于字符串处理
#include <fstream> // 用于文件读取
#include <filesystem> // 用于文件路径处理using namespace std;// 定义顶点数组对象(VAO)的数量
#define numVAOs 1// 全局变量
GLuint renderingProgram; // 存储编译后的着色器程序
GLuint vao[numVAOs]; // 存储顶点数组对象/**
* 打印着色器编译日志
* 用于调试着色器编译错误
* @param shader 着色器对象的ID
*/
void printShaderLog(GLuint shader) {int len = 0; // 日志长度int chWrittn = 0; // 实际写入的字符数char *log; // 日志内容// 获取日志长度glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);if (len > 0) {log = (char *)malloc(len);// 获取实际的日志信息glGetShaderInfoLog(shader, len, &chWrittn, log);cout << "着色器编译日志: " << log << endl;free(log);}
}/**
* 打印着色器程序链接日志
* 用于调试着色器程序链接错误
* @param prog 着色器程序的ID
*/
void printProgramLog(int prog) {int len = 0; // 日志长度int chWrittn = 0; // 实际写入的字符数char *log; // 日志内容// 获取日志长度glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);if (len > 0) {log = (char *)malloc(len);// 获取实际的日志信息glGetProgramInfoLog(prog, len, &chWrittn, log);cout << "程序链接日志: " << log << endl;free(log);}
}/**
* 检查OpenGL错误
* 遍历所有待处理的错误并打印
* @return 如果发现错误返回true,否则返回false
*/
bool checkOpenGLError() {bool foundError = false;int glErr = glGetError();while (glErr != GL_NO_ERROR) {cout << "OpenGL错误代码: " << glErr << endl;foundError = true;glErr = glGetError();}return foundError;
}/**
* 从文件中读取着色器源代码
* @param filePath 着色器文件的路径
* @return 包含着色器源代码的字符串
*
* 这个函数负责:
* 1. 打开并读取着色器源文件
* 2. 将文件内容转换为字符串
* 3. 处理文件打开失败的情况
*/
string readFile(const char *filePath) {string content;ifstream fileStream(filePath, ios::in);string line = "";// 检查文件是否成功打开if (!fileStream.is_open()) {cout << "无法打开文件: " << filePath << endl;return content;}// 逐行读取文件内容while (getline(fileStream, line)) {content.append(line + "\n");}fileStream.close();return content;
}
/**
* 创建、编译和链接着色器程序
* @return 返回编译和链接成功的着色器程序ID
*
* 这个函数完成以下步骤:
* 1. 创建着色器对象
* 2. 读取并编译顶点着色器和片段着色器
* 3. 创建着色器程序并链接着色器
* 4. 进行错误检查和状态报告
*/
GLuint createShaderProgram() {// 编译状态变量GLint vertCompiled; // 顶点着色器编译状态GLint fragCompiled; // 片段着色器编译状态GLint linked; // 程序链接状态// 创建着色器和程序对象GLuint vShader = glCreateShader(GL_VERTEX_SHADER); // 创建顶点着色器GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER); // 创建片段着色器GLuint vfprogram = glCreateProgram(); // 创建着色器程序// 读取着色器源代码string vertShaderStr = readFile("./shader/vertShader.glsl");string fragShaderStr = readFile("./shader/fragShader.glsl");const char *vertShaderSrc = vertShaderStr.c_str();const char *fragShaderSrc = fragShaderStr.c_str();// 将源代码加载到着色器对象中glShaderSource(vShader, 1, &vertShaderSrc, NULL);glShaderSource(fShader, 1, &fragShaderSrc, NULL);// 编译顶点着色器glCompileShader(vShader);checkOpenGLError(); // 检查OpenGL错误glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);if (vertCompiled == 1) {cout << "顶点着色器编译成功" << endl;}else {cout << "顶点着色器编译失败" << endl;printShaderLog(vShader);}// 编译片段着色器glCompileShader(fShader);checkOpenGLError(); // 检查OpenGL错误glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);if (fragCompiled == 1) {cout << "片段着色器编译成功" << endl;}else {cout << "片段着色器编译失败" << endl;printShaderLog(fShader);}// 将着色器附加到程序对象并链接glAttachShader(vfprogram, vShader); // 附加顶点着色器glAttachShader(vfprogram, fShader); // 附加片段着色器glLinkProgram(vfprogram); // 链接程序// 检查链接状态checkOpenGLError(); // 检查OpenGL错误glGetProgramiv(vfprogram, GL_LINK_STATUS, &linked);if (linked == 1) {cout << "着色器程序链接成功" << endl;}else {cout << "着色器程序链接失败" << endl;printProgramLog(vfprogram);}cout << "着色器程序创建完成,ID: " << vfprogram << endl;return vfprogram;
}/**
* 初始化OpenGL的设置
* @param window 目标窗口的指针
*/
void init(GLFWwindow* window) { renderingProgram = createShaderProgram();glGenVertexArrays(numVAOs, vao);glBindVertexArray(vao[0]);}/**
* 渲染函数,负责实际的绘制操作
* @param window 目标窗口的指针
* @param currentTime 当前时间,可用于动画
*/
void display(GLFWwindow* window, double currentTime) {glUseProgram(renderingProgram);glPointSize(30.0f);glDrawArrays(GL_POINTS, 0, 1);
}/**
* 主函数:程序入口点
* 负责初始化GLFW和GLEW,创建窗口,并进入渲染循环
*/
int main(void) {// 获取当前工作目录的路径std::filesystem::path currentPath = std::filesystem::current_path();// 获取绝对路径std::filesystem::path absolutePath = std::filesystem::absolute(currentPath);// 提取目录名称std::string directoryName = currentPath.filename().string();std::cout << "absolute_path: " << absolutePath.string() << std::endl;system("chcp 65001 > nul"); // 设置为 UTF-8 编码// 初始化GLFW库,如果失败则退出if (!glfwInit()) { exit(EXIT_FAILURE); }// 设置OpenGL版本为4.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 创建一个600x600像素的窗口GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 2", NULL, NULL);// 将新创建的窗口设置为当前OpenGL上下文glfwMakeContextCurrent(window);// 初始化GLEW库,用于管理OpenGL扩展,如果失败则退出if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }// 启用垂直同步,防止画面撕裂glfwSwapInterval(1);// 调用自定义的初始化函数init(window);// 渲染循环:直到窗口被关闭才退出while (!glfwWindowShouldClose(window)) {// 调用显示函数进行渲染,传入当前时间用于动画display(window, glfwGetTime());// 交换前后缓冲区,将渲染结果显示到屏幕上glfwSwapBuffers(window);// 处理窗口事件(如键盘输入、鼠标移动等)glfwPollEvents();}// 清理资源并退出glfwDestroyWindow(window);glfwTerminate();exit(EXIT_SUCCESS);
}
2.6. 曲面细分着色器
曲面细分着色器(Tessellation Shader)是OpenGL中的一个着色器阶段,用于增加渲染模型的细节。它通过在几何图元(如三角形或四边形)的表面生成更多的顶点来细分这些图元,从而提高渲染的细节水平。曲面细分着色器通常由两个主要部分组成:
-
曲面细分控制着色器(Tessellation Control Shader,TCS):这个着色器阶段决定了每个输入图元将被细分为多少个更小的图元。它还可以设置每个新顶点的属性。
-
曲面细分评估着色器(Tessellation Evaluation Shader,TES):在这个阶段,新的顶点位置被计算出来,并且可以应用自定义的数学函数来生成复杂的几何形状。TES还负责设置每个新顶点的最终属性。
曲面细分着色器通常用于需要动态调整模型细节的场景,例如地形渲染、水面模拟或任何需要根据摄像机距离调整细节水平的对象。
对于地面,我们通常用两个三角形(四个顶点)来表示,但是通过曲面细分,我们可以用更多的三角形(更多顶点)来表示,从而提高渲染的细节。
目前阶段我们不会深入研究曲面细分着色器的细节。
2.7. 几何着色器
几何着色器是OpenGL渲染管线中的一个可选阶段,位于顶点着色器和片段着色器之间。它的主要功能是处理图元(如点、线、三角形)并生成额外的图元。这使得开发者可以在渲染过程中动态地修改或创建新的几何形状。
顶点着色器及片段着色器只能处理单个顶点或片段,而几何着色器可以操作图元(如点、线、三角形),并生成新的图元。这使得几何着色器可以用于创建复杂的几何形状,如网格、粒子系统或地形。
比如拉伸、压缩、旋转、扭曲、删除等操作。
比如下图左边是环面模型,右边是几何着色器修改后的模型。
通过这种方式,可以减少模型顶点数量,提高渲染效率。
2.8. 光栅化阶段
光栅化的本质是将每对顶点进行插值,生成一系列的顶点。如下图所示,将三角形的三个顶点进行插值,生成三条边。
到此时,呈现出来的图像将会是线框模型
即
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
而如果想要呈现出实心的三角形,需要将线框模式改为填充模式 ,这也是默认的模式,即完全光栅化
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2.9. 绘制三角形
我们在顶点着色器中通过gl_VertexID
来获取当前正在处理的顶点编号,设置顶点坐标,从而实现绘制三角形。 gl_VertexID
是OpenGL内置的变量,表示当前正在处理的顶点编号,从0开始计数。
2.9.1. 实心三角形
顶点着色器
// 指定GLSL版本为4.30
#version 430/*** 顶点着色器的主函数* 负责设置顶点的位置*/
void main(void)
{// 设置顶点位置// vec4参数分别表示:// x=0.0:在x轴上居中// y=0.0:在y轴上居中// z=0.5:在z轴上的深度值// w=1.0:齐次坐标的w分量//gl_Position = vec4(0.0, 0.0, 0.5, 1.0);if (gl_VertexID == 0){gl_Position = vec4(0.0, 0.0, 0.5, 1.0);}else if (gl_VertexID == 1){gl_Position = vec4(0.5, 0.0, 0.5, 1.0);}else if (gl_VertexID == 2){gl_Position = vec4(0.5, 0.5, 0.5, 1.0);}}
片段着色器
不用修改
main.cpp
void display(GLFWwindow *window, double currentTime)
{// 使用名为 renderingProgram 的着色器程序glUseProgram(renderingProgram);// 设置要绘制的点的大小为 1 像素glPointSize(1.0f);glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 设置绘制模式为填充//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 绘制点图元,从数组的第 0 个元素开始,绘制 3 个点glDrawArrays(GL_TRIANGLES, 0, 3);
}
2.9.2. 线框三角形
只需要 将填充模式改为线框模式即可
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2.10. 让三角形动起来
我们只需要不断改变顶点的位置,就可以让三角形动起来。
由于display()函数每秒会持续不断执行,因此我们只需要在每次执行时,将顶点的位置稍微移动一点,就可以让三角形看起来在动。
我们可以在c++代码中,增加一个变量offset,在每次执行display()函数时,将offset的值进行改变,同时将offset的值传递给顶点着色器,从而实现让三角形动起来的效果。
在顶点着色器中,通过uniform
变量offsetX
来接收offset
的值,从而实现顶点位置的改变。
main.cpp
void display(GLFWwindow *window, double currentTime)
{// 清除颜色缓冲区和深度缓冲区glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);static float offset=0.0f; // 偏移量,用于动画效果static float delta=0.01f; // 偏移增量offset+=delta; // 更新偏移量if(offset>0.5f) delta=-0.01f;if(offset<-0.5f) delta=0.01f; // 如果偏移量超过范围,反转增量// 使用名为 renderingProgram 的着色器程序glUseProgram(renderingProgram);// 设置要绘制的点的大小为 1 像素glPointSize(1.0f);//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 设置绘制模式为填充glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 绘制点图元,从数组的第 0 个元素开始,绘制 3 个点GLuint offsetLocation = glGetUniformLocation(renderingProgram, "offsetX"); // 获取偏移量uniform变量的位置glUniform1f(offsetLocation, offset); // 设置偏移量uniform变量的值glDrawArrays(GL_TRIANGLES, 0, 3);
}
顶点着色器
// 指定GLSL版本为4.30
#version 430uniform float offsetX; // x轴偏移量
/*** 顶点着色器的主函数* 负责设置顶点的位置*/
void main(void)
{// 设置顶点位置// vec4参数分别表示:// x=0.0:在x轴上居中// y=0.0:在y轴上居中// z=0.5:在z轴上的深度值// w=1.0:齐次坐标的w分量//gl_Position = vec4(0.0, 0.0, 0.5, 1.0);if (gl_VertexID == 0){gl_Position = vec4(-0.3+offsetX, 0.0, 0.5, 1.0);}else if (gl_VertexID == 1){gl_Position = vec4(0.2+offsetX, 0.0, 0.5, 1.0);}else if (gl_VertexID == 2){gl_Position = vec4(0.2+offsetX, 0.5, 0.5, 1.0);}}
注意:
我们在display()函数中,需要调用以下代码
// 清除颜色缓冲区和深度缓冲区glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
否则,我们无法看到三角形在动,且会看到一些奇怪的图形,这是因为图形没有清空,导致图形叠加在一起。
学习笔记完整源代码下载