openGL管线

绝大多数openGL实现都有着相似的操作顺序,这一系列相关的处理阶段称为openGL渲染管线。我们可以把openGL看做是一些列机器的组合,它的输入是我们提供的数据,这些数据就像在流水线上一样依次被不同的机器进行处理,在一个执行完前一个阶段的操作后就被送入到后一个阶段进行下一次操作,最终成为在屏幕上显示的像素点。我们无法改变这些操作进行的顺序,只能通过openGL提供的API改变它的状态从而改变每个阶段对数据进行的操作来得到不同的效果。下图展示了openGL管线简化后的概览。其中,蓝色阴影的部分可以通过GLSL进行编程。

在openGL 3.1之前,openGL为我们提供了固定功能的管线,在openGL 3.1之后,固定功能的管线被废弃并删除了,我们至少需要提供顶点着色器和片段着色器的GLSL代码。

视图

模型变换

模型变换的目的是设置模型的位置和方向,模型上的点一般以该模型的局部坐标系为参考系,通过将模型变换矩阵$\pmb{M}$作用在这些点上从而实现对模型进行旋转、移动和缩放。

视图变换

视图变换相当于把照相机固定在三脚架上并将它对准场景。openGL有一个固定在原点并看向Z轴负方向的相机,为了应用相机,我们要做的是模拟将它模拟移动的合适的位置和方向。如果我们移动相机,首先需要将它在原点处旋转到合适的方向,然后再平移到对应的位置。但openGL中的相机永远固定在原点处,所以我们需要通过变换将世界坐标系中的点$P_w$转换成相机坐标系空间中的对应的点$P_c$。需要做的变换和上面描述的过程刚好相反,用矩阵描述如下:

通常,我们将视图矩阵$\pmb{V}$与模型矩阵$\pmb{M}$合成一个模型视图矩阵$MV=\pmb{V}\cdot \pmb{M}$,这样点$P_w$在自己的模型空间下可以一步转换至相机空间 $$ P_c=MVP_w $$ 复杂模型和场景通常是由简单的小模型分层组合构建的,我们通常只知道一个模型相对于另一个特定模型的位置,于是这形成了一种树形依赖关系,利用矩阵堆栈可以轻松完成这种分层构建的操作。

投影变换

投影变换就好像是为照相机选择镜头,它的目的是确定视野,并因此决定哪些物体位域视野之内以及他们能被看到的程度。除了考虑视野外,投影变换还决定了物体是如何投影到屏幕上的,一种类型是透视投影,它类似于我们在生活中看到的景象。另一种是正射投影,一般用于CAD软件中。

我们通过纵横比、视场、近剪裁平面(投影平面)和远剪裁平面这四个参数来生成透视投影的变换矩阵。处于远近剪裁平面之间的物体被投影到近剪裁平面上。

3D立方体程序

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
//main.cpp
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Utils.h"
using namespace std;

#define numVAOs 1
#define numVBOs 2

Utils util = Utils();
float cameraX, cameraY, cameraZ;
float cubeLocX, cubeLocY, cubeLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];

// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;

void setupVertices(void) {
	float vertexPositions[108] = {
		-1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
		1.0f, -1.0f, -1.0f, 1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,
		1.0f, -1.0f, -1.0f, 1.0f, -1.0f,  1.0f, 1.0f,  1.0f, -1.0f,
		1.0f, -1.0f,  1.0f, 1.0f,  1.0f,  1.0f, 1.0f,  1.0f, -1.0f,
		1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, 1.0f,  1.0f,  1.0f,
		-1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, 1.0f,  1.0f,  1.0f,
		-1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
		-1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
		-1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,
		1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,
		-1.0f,  1.0f, -1.0f, 1.0f,  1.0f, -1.0f, 1.0f,  1.0f,  1.0f,
		1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f, -1.0f
	};

	glGenVertexArrays(1, vao);
	glBindVertexArray(vao[0]);
	glGenBuffers(numVBOs, vbo);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//激活缓冲区
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    //将数据复制到缓冲区
}

void init(GLFWwindow* window) {
	renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
	cameraX = 0.0f; cameraY = 0.0f; cameraZ = 8.0f;
	cubeLocX = 0.0f; cubeLocY = -2.0f; cubeLocZ = 0.0f;
	setupVertices();
}

void display(GLFWwindow* window, double currentTime) {
	glClear(GL_DEPTH_BUFFER_BIT);//清除深度缓冲区
	glUseProgram(renderingProgram);
	//获取MV矩阵和投影矩阵的统一变量
	mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
	//构建透视投影矩阵
	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
	//构建视图矩阵
	vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
	mvMat = vMat * mMat;
	//将MV矩阵和透视投影矩阵复制给统一变量
	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//激活缓冲区
	glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);//将0号属性关联到缓冲区
	glEnableVertexAttribArray(0);//启用点的0号属性

	glEnable(GL_DEPTH_TEST);//开启深度检测
	glDepthFunc(GL_LEQUAL);

	glDrawArrays(GL_TRIANGLES, 0, 36);//以三角形方式绘制
}

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, "cubes", 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);
}

着色器代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//vertShader.glsl
#version 430

//关联顶点属性和特定缓冲区
layout (location=0) in vec3 position;//该顶点属性的识别号为0

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
out vec4 varyingColor;

void main(void){
	gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
	varyingColor = vec4(position,1.0)*0.5 + vec4(0.5, 0.5, 0.5, 0.5);
} 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//vertShader.glsl
#version 430
in vec4 varyingColor;
out vec4 color;

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;

void main(void){	
    color = varyingColor;
}

上面程序的数据流如下

在`setupVertices()`中,我们用由36个点组成的12个三角形来构建正方体,并将这些顶点坐标加载进`VBO`的的0个缓冲区中。

display()函数中,首先启用着色器,在GPU上安装GLSL代码。接下来获取统一变量的位置并构建MV矩阵然后发送给统一变量。接下来,它启用了了包晗正方体顶点坐标数据的缓冲区,并将其附加到顶点的0号属性,以准备将顶点数据发送到着色器。之后,它启用特定的深度测试仪消除隐藏面。最后,它通过glDrawArrays(GL_TRIANGLES, 0, 36)来制定模型由三角形组成且共有36个顶点

顶点着色器利用MV矩阵将传入的顶点转换到相机空间,这个值被放入内置变量gl_Postion中,然后继续通过管线并由光栅着色器进行插值。同时根据点的位置定义了它的颜色,由于颜色是从顶点着色器发出的,所以他们也在光栅化着色器中进行插值。同时,片段着色器接收传入的颜色(由光栅化着色器中插值)并用它来设置输出像素的颜色。

效果如下

实例化

当我们个渲染同一个对象的多个副本时,可以使用openGL的实例化绘制。使用实例化时,顶点着色器可以访问内置变量gl_InstanceID,它表示正在处理对象的第几个实例。为了使用实例化来绘制立方体,我们需要将构建不同模型矩阵的计算移到顶点着色器中。修改后的顶点着色器代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#version 430

layout (location=0) in vec3 position;

uniform mat4 v_matrix;
uniform mat4 proj_matrix;
uniform float tf;//时间因子

out vec4 varyingColor;

//声明矩阵变换工具函数
mat4 buildRotateX(float rad);
mat4 buildRotateY(float rad);
mat4 buildRotateZ(float rad);
mat4 buildTranslate(float x, float y, float z);

void main(void)
{	float x = gl_InstanceID + tf;
 
 	//对每个实例随机生成旋转和平移变换矩阵
	float a = sin(203.0 * x/8000.0) * 403.0;
	float b = cos(301.0 * x/4001.0) * 401.0;
	float c = sin(400.0 * x/6003.0) * 405.0;
	
	mat4 localRotX = buildRotateX(1.75*x);
	mat4 localRotY = buildRotateY(1.75*x);
	mat4 localRotZ = buildRotateZ(1.75*x);
	mat4 localTrans = buildTranslate(a,b,c);

	mat4 newM_matrix = localTrans * localRotX * localRotY * localRotZ;
	mat4 mv_matrix = v_matrix * newM_matrix;
	gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
	varyingColor = vec4(position,1.0)*0.5 + vec4(0.5, 0.5, 0.5, 0.5);
}

mat4 buildTranslate(float x, float y, float z)
{	mat4 trans = mat4(	1.0, 0.0, 0.0, 0.0,
		0.0, 1.0, 0.0, 0.0,
		0.0, 0.0, 1.0, 0.0,
		x, y, z, 1.0 );
	return trans;
}

//  rotation around the X axis
mat4 buildRotateX(float rad)
{	mat4 xrot = mat4(	1.0, 0.0, 0.0, 0.0,
		0.0, cos(rad), -sin(rad), 0.0,
		0.0, sin(rad), cos(rad), 0.0,
		0.0, 0.0, 0.0, 1.0 );
	return xrot;
}

//  rotation around the Y axis
mat4 buildRotateY(float rad)
{	mat4 yrot = mat4(	cos(rad), 0.0, sin(rad), 0.0,
		0.0, 1.0, 0.0, 0.0,
		-sin(rad), 0.0, cos(rad), 0.0,
		0.0, 0.0, 0.0, 1.0 );
	return yrot;
}

//  rotation around the Z axis
mat4 buildRotateZ(float rad)
{	mat4 zrot = mat4(	cos(rad), sin(rad), 0.0, 0.0,
		-sin(rad), cos(rad), 0.0, 0.0,
		0.0, 0.0, 1.0, 0.0,
		0.0, 0.0, 0.0, 1.0 );
	return zrot;
}

主程序中作如下修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void init(){
    //...
    cameraZ=500.0f;//拉远镜头以看到更多正方体
    //...
}
void dislay(){
	//...
	glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
	
	//得到并传递时间因子
	timeFactor = ((float)currentTime);
	tfLoc = glGetUniformLocation(renderingProgram, "tf");
	glUniform1f(tfLoc, (float)timeFactor);
	//...
	
	glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 100000);//绘制实例
}

最终效果如下: