计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 02.OpenGL图像管线

1. OpenGL图像管线

OpenGL(Open Graphics Library)是一个跨平台的、功能强大的图形渲染API,用于开发2D和3D图形应用程序。它由Khronos Group维护,广泛应用于游戏开发、图形设计、虚拟现实等领域。

1.0.1. OpenGL的特点:

  1. 跨平台:支持Windows、macOS、Linux等多个操作系统。 注:macOS上对OpenGL的支持不如Windows。
  2. 硬件加速:利用GPU进行高效的图形渲染。
  3. 开放标准:由Khronos Group管理,提供灵活的扩展机制。
  4. 实时渲染:适用于需要高性能图形渲染的应用。

1.0.2. OpenGL的主要功能:

  1. 图形绘制:支持点、线、三角形等基本图元的绘制。
  2. 着色器支持:通过GLSL(OpenGL Shading Language)实现自定义的顶点和片段着色器。
  3. 纹理映射:加载和应用纹理以增强图形的视觉效果。
  4. 变换与投影:支持模型变换、视图变换和投影变换。
  5. 光照与阴影:实现逼真的光照和阴影效果。

OpenGL的灵活性和高性能使其成为图形开发的核心工具之一。

2. OpenGL 与 Direct3D 开发语言上的对比

特性OpenGLDirect3D
语言支持支持多种语言,包括 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图像管线经常涉及的阶段:

  1. 顶点处理:处理顶点数据,包括顶点坐标、颜色、纹理坐标等。
  2. 光栅化:将顶点数据转换为片段,进行光栅化处理,生成片段。
  3. 片段处理:处理片段数据,包括颜色、深度、模板等。

其中,顶点处理和片段处理是核心阶段,它们分别负责处理顶点和片段数据。光栅化阶段则负责将顶点数据转换为片段,进行光栅化处理。
我们需要编写顶点着色器和片段着色器来处理顶点和片段数据,光栅化阶段则由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);
}

整个程序的流程如下:

  1. 初始化GLFW库,如果失败则退出。
  2. 设置OpenGL版本为4.3。
  3. 创建一个600x600像素的窗口。
  4. 将新创建的窗口设置为当前OpenGL上下文。
  5. 初始化GLEW库,用于管理OpenGL扩展,如果失败则退出。
  6. 启用垂直同步,防止画面撕裂。
  7. 调用自定义的初始化函数。
  8. 渲染循环:直到窗口被关闭才退出。
  9. 调用显示函数进行渲染,传入当前时间用于动画。
  10. 交换前后缓冲区,将渲染结果显示到屏幕上。
  11. 处理窗口事件(如键盘输入、鼠标移动等)。
  12. 清理资源并退出。

重点关注:

  1. init():自定义的初始化函数,负责初始化OpenGL的设置。目前为空
  2. 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 编码

我们在上一节的基础上进行添加,软件思路:

  1. 创建、编译和链接着色器程序
  2. 创建、绑定和配置顶点数组对象
  3. 调用显示函数进行渲染
  4. 调用glPointSize()设置点的大小,由于OpenGL默认的点大小为1.0,只显示一个点,会看不到效果,所以需要设置一个较大的点大小
  5. 调用glDrawArrays()绘制点,传入参数为GL_POINTS,表示绘制点,传入参数为0,表示从第0个顶点开始绘制,传入参数为1,表示绘制1个点

GLSL 与普通编码的过程不同,GLSL编译发生在C++运行时,实际运行在GPU中,我们不太好观察其编译错误。因此,我们使用辅助函数来打印编译错误,详见代码中的以下函数 :

  1. checkOpenGLError():检查OpenGL错误
  2. printShaderLog():打印着色器的编译错误
  3. 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_Positionvec4输出的顶点位置,在裁剪空间中定义。
gl_PointSizefloat输出的点的大小(像素),仅在点图元时使用。
gl_VertexIDint输入的顶点ID,表示当前顶点的索引。
gl_InstanceIDint输入的实例ID,表示当前实例的索引(如果启用了实例化渲染)。
gl_PrimitiveIDint输入的图元ID,表示当前图元的索引(在几何着色器中使用)。
gl_Layerint输出的图层ID,用于分层渲染(在几何着色器中使用)。
gl_ViewportIndexint输出的视口索引,用于多视口渲染(在几何着色器中使用)。
in 变量自定义类型从顶点缓冲对象(VBO)传递的输入顶点属性,如位置、颜色、法线等。
out 变量自定义类型传递到片段着色器的输出变量,如颜色、纹理坐标等。

2.5.1. 说明

  • inout 变量的具体类型和名称由开发者根据需求定义。
  • 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);
}

注意:

  1. out vec4 color 表示输出变量,其类型为vec4,表示一个4分量的向量,用于存储颜色值。
  2. 在渲染过程中,片段(或片元)代表的是经过光栅化处理的潜在像素。它们可能最终成为屏幕上的像素,但在某些情况下(如深度测试或模板测试失败),某些片段可能不会被显示出来。因此,片段是处于渲染管线更早期的阶段,而像素则是最终呈现在屏幕上的元素。

完整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中的一个着色器阶段,用于增加渲染模型的细节。它通过在几何图元(如三角形或四边形)的表面生成更多的顶点来细分这些图元,从而提高渲染的细节水平。曲面细分着色器通常由两个主要部分组成:

  1. 曲面细分控制着色器(Tessellation Control Shader,TCS):这个着色器阶段决定了每个输入图元将被细分为多少个更小的图元。它还可以设置每个新顶点的属性。

  2. 曲面细分评估着色器(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. 让三角形动起来

1

我们只需要不断改变顶点的位置,就可以让三角形动起来。
由于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);

否则,我们无法看到三角形在动,且会看到一些奇怪的图形,这是因为图形没有清空,导致图形叠加在一起。

学习笔记完整源代码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/904310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62

Linux_基础篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.01 适用环境: Centos7 文档说明 本文…

算法基础学习|03二分

一、思路 &#xff08;1&#xff09;mid(lr1)/2 if(check(mid)):1.true [mid,r] lmid 2.false [l,mid-1] rmid-1 &#xff08;2&#xff09;mid(lr)/2 if(check(mid)):1.true [l,mid] rmid 2.false [mid1,r] lmid1 二、模板 如何选择模…

18. LangChain分布式任务调度:大规模应用的性能优化

引言&#xff1a;从单机到万级并发的进化 2025年某全球客服系统通过LangChain分布式改造&#xff0c;成功应对黑五期间每秒12,000次的咨询请求。本文将基于LangChain的分布式架构&#xff0c;详解如何实现AI任务的自动扩缩容与智能调度。 一、分布式系统核心指标 1.1 性能基准…

Java泛型(补档)

核心概念 Java 泛型是 Java SE 1.5 引入的一项重要特性&#xff0c;它的核心思想是 参数化类型&#xff08;Parameterized Types&#xff09;&#xff0c;即通过将数据类型作为参数传递给类、接口或方法&#xff0c;使代码能够灵活地处理多种类型&#xff0c;同时保证类型安全性…

LeetCode 热题 100:普通数组

53. 最大子数组和 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输…

【kafka系列】消费者组

目录 消费者组功能点 1. 动态负载均衡 2. 容错高可用 3. 消费进度管理 4. 并行消费能力 5. 消费隔离性 其他要点 1. Rebalance过程详解 2. 位移提交的精确语义 3. 消费者限速策略 4. 跨机房消费设计 消费者组功能点 1. 动态负载均衡 核心机制&#xff1a;通过Rebal…

黑马点评day01(基于Redis)

1.7 Redis代替session的业务流程 1.7.1、设计key的结构 首先我们要思考一下利用redis来存储数据&#xff0c;那么到底使用哪种结构呢&#xff1f;由于存入的数据比较简单&#xff0c;我们可以考虑使用String&#xff0c;或者是使用哈希&#xff0c;如下图&#xff0c;如果使用…

Python爬虫实战:获取优美图库各类高清图片,为用户提供设计素材

一、引言 在互联网时代,高清壁纸资源丰富多样,而优美图库作为一个提供大量精美壁纸的网站,吸引了众多用户。通过 Python 爬虫技术,可以自动化地从该网站获取所需的壁纸资源,为用户节省时间和精力。然而,网站通常会采取反爬措施来防止数据被恶意抓取,因此需要在爬虫程序…

Go反射-通过反射调用结构体的方法(带入参)

使用反射前&#xff0c;我们需要提前做好映射配置 papckage_struct_relationship.go package reflectcommonimport (api "template/api" )// 包名到包对象的映射 var structMap map[string]func() interface{}{"template/api": func() interface{} { re…

Git_.gitignore文件简介及使用

.gitignore 这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中。实际项目中&#xff0c;很多文件都是不需要版本管理的&#xff0c;比如Python的.pyc文件&#xff0c;Git会根据这个文件里配置的这些规则来判断是否将文件添加到版本控制中。 注意&#xff0c;直接新建文…

HarmonyOS ArkUI安全控件开发指南:粘贴、保存与位置控件的实现与隐私保护实践

目录 安全控件1. 粘贴控件1.1 约束与限制1.2 开发步骤 2. 保存控件2.1 约束与限制2.2 开发步骤 3. 位置控件3.1 约束与限制3.2 开发步骤 安全控件 安全控件是系统提供的一组系统实现的ArkUI组件&#xff0c;其中保存控件在用户首次使用时&#xff0c;会弹出通知弹窗&#xff0…

C++笔记之接口`Interface`

C++笔记之接口Interface code review! 一个简洁简短的 C++ 接口实现示例: #include <iostream>// 1. 定义接口(抽象类) class Shape {public:

动态图表 -- eg1

问题&#xff1a; 前端vue&#xff0c;后端springboot&#xff0c;实现动态表格样式&#xff0c;&#xff08;表格List<Student>&#xff0c;Student类有年级&#xff0c;班级&#xff0c;文理科分类&#xff0c;姓名&#xff0c;学号&#xff0c;等属性。先根据年级分类…

C++学习之shell高级和正则表达式

目录 1.正则表达式 2.C中使用正则 3.复习 4.sort命令 5.uniq命令 6.wc命令 7.grep命令 8.find命令 9.xargs命令 10.sed命令 11.awk命令 12.crontab 1.正则表达式 1 管道 使用| 将多个命令拼接在一起 原理&#xff0c;就是将前一个命令的标准输出作为后一个…

【Vue】 实现TodoList案例(待办事项)

目录 组件化编码流程&#xff08;通用&#xff09; 1.实现静态组件&#xff1a;抽取组件&#xff0c;使用组件实现静态页面效果 2.展示动态数据&#xff1a; 1. 常规 HTML 属性 3.交互——从绑定事件监听开始 什么时候要用 event&#xff1a; 什么时候不需要用 event&am…

【Bootstrap V4系列】学习入门教程之 组件-卡片(Card)

Bootstrap V4系列 学习入门教程之 组件-卡片&#xff08;Card&#xff09; 卡片&#xff08;Card&#xff09;一、Example二、Content types 内容类型2.1 Body 主体2.2 Titles, text, and links 标题、文本和链接2.3 Images 图片2.4 List groups 列表组2.5 Kitchen sink 洗涤槽…

java学习之数据结构:四、树(代码补充)

这部分主要是用代码实现有序二叉树、树遍历、删除节点 目录 1.构建有序二叉树 1.1原理 1.2插入实现 2.广度优先遍历--队列实现 3.深度优先遍历--递归实现 3.1先序遍历 3.2中序遍历 3.3后序遍历 4.删除 4.1删除叶子节点 4.2删除有一棵子树的节点 4.3删除有两棵子树的节…

架构进阶:什么是数据架构,如何理解数据架构?(华为)

数据架构是企业架构的重要组成部分,DAMA、IBM 及国内大厂对其定义各有侧重。它包含数据资产目录、数据标准、数据模型和数据分布四个组件。数据资产目录可梳理企业数据资产,数据标准统一数据含义和规则,数据模型反映业务对象关联关系,数据分布呈现数据流动情况。数据架构是…

Unity SpriteEditor(精灵图片编辑器)

&#x1f3c6; 个人愚见&#xff0c;没事写写笔记 &#x1f3c6;《博客内容》&#xff1a;Unity3D开发内容 &#x1f3c6;&#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f50e;SpriteEditor&#xff1a; 精灵图片编辑器 &#x1f4cc;用于编辑2D游戏开发中使用的Sp…

【网络原理】从零开始深入理解HTTP的报文格式(一)

本篇博客给大家带来的是网络HTTP协议的知识点, 重点介绍HTTP的报文格式. &#x1f40e;文章专栏: JavaEE初阶 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f68…