✺ch2——OpenGL图像管线

目录

    • 基于C++图形应用&管线概览
    • OpenGL类型
    • 第一个C++/OpenGL应用程序
          • ◍API (1)
    • GLSL类型
    • 着色器——画一个点的程序
          • ◍API (2)
          • ◍API (3)
    • 栅格化
    • 像素操作——Z-buffer算法
    • 检测 OpenGL 和 GLSL 错误
          • ◍API (4)
    • 从顶点来构建一个三角形
    • 场景动画
          • ◍API (5)
    • OpenGL某些方面的数值——glGet()

基于C++图形应用&管线概览

✺ch2——OpenGL图像管线_第1张图片
✺ch2——OpenGL图像管线_第2张图片
将 GLSL 程序载入这些着色器阶段也是 C++/OpenGL 应用程序的责任之一,其过程如下:
(1)使用 C++获取 GLSL 着色器代码,既可以从文件中读取,也可以硬编码在字符串中。
(2)创建 OpenGL 着色器对象,并将 GLSL 着色器代码加载到着色器对象中。
(3)用 OpenGL 命令编译并连接着色器对象,将它们装载到 GPU。

OpenGL类型

✺ch2——OpenGL图像管线_第3张图片

第一个C++/OpenGL应用程序

#include 
#include 
#include 
using namespace std;
void init(GLFWwindow* window) { }

void display(GLFWwindow* window, double currentTime) {
    glClearColor(1.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
}

int main(void) {
    if (!glfwInit()) { exit(EXIT_FAILURE); }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 1", NULL, NULL);
    glfwMakeContextCurrent(window);
    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);
}

✺ch2——OpenGL图像管线_第4张图片

◍API (1)

glClearColor — 指定颜色缓冲区的清除值
void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
参数:
red, green, blue, alpha
指定清除颜色缓冲区时使用的红色、绿色、蓝色和 Alpha 值。初始值全部为0。
描述:
glClearColor 指定了 glClear 用于清除颜色缓冲区的红色、绿色、蓝色和 alpha 值。由 glClearColor 指定的值被限制在范围 [0,1] 内。这意味着参数是从 0.0 到 1.0 的浮点值。

glClear — 将缓冲区清除为预设值
void glClear(GLbitfield mask);
参数:
mask - 指示要清除的缓冲区的掩码的按位或。这三个掩码是 GL_COLOR_BUFFER_BIT 、 GL_DEPTH_BUFFER_BIT 和 GL_STENCIL_BUFFER_BIT 。
描述:
glClear 函数用于将窗口的位平面区域设置为之前由 glClearColorglClearDepthglClearStencil 选择的值。通过使用 glDrawBuffer 一次选择多个缓冲区,可以同时清除多个颜色缓冲区。
glClear 的操作受到像素拥有权测试、剪刀测试、抖动和缓冲区写掩码的影响。剪刀框限定了清除的区域。glClear 忽略了 alpha 函数、混合函数、逻辑操作、模板测试、纹理映射和深度缓冲。
glClear 接受一个参数,该参数是几个值的按位或,指示要清除的缓冲区。
这些值如下:

  • GL_COLOR_BUFFER_BIT:表示当前启用颜色写入的缓冲区。
  • GL_DEPTH_BUFFER_BIT:表示深度缓冲区。
  • GL_STENCIL_BUFFER_BIT:表示模板缓冲区。
     

每个缓冲区被清除到的值取决于该缓冲区的清除值的设置。

glewInit
首先,您需要创建一个有效的OpenGL渲染环境,并调用glewInit()来初始化扩展入口点。如果glewInit()返回GLEW_OK,则初始化成功,您可以使用可用的扩展以及核心OpenGL功能。
GLEW是OpenGL扩展库,它提供了一种简单的机制来查询和加载OpenGL扩展。GLEW的初始化需要在创建OpenGL渲染环境之后,但在使用任何OpenGL扩展之前进行。GLEW的初始化过程包括以下步骤:

  1. 创建OpenGL渲染环境。
  2. 调用glewInit()函数。
  3. 检查GLEW_OK是否返回,如果返回,则初始化成功。
    如果初始化成功,您可以使用可用的扩展以及核心OpenGL功能。例如,您可以使用以下代码检查GLEW是否初始化成功:
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, “Error: %s\n”, glewGetErrorString(err));
        exit(1);
    }

glfwWindowHint
void glfwWindowHint(int hint, int value);
参数:
[in] hint - 设置的窗口提示。
[in] value - 窗口提示的新值。
这个函数为下一次调用glfwCreateWindow设置了一些提示。一旦设置,这些提示的值将保持不变,直到被此函数或glfwDefaultWindowHints调用更改,或者直到库被终止。
只能使用整数值提示来设置此函数。字符串值提示使用glfwWindowHintString设置。
此函数不会检查指定的提示值是否有效。如果您将提示设置为无效的值,下一次调用glfwCreateWindow将会报告这一点。
某些提示是特定于平台的。这些提示可以在任何平台上设置,但它们只会影响其特定的平台。其他平台将忽略它们。设置这些提示不需要特定于平台的头文件或函数。

glfwCreateWindow
GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
参数:

  • [in]width:窗口的期望宽度,以屏幕坐标表示。必须大于零。
  • [in]height:窗口的期望高度,以屏幕坐标表示。必须大于零。
  • [in]title:初始的UTF-8编码窗口标题。
  • [in]monitor:用于全屏模式的监视器,或者对于窗口模式为NULL。
  • [in]share:与之共享资源的窗口的上下文,或者不共享资源的NULL。
     

返回:
创建的窗口的句柄,如果发生错误则为 NULL 。
 
描述:
这个函数用于创建一个窗口及其关联的OpenGL或OpenGL ES上下文。大部分控制窗口和上下文创建的选项都是通过窗口提示来指定的。
 
成功创建窗口不会改变当前的上下文。在您使用新创建的上下文之前,您需要将其设置为当前上下文。关于共享参数的详细信息,请参阅上下文对象共享。
 
所创建的窗口、帧缓冲和上下文可能与您请求的不同,因为并非所有参数和提示都是硬约束。这包括窗口的大小,尤其是全屏窗口的大小。要查询已创建窗口、帧缓冲和上下文的实际属性,请参阅glfwGetWindowAttrib、glfwGetWindowSize和glfwGetFramebufferSize。
 
要创建全屏窗口,您需要指定窗口将覆盖的监视器。如果未指定监视器,窗口将处于窗口模式。除非您有一种让用户选择特定监视器的方法,否则建议您选择主监视器。有关如何查询连接的监视器的更多信息,请参阅检索监视器。
 
对于全屏窗口,指定的大小将成为窗口所需视频模式的分辨率。只要全屏窗口未被最小化,支持的视频模式将与所需视频模式最接近的模式设置为指定的监视器。有关全屏窗口的更多信息,包括创建所谓的窗口全屏或无边框全屏窗口,请参阅“窗口全屏”窗口。
 
创建窗口后,您可以使用glfwSetWindowMonitor在窗口模式和全屏模式之间切换。这不会影响其OpenGL或OpenGL ES上下文。
 
默认情况下,新创建的窗口使用窗口系统建议的位置。要在特定位置创建窗口,请首先使用GLFW_VISIBLE窗口提示使其初始不可见,然后设置其位置并显示它。
 
只要至少有一个全屏窗口未被最小化,屏幕保护程序将被禁止启动。
 
窗口系统对窗口大小设置了限制。非常大或非常小的窗口尺寸可能会在创建时被窗口系统覆盖。请在创建后检查实际大小。
 
交换间隔在窗口创建时不会被设置,初始值可能因驱动程序设置和默认值而异。

glfwMakeContextCurrent
void glfwMakeContextCurrent(GLFWwindow* window);
参数:
[in] window - 要使其上下文成为当前上下文的窗口,或 NULL 分离当前上下文。
描述:
这个函数将指定窗口的OpenGL或OpenGL ES上下文设置为当前线程的上下文。一个上下文一次只能在一个线程上设置为当前上下文,每个线程一次只能有一个当前上下文。
 
在将上下文从一个线程移动到另一个线程时,您必须在新线程上设置它为非当前上下文之前,将其设置为旧线程上的非当前上下文。
 
默认情况下,使上下文变为非当前上下文会隐式地强制执行流水线刷新。在支持GL_KHR_context_flush_control的机器上,您可以通过设置GLFW_CONTEXT_RELEASE_BEHAVIOR提示来控制上下文是否执行此刷新。
 
指定的窗口必须具有OpenGL或OpenGL ES上下文。如果指定没有上下文的窗口,将生成GLFW_NO_WINDOW_CONTEXT错误。

glfwSwapInterval
void glfwSwapInterval(int interval);
参数:
[in] interval - glfwSwapBuffers 交换缓冲区之前等待的最小屏幕更新次数。
描述:
这个函数设置当前OpenGL或OpenGL ES上下文的交换间隔,即从调用glfwSwapBuffers开始等待的屏幕更新次数,直到交换缓冲区并返回。这有时被称为垂直同步、垂直回溯同步或简称为垂直同步。
 
支持WGL_EXT_swap_control_tear和GLX_EXT_swap_control_tear扩展之一的上下文还接受负交换间隔,这允许驱动程序即使帧稍晚到达也立即进行交换。您可以使用glfwExtensionSupported检查这些扩展。
 
调用此函数时,上下文必须在调用线程上设置为当前上下文。在没有当前上下文的情况下调用此函数将导致GLFW_NO_CURRENT_CONTEXT错误。
 
此函数不适用于Vulkan。如果您使用Vulkan进行渲染,请查看您的交换链的呈现模式。

glfwSwapBuffers
void glfwSwapBuffers(GLFWwindow* window);
参数:
window - 要交换缓冲区的窗口。
描述:
这个函数用于在使用OpenGL或OpenGL ES进行渲染时,交换指定窗口的前后缓冲区。如果交换间隔大于零,GPU驱动程序会在交换缓冲区之前等待指定数量的屏幕更新。
 
指定的窗口必须具有OpenGL或OpenGL ES上下文。如果指定的窗口没有上下文,将会生成GLFW_NO_WINDOW_CONTEXT错误。
 
这个函数不适用于Vulkan。如果你正在使用Vulkan进行渲染,请参考vkQueuePresentKHR。

glfwPollEvents
void glfwPollEvents(void);
这个函数只处理已经在事件队列中的事件,然后立即返回。处理事件会导致与这些事件相关联的窗口和输入回调被调用。
 
在某些平台上,窗口移动、调整大小或菜单操作会导致事件处理被阻塞。这是因为这些平台上的事件处理的设计方式。您可以使用窗口刷新回调在此类操作期间在必要时重新绘制窗口的内容。
 
请不要假设您设置的回调只会在像这个函数这样的事件处理函数响应时被调用。虽然需要轮询事件,但需要GLFW注册自己的回调的窗口系统可以在响应于许多窗口系统函数调用时将事件传递给GLFW。GLFW会在返回之前将这些事件传递给应用程序回调。
 
对于操纵杆输入,不需要进行事件处理。

glfwGetTime
double glfwGetTime(void);
这个函数返回当前的GLFW时间,以秒为单位。除非使用glfwSetTime设置了时间,否则它会测量自GLFW初始化以来经过的时间。
 
这个函数和glfwSetTime是基于glfwGetTimerFrequencyglfwGetTimerValue的辅助函数。
 
计时器的分辨率因系统而异,但通常在几微秒或纳秒的数量级上。它使用每个支持平台上的最高分辨率单调时间源。
 
返回值:

  • 当前时间,以秒为单位,如果发生错误则返回零。

GLSL类型

✺ch2——OpenGL图像管线_第5张图片

着色器——画一个点的程序

#include 
#include 
#include 
using namespace std;

#define numVAOs 1

GLuint renderingProgram;
GLuint vao[numVAOs];

GLuint createShaderProgram() {
	const char *vshaderSource =
		"#version 430    \n"
		"void main(void) \n"
		"{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";

	const char *fshaderSource =
		"#version 430    \n"
		"out vec4 color; \n"
		"void main(void) \n"
		"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";

	GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
	GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
	GLuint vfprogram = glCreateProgram();

	glShaderSource(vShader, 1, &vshaderSource, NULL);
	glShaderSource(fShader, 1, &fshaderSource, NULL);
	glCompileShader(vShader);
	glCompileShader(fShader);
		
	glAttachShader(vfprogram, vShader);
	glAttachShader(vfprogram, fShader);
	glLinkProgram(vfprogram);

	return vfprogram;
}

void init(GLFWwindow* window) {
	renderingProgram = createShaderProgram();
	glGenVertexArrays(numVAOs, vao);
	glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
	glUseProgram(renderingProgram);
	glPointSize(30.0f);
	glDrawArrays(GL_POINTS, 0, 1);
}

int main(void) {
	if (!glfwInit()) { exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 2", NULL, NULL);
	glfwMakeContextCurrent(window);
	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);
}

✺ch2——OpenGL图像管线_第6张图片

◍API (2)

glCreateShader — 创建一个着色器对象
GLuint glCreateShader(GLenum shaderType);
参数:
shaderType - 指定了要创建的着色器的类型,它必须是以下之一:GL_COMPUTE_SHADERGL_VERTEX_SHADERGL_TESS_CONTROL_SHADERGL_TESS_EVALUATION_SHADERGL_GEOMETRY_SHADERGL_FRAGMENT_SHADER
 
glCreateShader 函数创建一个空的着色器对象,并返回一个非零值,用于引用该对象。着色器对象用于维护定义着色器的源代码字符串。shaderType 参数指示要创建的着色器的类型。支持五种着色器类型:

  1. GL_COMPUTE_SHADER:计算着色器,用于在可编程计算处理器上运行。
  2. GL_VERTEX_SHADER:顶点着色器,用于在可编程顶点处理器上运行。
  3. GL_TESS_CONTROL_SHADER:曲面细分控制着色器,用于在可编程曲面细分控制处理器中运行。
  4. GL_TESS_EVALUATION_SHADER:曲面细分评估着色器,用于在可编程曲面细分评估处理器中运行。
  5. GL_GEOMETRY_SHADER:几何着色器,用于在可编程几何处理器上运行。
  6. GL_FRAGMENT_SHADER:片段着色器,用于在可编程片段处理器上运行。

 
创建后,着色器对象的 GL_SHADER_TYPE 参数将根据 shaderType 的值设置为上述类型之一。¹

glShaderSource — 替换着色器对象中的源代码
void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
参数:

  • shader:这是一个着色器对象的句柄,用于指定要替换源代码的着色器。
  • count:这个参数指定了字符串数组中的元素数量。
  • string:这是一个指向包含要加载到着色器中的源代码的字符串的指针数组。
  • length:这是一个字符串长度的数组。

 
具体来说,glShaderSource函数将指定的字符串数组中的源代码设置到着色器对象中。如果之前在着色器对象中存储了源代码,那么它将被完全替换。数组中的字符串数量由count指定。如果lengthNULL,则每个字符串被假定为以空字符结尾。如果length不为NULL,则它指向一个包含每个字符串对应元素的字符串长度的数组。length数组中的每个元素可以包含相应字符串的长度(空字符不计入字符串长度)或小于0的值,表示该字符串以空字符结尾。这些源代码字符串在此阶段不会被扫描或解析;它们只是简单地复制到指定的着色器对象中。

glCompileShader — 编译着色器对象
void glCompileShader(GLuint shader);
参数:
shader - 指定要编译的着色器对象。
glCompileShader 编译存储在由 shader 指定的着色器对象中的源代码字符串。
 
编译状态将作为着色器对象状态的一部分存储。如果着色器在没有错误的情况下编译完成并且可以使用,该值将设置为 GL_TRUE;否则设置为 GL_FALSE。可以通过调用 glGetShader 并传递 shader 和 GL_COMPILE_STATUS 参数来查询此状态。
 
着色器的编译可能因为多种原因而失败,这些原因由 OpenGL 着色语言规范指定。无论编译是否成功,都可以通过调用 glGetShaderInfoLog 来获取有关着色器对象的编译信息日志。

glCreateProgram — 创建一个程序对象
GLuint glCreateProgram(void);
返回:
如果创建程序对象时发生错误,则该函数返回 0。
描述:
glCreateProgram 创建一个空的程序对象,并返回一个非零值,以便可以引用它。程序对象是可以附加着色器对象的对象。这提供了一种机制,用于指定将链接以创建程序的着色器对象。它还提供了一种检查将用于创建程序的着色器之间兼容性的方法(例如,检查顶点着色器和片段着色器之间的兼容性)。当不再作为程序对象的一部分时,着色器对象可以被分离。
 
通过使用 glAttachShader 成功地将着色器对象附加到程序对象上,使用 glCompileShader 成功地编译着色器对象,并使用 glLinkProgram 成功地链接程序对象,可以在程序对象中创建一个或多个可执行文件。当调用 glUseProgram 时,这些可执行文件将成为当前状态的一部分。可以通过调用 glDeleteProgram 来删除程序对象。与程序对象关联的内存将在它不再是任何上下文的当前渲染状态的一部分时被删除。
 
这个函数在创建程序对象时发生错误时返回 0。

glAttachShader — 将着色器对象附加到程序对象
void glAttachShader(GLuint program, GLuint shader);
glAttachShader 函数将着色器对象附加到程序对象上,以便创建完整的着色器程序。以下是关于 glAttachShader 的详细描述:

  • 参数
    - program:指定要附加着色器对象的程序对象。
    - shader:指定要附加的着色器对象。
  • 描述
  • 为了创建完整的着色器程序,需要一种方式来指定将要链接在一起的内容列表。程序对象提供了这种机制。
  • 要在程序对象中链接在一起的着色器首先必须附加到该程序对象上。glAttachShader 将由 shader 指定的着色器对象附加到由 program 指定的程序对象上。这表示 shader 将包含在将在 program 上执行的链接操作中。
  • 可以对附加到程序对象的着色器对象执行的所有操作都是有效的,无论着色器对象是否附加到程序对象上。
  • 可以在将源代码加载到着色器对象之前或在编译着色器对象之前将着色器对象附加到程序对象上。
  • 可以附加多个相同类型的着色器对象,因为每个着色器对象可能包含完整着色器的一部分。
  • 还可以将着色器对象附加到多个程序对象上。
  • 如果着色器对象在附加到程序对象时被删除,它将被标记为待删除状态,直到调用 glDetachShader 将其从所有附加到它的程序对象中分离为止。

glLinkProgram — 链接程序对象
void glLinkProgram(GLuint program);
参数:
program - 指定要链接的程序对象的句柄。
描述:
✺ch2——OpenGL图像管线_第7张图片
glLinkProgram函数用于链接由program指定的程序对象。如果附加到program的着色器对象中有类型为GL_VERTEX_SHADER的着色器,它们将用于创建一个可在可编程顶点处理器上运行的可执行文件。如果附加到program的着色器对象中有类型为GL_GEOMETRY_SHADER的着色器,它们将用于创建一个可在可编程几何处理器上运行的可执行文件。如果附加到program的着色器对象中有类型为GL_FRAGMENT_SHADER的着色器,它们将用于创建一个可在可编程片段处理器上运行的可执行文件。(链接失败的一些因素)
 
链接操作的状态将作为程序对象状态的一部分存储。如果程序对象在没有错误的情况下链接并且可以使用,该值将设置为GL_TRUE,否则为GL_FALSE。您可以通过调用glGetProgram并传递programGL_LINK_STATUS参数来查询此值。
 
成功链接操作的结果是,属于program的所有活动用户定义的统一变量将被初始化为0,并且程序对象的每个活动统一变量将被分配一个可以通过调用glGetUniformLocation查询的位置。此外,任何未绑定到通用顶点属性索引的活动用户定义的属性变量也将在此时绑定到一个属性索引上。
 
glLinkProgram 函数用于链接已经成功附加到程序对象的着色器,将它们组合成一个完整的着色器程序。以下是关于 glLinkProgram 的详细描述:

  • 链接成功后的操作
  • 如果链接操作成功,程序对象将成为当前状态的一部分,可以通过调用 glUseProgram 来激活它。
  • 无论链接操作是否成功,程序对象的信息日志都将被覆盖。可以通过调用 glGetProgramInfoLog 来检索信息日志。
  • 可执行文件的安装
  • 如果链接操作成功,并且指定的程序对象已经在之前的 glUseProgram 调用中作为当前使用的程序对象,glLinkProgram 还会将生成的可执行文件安装为当前渲染状态的一部分。
  • 如果当前使用的程序对象重新链接失败,其链接状态将设置为 GL_FALSE,但可执行文件和相关状态将保留为当前状态,直到后续的 glUseProgram 调用将其从使用中移除。在移除使用后,只有在成功重新链接后,它才能再次成为当前状态的一部分。
  • 着色器类型的影响
  • 如果程序包含类型为 GL_VERTEX_SHADER 的着色器对象,以及可选的 GL_GEOMETRY_SHADER 类型的着色器对象,但不包含类型为 GL_FRAGMENT_SHADER 的着色器对象,则顶点着色器可执行文件将安装在可编程顶点处理器上,几何着色器可执行文件(如果存在)将安装在可编程几何处理器上,但片段处理器上不会安装任何可执行文件。使用此类程序对原语进行光栅化的结果将是未定义的。
  • 信息日志和程序生成
  • 链接操作时,程序对象的信息日志将被更新,程序将被生成。
  • 链接操作后,应用程序可以自由修改附加的着色器对象、编译附加的着色器对象、分离着色器对象、删除着色器对象以及附加其他着色器对象。这些操作都不会影响信息日志或程序对象的一部分。

glUseProgram — 安装程序对象作为当前渲染状态的一部分
void glUseProgram(GLuint program);
参数:
program - 表示要使用的程序对象的句柄,这些程序对象的可执行文件将成为当前渲染状态的一部分。这个函数的作用是将指定的程序对象安装到渲染管线中,以便在渲染时使用。
 
glUseProgram函数用于将由program指定的程序对象作为当前渲染状态的一部分安装。通过使用glAttachShader成功附加着色器对象、使用glCompileShader成功编译着色器对象以及使用glLinkProgram成功链接程序对象,可以在程序对象中创建一个或多个可执行文件。
 
如果程序对象包含一个或多个已成功编译和链接的类型为GL_VERTEX_SHADER的着色器对象,则该程序对象将包含一个在顶点处理器上运行的可执行文件。如果程序对象包含一个或多个已成功编译和链接的类型为GL_GEOMETRY_SHADER的着色器对象,则该程序对象将包含一个在几何处理器上运行的可执行文件。同样,如果程序对象包含一个或多个已成功编译和链接的类型为GL_FRAGMENT_SHADER的着色器对象,则该程序对象将包含一个在片段处理器上运行的可执行文件。
 
在使用程序对象时,应用程序可以自由修改附加的着色器对象、编译附加的着色器对象、附加其他着色器对象以及分离或删除着色器对象。这些操作都不会影响当前状态中的可执行文件。然而,如果当前正在使用的程序对象重新链接成功(请参阅glLinkProgram),则重新链接该程序对象将将其作为当前渲染状态的一部分安装。如果当前正在使用的程序对象重新链接失败,其链接状态将设置为GL_FALSE,但可执行文件和相关状态将保留为当前状态的一部分,直到随后的glUseProgram调用将其从使用中移除。在移除使用后,只有在成功重新链接后,它才能成为当前状态的一部分。
 
如果program为零,则当前渲染状态将引用一个无效的程序对象,着色器执行的结果是未定义的。然而,这不是一个错误。
 
如果program不包含类型为GL_FRAGMENT_SHADER的着色器对象,则将在顶点和可能的几何处理器上安装一个可执行文件,但片段着色器执行的结果将是未定义的。

glGenVertexArrays — 生成顶点数组对象名称
void glGenVertexArrays(GLsizei n, GLuint* arrays);

  • n:指定要生成的顶点数组对象名称的数量。
  • arrays:指定一个数组,用于存储生成的顶点数组对象名称。

 
VAO是一个OpenGL对象,它存储了提供顶点数据所需的所有状态,包括顶点数据的格式以及提供实际顶点数据数组的缓冲对象(VBO)。您可以将VAO视为一个容器,其中包含了高效渲染顶点所需的一切。
 
glGenVertexArrays函数返回n个顶点数组对象名称,并将其存储在arrays中。不能保证这些名称形成连续的整数集合;但是,可以确保在调用glGenVertexArrays之前,没有任何返回的名称被使用过。
 
通过调用glGenVertexArrays返回的顶点数组对象名称不会在随后的调用中再次返回,除非首先使用glDeleteVertexArrays将其删除。
 
arrays中返回的名称仅在glGenVertexArrays的目的下标记为已使用,但只有在首次绑定时才会获取状态和类型。

glBindVertexArray — 绑定顶点数组对象
void glBindVertexArray(GLuint array);
参数:
array - 指定要绑定的顶点数组的名称。
glBindVertexArray将名称为array的顶点数组对象绑定到当前上下文。array是之前通过调用glGenVertexArrays返回的顶点数组对象的名称,或者为零以解除现有顶点数组对象的绑定。
 
如果不存在名称为array的顶点数组对象,则在首次绑定时将创建一个。如果绑定成功,不会对顶点数组对象的状态进行更改,并且任何先前的顶点数组对象绑定都会被解除。

glPointSize — 指定光栅化点的直径
void glPointSize(GLfloat size);
参数:
size - 指定栅格化点的直径。初始值为1。
glPointSize 指定了点的光栅化直径。如果点大小模式被禁用(参见 glEnable 参数 GL_PROGRAM_POINT_SIZE),则将使用此值来光栅化点。否则,将使用写入到着色语言内置变量 gl_PointSize 的值。需要注意的是,如果启用了点的抗锯齿,那么点的大小可能会有不同的效果。

glDrawArrays — 从数组数据渲染图元
void glDrawArrays(GLenum mode, GLint first, GLsizei count);
参数:
mode - 指定要渲染的图元类型。符号常量 GL_POINTS 、 GL_LINE_STRIP 、 GL_LINE_LOOP 、 GL_LINES 、 GL_LINE_STRIP_ADJACENCY 、 GL_LINES_ADJACENCY 、接受 GL_TRIANGLE_STRIP 、 GL_TRIANGLE_FAN 、 GL_TRIANGLES 、 GL_TRIANGLE_STRIP_ADJACENCY 、 GL_TRIANGLES_ADJACENCY 和 GL_PATCHES 。
first - 指定启用数组中的起始索引。
count - 指定要渲染的索引数。
 
glDrawArrays 指定了多个几何图元,只需很少的子程序调用。与调用GL过程以传递每个单独的顶点、法线、纹理坐标、边缘标志或颜色不同,您可以预先指定顶点、法线和颜色的单独数组,并使用它们构建一系列图元,只需一次调用 glDrawArrays
 
当调用 glDrawArrays 时,它从每个启用的数组中使用连续的 count 元素来构建一系列几何图元,从 first 元素开始。mode 指定了构建哪种类型的图元以及数组元素如何构建这些图元。
 
glDrawArrays 修改的顶点属性在 glDrawArrays 返回后具有未指定的值。未被修改的属性仍然保持良好定义。

◍API (3)

gl_Position — 包含当前顶点的位置
gl_Position 是 gl_PerVertex 命名块的成员:

out gl_PerVertex {
   vec4 gl_Position;
   float gl_PointSize;
   float gl_ClipDistance[];
};

描述:
在顶点、细分评估和几何语言中,gl_PerVertex命名块的单个全局实例可用,其gl_Position成员是一个输出,用于接收齐次顶点位置。它可以在着色器执行期间的任何时候写入。写入gl_Position的值将由基元装配、裁剪、剔除和其他固定功能操作(如果存在)使用,这些操作在顶点处理后对基元进行操作。
在细分控制语言中,gl_PerVertex命名块用于构造一个数组gl_out[],其gl_Position成员保存齐次控制点位置,这些位置成为后续细分评估着色器的输入。
在顶点、细分控制和细分评估着色器阶段后,如果相应的着色器可执行文件没有写入gl_Position,则gl_Position的值(或在细分控制着色器中的gl_out[]数组的gl_Position成员)未定义。如果几何着色器可执行文件在上次调用EmitVertex时没有写入gl_Position(或根本没有写入),则在几何处理阶段后也未定义gl_Position的值。
在细分控制、细分评估和几何语言中,gl_PerVertex命名块用于构造一个数组gl_in[],其中包含每个顶点或每个控制点输入的内容,其内容表示前一阶段写入的相应输出。

在加载顶点之前, C++/OpenGL 应用程序必须编译并链接合适的 GLSL 顶点着色器和片段着色器程序,之后将它们载入管线。

当调用 glDrawArrays()时, 管线中的 GLSL 代码开始执行。 现在可以向管线添加一些 GLSL 代码了。不管它们从何处读入,所有的顶点都会被传入顶点着色器。顶点们会被逐个处理,即着色器会对每个顶点执行一次。对拥有很多顶点的大型复杂模型而言,顶点着色器会执行成百上千甚至上百万次,这些执行过程通常是并行的。

display()函数所做的事情中包含调用glUseProgram(),用于将含有两个已编译着色器的程序载入 OpenGL 管线阶段(在 GPU 上! )。注意, glUseProgram()并没有运行着色器,它只是将着色器加载进硬件。

当准备将数据集发送给管线时,数据集是以缓冲区形式发送的。 这些缓冲区最后都会被存入顶点数组对象( Vertex Array Object, VAO)中。在本例中,我们向顶点着色器中硬编码了一个点,因此不需要任何缓冲区。但是,即使应用程序完全没有用到任何缓冲区, OpenGL 仍然需要在使用着色器的时候拥有至少一个创建好的VAO,所以这两行代码用来创建 OpenGL 要求的 VAO。

在顶点着色器,给 gl_Position 指定 out 标签不是必需的,因为 gl_Position 是预定义的输出变量。(GLSL内置类型,见:Built-in Variable (GLSL))
当准备将数据集发送给管线时,数据集是以缓冲区形式发送的。这些缓冲区最后都会被存入顶点数组对象( Vertex Array Object,VAO)中。

最后的问题就是从顶点着色器出来的顶点是如何变成片段着色器中的像素的。回忆一下,在顶点处理和像素处理中间存在着栅格化阶段。正是在这个阶段,图元(点或三角形)转换成了像素的集合。

栅格化

最终,我们 3D 世界中的点、三角形、颜色等全都需要展现在一个 2D 显示器上。这个 2D屏幕由栅格(即矩形像素阵列)组成。

当 3D 物体栅格化后, OpenGL 会将物体中的图元(通常是三角形)转化为片段。片段拥有关于像素的信息。 栅格化过程确定了为了显示由 3 个顶点确定的三角形需要绘制的所有像素的位置。

栅格化过程开始时,先对三角形的每对顶点进行插值。插值过程可以通过选项调节,就目前而言,使用下图所示的简单的线性插值就够了。原本的 3 个顶点被标记为红色。
✺ch2——OpenGL图像管线_第8张图片
如果我们不加入之前的那一行代码(或者配置时用 GL_FILL 而非 GL_LINE),插值过程将会继续沿着栅格线填充三角形的内部,如下图所示。
✺ch2——OpenGL图像管线_第9张图片
栅格化不仅可以对像素插值,任何顶点着色器输出的变量和片段着色器的输入变量都可以基于对应的像素进行插值。

像素操作——Z-buffer算法

当我们在 display()中使用 glDrawArrays()命令绘制场景中的物体时,我们通常期望前面的物体挡住后面的物体。这也可以推广到物体自身,我们通常期望看到物体的正对我们,而不是背对我们。

为了实现这个效果,我们需要执行隐藏面消除(Hidden Surface Removal,HSR)操作。基于场景需要, OpenGL 可以进行一系列不同的 HSR 操作。虽然这个阶段不可编程,但是理解它的工作原理也是非常重要的。我们不仅需要正确地配置它,之后还需要在给场景添加阴影时对它进行进一步操作。

OpenGL 可以精巧地协调两个缓冲区,即颜色缓冲区深度缓冲区(也叫作 Z 缓冲区、Z-buffer),从而完成隐藏面消除。这两个缓冲区都和栅格的大小相同——对于屏幕上每个像素,在两个缓冲区都各有对应条目。

当绘制场景中的各种对象时, 片段着色器会生成像素颜色。像素颜色会存放在颜色缓冲区中,而最终颜色缓冲区会被写入屏幕。当多个对象占据颜色缓冲区中的相同像素时,必须根据最接近观察者的对象来确定要保留的像素颜色。

隐藏面消除按照如下步骤完成:
(1)在每个场景渲染前,将深度缓冲区全部初始化为表示最大深度的值。
(2)当片段着色器输出像素颜色时,计算它到观察者的距离。
(3)如果(对于当前像素)距离小于深度缓冲区存储的值,那么用当前像素颜色替换颜色缓冲区中的颜色,同时用当前距离替换深度缓冲区中的值;否则,抛弃当前像素。

这个过程即 Z-buffer 算法,其伪代码如下所示:

Color[][] colorBuf = new Color[pixelRows][pixelCols];// 初始化pixelRows行pixelCols列的二维数组(存像素颜色值)
double[][] depthBuf = new double[pixelRows][pixedCols];// 初始化pixelRows行pixelCols列的二维数组(存像素深度值)
for (each row and column) {// 初始化颜色和深度缓冲区
	colorBuf[row][column] = backgroundColor;// 初始化所有像素的颜色值为背景颜色(深度值为“far away"处的颜色)
	depthBuf[row][column] = far away;// 初始化所有像素的深度值为“far away”,即为远剪裁平面对应的值——1
}
for (each shape) {
	// 当 shape 中某一个像素,比深度缓冲区中的像素,离近剪裁平面更近(也就是depth值更小)时;同时更新【两个】缓冲区
	for (each pixel in the shape) {
		if (depth at pixel < depthBuf value) {// shape中的像素深度值 小于 depthBuf中的深度值
			depthBuf[pixel.row][pixel.col] = depth at pixel;// 替换[深度缓冲区]中位置[row][col]处的值
			colorBuf[pixel.row][pixel.col] = color at pixel;// 替换[颜色缓冲区]中位置[row][col]处的值
		}
	}
}
// 绘制时,只用到颜色缓冲区(帧缓冲区),所以把它return使用。
return colorBuf;

检测 OpenGL 和 GLSL 错误

编译和运行 GLSL 代码的过程与普通代码的不同, GLSL 的编译发生在 C++运行时。另外一个复杂的点是 GLSL 代码并没有运行在 CPU 中(它运行在 GPU 中),因此操作系统并不总能捕获 OpenGL 运行时的错误。以上这两点使调试变得很困难,因为常常很难判断着色器的运行是否失败,以及为什么失败。

◍API (4)

glGetShaderiv — 从着色器对象返回参数
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);
参数:
shader - 指定要查询的着色器对象。
pname - 指定对象参数。可接受的符号名称为 GL_SHADER_TYPE 、 GL_DELETE_STATUS 、 GL_COMPILE_STATUS 、 GL_INFO_LOG_LENGTH 、 GL_SHADER_SOURCE_LENGTH 。
params - 返回请求的对象参数。
描述:
glGetShader 在 params 中返回特定着色器对象的参数值。定义了以下参数:

  • GL_SHADER_TYPE
    如果 shader 是顶点着色器对象,则 params 返回 GL_VERTEX_SHADER ;如果 shader 是几何着色器对象,则返回 GL_GEOMETRY_SHADER ,如果 shader 是片段着色器对象,则为 GL_FRAGMENT_SHADER 。
  • GL_DELETE_STATUS
    如果 shader 当前被标记为删除,则 params 返回 GL_TRUE ,否则返回 GL_FALSE 。
  • GL_COMPILE_STATUS
    如果 shader 上的最后一次编译操作成功,则 params 返回 GL_TRUE ,否则返回 GL_FALSE 。
  • GL_INFO_LOG_LENGTH
    params 返回 shader 信息日志中的字符数,包括空终止字符(即存储信息日志所需的字符缓冲区的大小)。如果 shader 没有信息日志,则返回值0。
  • GL_SHADER_SOURCE_LENGTH
    params 返回构成 shader 着色器源的源字符串的串联长度,包括空终止字符。 (即存储着色器源所需的字符缓冲区的大小)。如果不存在源代码,则返回 0。

 
笔记:
如果生成错误,则不会更改 params 的内容。
错误:

  • 如果 shader 不是 OpenGL 生成的值,则生成 GL_INVALID_VALUE 。
  • 如果 shader 不引用着色器对象,则会生成 GL_INVALID_OPERATION 。
  • 如果 pname 不是可接受的值,则生成 GL_INVALID_ENUM 。

glGetProgramiv — 从程序对象返回参数
void glGetProgramiv(GLuint program, GLenum pname, GLint *params);
描述:
glGetProgram 在 params 中返回特定程序对象的参数值。定义了以下参数:

  • GL_DELETE_STATUS
    如果 program 当前被标记为删除,则 params 返回 GL_TRUE ,否则返回 GL_FALSE 。
  • GL_LINK_STATUS
    如果 program 上的最后一个链接操作成功,则 params 返回 GL_TRUE ,否则返回 GL_FALSE 。
  • GL_VALIDATE_STATUS
    params 返回 GL_TRUE ,或者如果 program 上的最后一次验证操作成功,则返回 GL_FALSE 。
  • GL_INFO_LOG_LENGTH
    params 返回 program 信息日志中的字符数,包括空终止字符(即存储信息日志所需的字符缓冲区的大小)。如果 program 没有信息日志,则返回值0。
  • GL_ATTACHED_SHADERS
    params 返回附加到 program 的着色器对象的数量。
  • … …

… …

glGetShaderInfoLog — 返回着色器对象的信息日志
void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
参数:
shader - 指定要查询信息日志的着色器对象。
maxLength - 指定存储返回信息日志的字符缓冲区的大小。
length - 返回 infoLog 中返回的字符串的长度(不包括空终止符)。
infoLog - 指定用于返回信息日志的字符数组。
描述:
glGetShaderInfoLog 返回指定着色器对象的信息日志。编译着色器时,会修改着色器对象的信息日志。返回的字符串将以 null 结尾。
glGetShaderInfoLoginfoLog 中返回尽可能多的信息日志,最多可达 maxLength 个字符。实际返回的字符数(不包括空终止字符)由 length 指定。如果不需要返回字符串的长度,则可以在 length 参数中传递 NULL 值。存储返回信息日志所需的缓冲区大小可以通过调用 glGetShader 获取,值为 GL_INFO_LOG_LENGTH 。
着色器对象的信息日志是一个字符串,其中可能包含诊断消息、警告消息以及有关上次编译操作的其他信息。当一个 shader 对象被创建时,它的信息日志将是一个长度为0的字符串。
笔记:
着色器对象的信息日志是 OpenGL 实现者传达有关编译过程的信息的主要机制。因此,信息日志可以在开发过程中为应用程序开发人员提供帮助,即使编译成功。应用程序开发人员不应期望不同的 OpenGL 实现会生成相同的信息日志。

glGetError — 返回错误信息
GLenum glGetError(void);
描述:
glGetError 返回错误标志的值。每个可检测的错误都分配有一个数字代码和符号名称。当错误发生时,错误标志被设置为适当的错误代码值。在调用 glGetError、返回错误代码并将标志重置为 GL_NO_ERROR 之前,不会记录其他错误。如果对 glGetError 的调用返回 GL_NO_ERROR ,则自上次调用 glGetError 或初始化 GL 以来没有可检测到的错误。
为了允许分布式实现,可能有多个错误标志。如果任何单个错误标志记录了错误,则返回该标志的值,并且在调用 glGetError 时将该标志重置为 GL_NO_ERROR 。如果多个标志记录了错误, glGetError 返回并清除任意错误标志值。因此,如果要重置所有错误标志,则应始终在循环中调用 glGetError ,直到返回 GL_NO_ERROR 为止。

GLenum err;
while((err = glGetError()) != GL_NO_ERROR) {
    // Process/log the error.
}

最初,所有错误标志都设置为 GL_NO_ERROR 。
当前定义了以下错误:

  • GL_NO_ERROR
    没有记录任何错误。该符号常量的值保证为 0。
  • GL_INVALID_ENUM
    为枚举参数指定了不可接受的值。有问题的命令将被忽略,并且除了设置错误标志之外没有其他副作用。
  • GL_INVALID_VALUE
    数字参数超出范围。有问题的命令将被忽略,并且除了设置错误标志之外没有其他副作用。
  • GL_INVALID_OPERATION
    当前状态不允许指定的操作。有问题的命令将被忽略,并且除了设置错误标志之外没有其他副作用。
  • GL_INVALID_FRAMEBUFFER_OPERATION
    帧缓冲区对象不完整。有问题的命令将被忽略,并且除了设置错误标志之外没有其他副作用。
  • GL_OUT_OF_MEMORY
    没有足够的内存来执行该命令。记录此错误后,除了错误标志的状态外,GL 的状态未定义。
  • GL_STACK_UNDERFLOW
    尝试执行会导致内部堆栈下溢的操作。
  • GL_STACK_OVERFLOW
    尝试执行会导致内部堆栈溢出的操作。
     
    当设置错误标志时,仅当 GL_OUT_OF_MEMORY 发生时,GL操作的结果才是未定义的。在所有其他情况下,生成错误的命令将被忽略,并且对 GL 状态或帧缓冲区内容没有影响。如果生成命令返回一个值,则返回0。如果 glGetError 本身生成错误,则返回0。

下面是添加了『捕获OpenGL和GLSL异常』的完整程序代码:

#include 
#include 
#include 
using namespace std;
#define numVAOs 1
GLuint renderingProgram;
GLuint vao[numVAOs];

/**
 * ➊当 GLSL 代码【编译】失败时,显示 OpenGL 日志内容。
 */
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 << "Shader Info Log: " << log << endl;
		free(log);
	}
}

/**
 * ➋当 GLSL 【链接】失败时,显示 OpenGL 日志内容。
 */
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 << "Program Info Log: " << log << endl;
		free(log);
	}
}

/**
 * ➌检查 OpenGL 错误标志,即是否发生 OpenGL 错误(编译 + 运行时)。
 */
bool checkOpenGLError() {
	bool foundError = false;
	GLenum glErr;
	while ((glErr = glGetError()) != GL_NO_ERROR) {
		cout << "glError: " << glErr << endl;
		foundError = true;
	}
	return foundError;
}

GLuint createShaderProgram() {
	GLint vertCompiled;
	GLint fragCompiled;
	GLint linked;
	const char *vshaderSource =
		"#version 430    \n"
		"void main(void) \n"
		"{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";
	const char *fshaderSource =
		"#version 430    \n"
		"out vec4 color; \n"
		"void main(void) \n"
		"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";
	GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
	GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
	GLuint vfprogram = glCreateProgram();
	glShaderSource(vShader, 1, &vshaderSource, NULL);
	glShaderSource(fShader, 1, &fshaderSource, NULL);
	// catch errors while compiling shaders
	glCompileShader(vShader);
	checkOpenGLError();// GL compile✚✚✚✚✚✚✚✚✚✚✚✚
	glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
	if (vertCompiled != GL_TRUE) {
		cout << "vertex compilation failed" << endl;
		printShaderLog(vShader);// GLSL compile✚✚✚✚✚✚✚✚✚✚✚✚
	}
	glCompileShader(fShader);
	checkOpenGLError();// GL compile✚✚✚✚✚✚✚✚✚✚✚✚
	glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
	if (fragCompiled != GL_TRUE) {
		cout << "fragment compilation failed" << endl;
		printShaderLog(fShader);// GLSL compile✚✚✚✚✚✚✚✚✚✚✚✚
	}
	// catch errors while linking shaders
	glAttachShader(vfprogram, vShader);
	glAttachShader(vfprogram, fShader);
	glLinkProgram(vfprogram);
	checkOpenGLError();// GL link✚✚✚✚✚✚✚✚✚✚✚✚
	glGetProgramiv(vfprogram, GL_LINK_STATUS, &linked);
	if (linked != GL_TRUE) {
		cout << "linking failed" << endl;
		printProgramLog(vfprogram);// GLSL link✚✚✚✚✚✚✚✚✚✚✚✚
	}
	return vfprogram;
}

void init(GLFWwindow* window) {
	renderingProgram = createShaderProgram();
	glGenVertexArrays(numVAOs, vao);
	glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
	glUseProgram(renderingProgram);
	glPointSize(30.0f);
	glDrawArrays(GL_POINTS, 0, 1);
}

int main(void) {
	if (!glfwInit()) { exit(EXIT_FAILURE); }
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter 2 - program 3", NULL, NULL);
	glfwMakeContextCurrent(window);
	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);
}

从顶点来构建一个三角形

在 C++/OpenGL 应用程序中(特别是在 glDrawArrays()调用中)我们指定 GL_TRIANGLES(而非
GL_POINTS),同时指定管线中有 3 个顶点。这样顶点着色器会在每个迭代运行 3 遍,内置变量
gl_VertexID 会自增(初始值为 0)。通过检测 gl_VertexID 的值,着色器可以在每次运行时输出不
同的点。前面说到,这 3 个顶点会经过栅格化阶段,生成一个填充过的三角形。

// 顶点着色器
#version 430
void main(void) {
    if (gl_VertexID == 0) gl_Position = vec4( 0.25, -0.25, 0.0, 1.0);
	else if (gl_VertexID == 1) gl_Position = vec4(-0.25, -0.25, 0.0, 1.0);
	else gl_Position = vec4( 0.25, 0.25, 0.0, 1.0);
}
// C++/OpenGL 应用程序——在 display()函数中
...
glDrawArrays(GL_TRIANGLES, 0, 3);

✺ch2——OpenGL图像管线_第10张图片

场景动画

我们构建的main()函数只调用了init()一次,之后就重复调用display()。因此虽然前面所有的例子看起来都是静态绘制的场景,但实际上main()函数中的循环会让它们一次又一次地绘制。
因此,main()函数的结构已经可以支持动画了。我们只需要设计display()函数来随时间改变要绘制的内容。
场景的每一次绘制都叫作一,调用display()的频率叫作帧率

#define numVAOs 1
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint offsetLoc;
float x = 0.0f;// location of triangle on x axis
float inc = 0.01f;// offset for moving the triangle

void init(GLFWwindow* window) {
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	glGenVertexArrays(numVAOs, vao);
	glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
	glClear(GL_DEPTH_BUFFER_BIT);
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);// clear the background to black, each time

	glUseProgram(renderingProgram);

	x += inc;// move the triangle along x axis
	if (x > 1.0f) inc = -0.01f;// switch to moving the triangle to the left
	if (x < -1.0f) inc = 0.01f;// switch to moving the triangle to the right
	offsetLoc = glGetUniformLocation(renderingProgram, "offset");// get ptr to "offset"
	glProgramUniform1f(renderingProgram, offsetLoc, x);// send value in "x" to "offset"
	glDrawArrays(GL_TRIANGLES, 0, 3);
}

顶点着色器代码如下:(片段着色器不变)

#version 430
uniform float offset;
void main(void) {
    if (gl_VertexID == 0)
        gl_Position = vec4(0.25 + offset, -0.25, 0.0, 1.0);
    else if (gl_VertexID == 1)
        gl_Position = vec4(-0.25 + offset, -0.25, 0.0, 1.0);
    else
        gl_Position = vec4(0.25 + offset, 0.25, 0.0, 1.0);
}

✺ch2——OpenGL图像管线_第11张图片

◍API (5)

glGetUniformLocation — 返回统一变量的位置
GLint glGetUniformLocation(GLuint program, const GLchar *name);
参数:
program - 指定要查询的程序对象。
name - 指向一个以 null 结尾的字符串,其中包含要查询其位置的统一变量的名称。
描述:
glGetUniformLocation 返回一个整数,表示程序对象中特定统一变量的位置。name 必须是一个以空字符结尾的字符串,不得包含空格。name 必须是程序中的一个活动统一变量名称,且不能是结构、结构数组或向量或矩阵的子组件。如果 name 不对应于程序中的活动统一变量,或者 name 以保留前缀 “gl_” 开头,或者 name 与原子计数器或命名统一块相关联,则此函数返回 -1。
 
结构或结构数组中的统一变量可以通过为结构中的每个字段调用 glGetUniformLocation 来查询。在 name 中,可以使用数组元素运算符 “[]” 和结构字段运算符 “.” 来选择数组内的元素或结构内的字段。使用这些运算符的结果不允许是另一个结构、结构数组或向量或矩阵的子组件。除非 name 的最后部分指示了统一变量数组,否则可以通过使用数组的名称或附加 “[0]” 的名称来检索数组的第一个元素的位置。
 
实际分配给统一变量的位置直到程序对象成功链接后才知道。链接完成后,可以使用 glGetUniformLocation 命令获取统一变量的位置。然后,可以将此位置值传递给 glUniform 来设置统一变量的值,或者传递给 glGetUniform 以查询统一变量的当前值。成功链接后,统一变量的索引值保持不变,直到下一次链接命令发生。只有在链接成功后,才能查询统一变量的位置和值。

glProgramUniform — 为指定程序对象指定统一变量的值
两种形式:

  1. glProgramUniform{1|2|3|4}{f|i|ui}{v}
  2. glProgramUniformMatrix{2|3|4|2x3|3x2|2x4|4x2|3x4|4x3}fv

 
本例中:
void glProgramUniform1f(GLuint program, GLint location, GLfloat v0);
其他形式,如:
void glProgramUniform1fv(GLuint program, GLint location, GLsizei count, const GLfloat *value);
void glProgramUniformMatrix2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
 
参数:

  • program:指定包含要修改的统一变量的程序的句柄。
  • location:指定要修改的统一变量的位置。
  • count:对于矢量命令(glProgramUniform*v),指定要修改的元素数量。如果目标统一变量不是数组,则应为1;如果是数组,则应为1或更多。
  • 对于矩阵命令(glProgramUniformMatrix*),指定要修改的矩阵数量。如果目标统一变量不是矩阵数组,则应为1;如果是矩阵数组,则应为1或更多。
  • transpose:对于矩阵命令,指定在将值加载到统一变量时是否转置矩阵。
  • v0v1v2v3:对于标量命令,指定要用于指定统一变量的新值。
  • value:对于矢量和矩阵命令,指定一个指向包含要用于更新指定统一变量的值的数组的指针。

 
描述:
glProgramUniform 修改统一变量或统一变量数组的值。要修改的统一变量的位置由 location 指定,它应该是由 glGetUniformLocation 返回的值。glProgramUniform 操作的是由 program 指定的程序对象。
 
命令 glProgramUniform{1|2|3|4}{f|i|ui} 用于使用传递的参数值更改由 location 指定的统一变量的值。命令中指定的数字应与指定统一变量的数据类型中的组件数匹配(例如,对于 float、int、unsigned int、bool,应为 1;对于 vec2、ivec2、uvec2、bvec2 等,应为 2)。后缀 f 表示传递的是浮点数值;后缀 i 表示传递的是整数值;后缀 ui 表示传递的是无符号整数值,且此类型还应与指定统一变量的数据类型匹配。此函数的 i 变体应用于为定义为 int、ivec2、ivec3、ivec4 或这些类型数组的统一变量提供值。此函数的 ui 变体应用于为定义为 unsigned int、uvec2、uvec3、uvec4 或这些类型数组的统一变量提供值。f 变体应用于为类型为 float、vec2、vec3、vec4 或这些类型数组的统一变量提供值。iuif 变体都可以用于为类型为 bool、bvec2、bvec3、bvec4 或这些类型数组的统一变量提供值。如果输入值为 0 或 0.0f,则统一变量将设置为 false;否则将设置为 true。
 
成功链接程序对象时,所有活动的在程序对象中定义的统一变量都会被初始化为 0。它们会保留由 glProgramUniform 调用分配给它们的值,直到程序对象上发生下一次成功的链接操作,然后它们将再次初始化为 0。
 
命令 glProgramUniform{1|2|3|4}{f|i|ui}v 可用于修改单个统一变量或统一变量数组。这些命令传递一个计数和一个指向要加载到统一变量或统一变量数组中的值的指针。如果修改单个统一变量的值,应使用计数1;如果要修改整个数组或数组的一部分,可以使用计数1或更大的计数。当在统一变量数组中从任意位置 m 开始加载 n 个元素时,数组中的元素 m + n - 1 将被新值替换。如果 m + n - 1 大于统一变量数组的大小,则数组末尾之外的所有数组元素的值将被忽略。命令名称中指定的数字表示 value 中每个元素的组件数量,它应与指定统一变量的数据类型中的组件数量相匹配(例如,1 对应于 float、int、bool;2 对应于 vec2、ivec2、bvec2 等)。命令名称中指定的数据类型必须与之前为 glProgramUniform{1|2|3|4}{f|i|ui} 描述的指定统一变量的数据类型匹配。
 
对于统一变量数组,数组的每个元素都被视为命令名称中指示的类型(例如,glProgramUniform3fglProgramUniform3fv 可用于加载类型为 vec3 的统一变量数组)。要修改的统一变量数组的元素数量由 count 指定。
 
命令 glProgramUniformMatrix{2|3|4|2x3|3x2|2x4|4x2|3x4|4x3}fv 用于修改矩阵或矩阵数组。命令名称中的数字表示矩阵的维度。数字 2 表示一个 2 × 2 矩阵(即 4 个值),数字 3 表示一个 3 × 3 矩阵(即 9 个值),数字 4 表示一个 4 × 4 矩阵(即 16 个值)。非方阵的维度是明确的,第一个数字代表列数,第二个数字代表行数。例如,2x4 表示一个 2 列 4 行的 2 × 4 矩阵(即 8 个值)。如果 transpose 是 GL_FALSE,每个矩阵都假定以列主序提供。如果 transpose 是 GL_TRUE,每个矩阵都假定以行主序提供。count 参数表示要传递的矩阵数量。如果修改单个矩阵的值,应使用计数 1;如果要修改矩阵数组,可以使用大于 1 的计数。

OpenGL某些方面的数值——glGet()

你可能多次想要了解 OpenGL 某些方面的数值限制。例如,程序员可能需要知道几何着色器可以生成的最大输出数,或者可以为渲染点指定的最大尺寸。这些值中很多都依赖于实现, 即在不同的计算机上是不同的。 OpenGL 提供了使用 glGet()命令来获取这些值的机制。基于查询的参数的不同类型, glGet()命令也有着不同的形式。例如,查询点的尺寸的最大值时,如下调用会将最小值和最大值(基于计算机上的 OpenGL 实现)放入名为 size 的数组,作为前两个元素。

int sizeA;
float sizeB[2];
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &sizeA);
glGetFloatv(GL_POINT_SIZE_RANGE, sizeB);
cout << "MAX_TEXTURE_IMAGE_UNITS = " << sizeA << endl;
cout << "POINT_SIZE_RANGE = " << sizeB[0] << " ~ " << sizeB[1] << endl;
/****** 某机器的输出 ******/
MAX_TEXTURE_IMAGE_UNITS = 32
POINT_SIZE_RANGE = 1 ~ 63.375

你可能感兴趣的:(#,第二版》,c++,OpenGL)