计算机图形学编程(使用OpenGL和C++) 第4章 管理3D图形数据 笔记
数据处理
想要绘制一个对象,它的顶点数据需要发送给顶点着色器。通常会把顶点数据在C++端放入
一个缓冲区,并把这个缓冲区和着色器中声明的顶点属性相关联。
- 初始化立方体顶点数据:声明vertexPositions数组
我们需要用三角形来构建这个立方体,因此立方体的每一个面都需要由两个三角形构成,总共6×2=12个三角形(见图4.4)。由于每个三角形都由 3 个顶点指定,因此总共有36 个顶点。由于每个顶点具有 3 个值,因此数组中总共有
36×3=108 个值。
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};
-
加载方体顶点到VBO中:
在OpenGL中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object,VBO)中,VBO在C++/OpenGL 应用程序中被声明和实例化。一个场景可能需要很多VBO,所以我们常常会在init()中生成并填充若干个VBO,以备程序需要时直接使用。OpenGL 中另一个作顶点数组对象(Vertex Array Object,VAO)。OpenGL要求至少创建一个VAO
#define numVAOs 1
#define numVBOs 1GLuint vao[numVAOs];
GLuint vbo[numVBOs];glGenVertexArrays(1, vao);//创建 VAO 并返回它们的整数型ID,存进数组vao
glBindVertexArray(vao[0]);//将指定的VAO 标记为“活跃”,这样生成的缓冲区①就会和这个VAO相关联。glGenBuffers(numVBOs, vbo);//创建 VBO并返回它们的整数型ID,存进数组vbo
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//第0个将缓冲区标记为“活跃”;
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);//将包含顶点数据的数组复制进活跃缓冲区
- 写顶点着色器
#version 430layout (location=0) in vec3 position;
每个缓冲区需要有在顶点着色器中声明的相应顶点属性变量。顶点属性通常是着色器中首先声明的变量。
- 关键字in意思是“输入”(input),表示这个顶点属性将会从缓冲区中接收数值。
- vec3的意思是着色器的每次调用会抓到3个浮点类型数值(分别表示x轴、y轴、z轴坐标,它们组成一个顶点数据)。
- 变量的名字是position。
- layout (location=0)称为“layout 修饰符”,也就是我们把顶点属性和特定缓冲区关联起来的方法。这个顶点属性的识别号是0。
- 将缓冲区中的值发送到着色器中的顶点属性
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);// 标记第 0 个缓冲区为“活跃” glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); 将第 0 个属性关联到缓冲区glEnableVertexAttribArray(0); // 启用第 0 个顶点属性
当glDrawArrays()执行时,缓冲区中的数据开始流动,从缓冲区的开头开始,按顺序流过顶点着色器。像第 2 章中介绍的一样,顶点着色器对每个顶点执行一次。
MVP Matrix
MVP矩阵顾名思义,它是由M,V,P三个矩阵组够成的矩阵,其中M为模型矩阵,V为视图矩阵,P为投影矩阵。M矩阵用来将物体顶点在模型空间下的坐标转换为在世界空间下的坐标,V矩阵用来将物体顶点在世界空间下的坐标转换为视图空间下的坐标。P矩阵用来将物体顶点在视图空间的坐标转换为裁剪空间的坐标。M矩阵包括平移矩阵、旋转矩阵、缩放矩阵。V矩阵包括旋转矩阵,平移矩阵。P矩阵则包括正交投影矩阵和透视投影矩阵。
世界空间
以世界原点为原点建立的坐标系。
模型空间
模型空间是以模型某一点为原点建立坐标系而形成的空间
如图左边球,以球心为原点建立坐标系
由于世界空间和模型空间有两套坐标系,我们需要进行坐标系变换将物体放在世界坐标系下面:
模型矩阵描述的是 3D Point 的仿射变换,包含 Scale(缩放)、Rotate(旋转)、Translate(平移)。
可以按照下面的方式表示:
M = S c a l e × R o t a t e × T r a n s l a t e M = Scale \times Rotate \times Translate M=Scale×Rotate×Translate
最后进行 Translation 是为了保证前面的操作参考坐标轴不会变化。
OpenGL 是左乘的,因此编程时计算模型矩阵需要按照 Translate、Rotate、Scale 的顺序进行。
写的具体一点如下: [ x ^ y ^ z ^ 0 ] = [ a b c t x d e f t y g h i t z 0 0 0 1 ] × [ x y z 1 ] \begin{bmatrix} \hat{x}\\ \hat{y}\\ \hat{z}\\ 0\\ \end{bmatrix}= \begin{bmatrix} a & b & c & t_x\\ d & e & f & t_y\\ g & h & i & t_z\\ 0 & 0 & 0 & 1 \end{bmatrix}\times \begin{bmatrix} x \\ y \\ z \\ 1 \\ \end{bmatrix} x^y^z^0 = adg0beh0cfi0txtytz1 × xyz1
视觉空间和合成相机
将相机转换到坐标系原点,且up为y轴正方向,look-at为z轴负方向。该转换会应用于整个场景,不只是相机,即转换前后,相机在场景中的相对位置不变
观察矩阵用于将模型投影到摄像机(Camera)上。一般而言,定义观察矩阵(或者说摄像机状态)需要下面的一些参数:
Position:摄像机位置 P = [ x p , y p , z p ] T P = [x_p,y_p,z_p]^T P=[xp,yp,zp]T
Up:摄像机上方 U = [ x u , y u , z u ] T U = [x_u,y_u,z_u]^T U=[xu,yu,zu]T
LookAt:摄像机观察方向 L = [ x l , y l , z l ] T L = [x_l,y_l,z_l]^T L=[xl,yl,zl]T
Right:摄像机右方 R = L × U = [ x r , y r , z r ] T R = L \times U = [x_r, y_r, z_r]^T R=L×U=[xr,yr,zr]T
- Position:点坐标 e → = [ x e , y e , z e ] T \overrightarrow{e}= [x_e,y_e,z_e]^T e=[xe,ye,ze]T 表示相机的位置(eye position)
- gaze at : 相机看向的方向 g → = [ x g , y g , z g ] T \overrightarrow{g}= [x_g,y_g,z_g]^T g=[xg,yg,zg]T
- 表示相机向上的朝向(top): t → = [ x t , y t , z t ] T \overrightarrow{t}= [x_t,y_t,z_t]^T t=[xt,yt,zt]T
为了推导出实际的 View 矩阵(记为 V V V),假设初始状态如下的 View 矩阵(记为 V 0 V_0 V0)的参数如下:
P = [ 0 , 0 , 0 ] T P = [0,0,0]^T P=[0,0,0]T(原点)
U = [ 0 , 1 , 0 ] T U = [0,1,0]^T U=[0,1,0]T(正 Y 轴)
L = [ 0 , 0 , − 1 ] T L = [0,0,-1]^T L=[0,0,−1]T(负 Z 轴)
R = [ 1 , 0 , 0 ] R = [1,0,0] R=[1,0,0](正 Z 轴)
注意,对于观察者而言,我们要感受到物体进行了平移旋转之类的操作,需要对 View 矩阵(摄像机)进行相反的操作。要得到实际 View 矩阵,需要进行逆变换 V → V 0 V \rightarrow V_0 V→V0,其操作具体如下:
Translate
原点转换: e → = [ x e , y e , z e ] T → [ 0 , 0 , 0 ] T \overrightarrow{e}= [x_e,y_e,z_e]^T \rightarrow [0,0,0]^T e=[xe,ye,ze]T→[0,0,0]T
易得
V T = [ 1 0 0 − x e 0 1 0 − y e 0 0 1 − z e 0 0 0 1 ] V_T= \begin{bmatrix} 1 & 0 & 0 & -x_e\\ 0 & 1 & 0 & -y_e\\ 0 & 0 & 1 & -z_e\\ 0 & 0 & 0 & 1 \end{bmatrix} VT= 100001000010−xe−ye−ze1
Rotate:
Y 轴方向: [ x t , y t , z t ] T → [ 0 , 1 , 0 ] T [x_t,y_t,z_t]^T \rightarrow [0,1,0]^T [xt,yt,zt]T→[0,1,0]T,有 R × [ x t , y t , z t ] T = [ 0 , 1 , 0 ] T R\times [x_t,y_t,z_t]^T = [0,1,0]^T R×[xt,yt,zt]T=[0,1,0]T
Z 轴方向: [ x g , y g , z g ] T → [ 0 , 0 , − 1 ] T [x_g,y_g,z_g]^T \rightarrow [0,0,-1]^T [xg,yg,zg]T→[0,0,−1]T,有 R × [ x g , y g , z g ] T = [ 0 , 0 , − 1 ] T R\times [x_g,y_g,z_g]^T = [0,0,-1]^T R×[xg,yg,zg]T=[0,0,−1]T
X 轴方向: [ x g , y g , z g ] T × [ x t , y t , z t ] ] T = [ x r , y r , z r ] T → [ 1 , 0 , 0 ] T [x_g,y_g,z_g]^T \times [x_t,y_t,z_t]]^T = [x_r, y_r, z_r]^T \rightarrow [1,0,0]^T [xg,yg,zg]T×[xt,yt,zt]]T=[xr,yr,zr]T→[1,0,0]T
有
对于 Rotate,不方便直接计算,考虑逆向情况, 即世界坐标旋转到相机坐标。由于是正交变换所以有:
R − 1 × [ 0 , 1 , 0 ] T = [ x t , y t , z t ] T R^{-1}\times [0,1,0]^T = [x_t,y_t,z_t]^T R−1×[0,1,0]T=[xt,yt,zt]T
R − 1 × [ 0 , 0 , − 1 ] T = [ x g , y g , z g ] T R^{-1}\times [0,0,-1]^T = [x_g,y_g,z_g]^T R−1×[0,0,−1]T=[xg,yg,zg]T
因此有
参考文献
https://blog.csdn.net/ZDEWBYE/article/details/129481440
https://fuji-w.github.io/theory/CG/basic/matrix/
https://games-cn.org/intro-graphics/