✠OpenGL-4-图形数据
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了✠OpenGL-4-图形数据相关的知识,希望对你有一定的参考价值。
目录
- 数据类型和函数名
- 缓冲区和顶点属性
- 统一变量
- 顶点属性插值(光栅着色器)
- 模型-视图和透视矩阵
- 第一个3D程序——一个3D立方体
- 分析:物体经模型-视图矩阵变换后的坐标值是[相机空间]的坐标值
- 为什么取Z~near~=0.1,Z~far~=1000,FOV角=60°呢?
- 近剪裁平面那么小,有什么不对吗?
- 改变物体尺寸及视口尺寸对物体呈现的影响
- 初始窗口尺寸对之后物体呈现的影响
- 解决改变窗口尺寸时,优化显示区域(glViewport())
- 生成有插值颜色的立方体
- 旋转动画
- cameraZ的取值对视野的影响
- 渲染一个对象的多个副本
- 实例化
- 在同一个场景中渲染多个不同模型
- 矩阵堆栈
- 应对“Z冲突”伪影
- 图元的其他选项
- 性能优先的编程方法
- 补充说明
数据类型和函数名
OpenGL的数据类型定义可以与其它语言一致,但建议在ANSI C下最好使用以下定义的数据类型:
前缀 | 数据类型 | 相应C语言类型 | OpenGL类型 |
---|---|---|---|
b | 8-bit integer | signed char | GLbyte |
s | 16-bit integer | short | GLshort |
i | 32-bit integer | long | GLint, GLsizei |
f | 32-bit floating-point | float | GLfloat, GLclampf |
d | 64-bit floating-point | double | GLdouble, GLclampd |
ub | 8-bit unsigned integer | unsigned char | GLubyte, GLboolean |
us | 16-bit unsigned integer | unsigned short | GLushort |
ui | 32-bit unsigned integer | unsigned long | GLuint, GLenum, GLbitfield |
函数名有前缀,然后头字母大写,后缀是参数类型的简写,例如:
glVertex2i(2, 4);
glVertex3f(2.0, 4.0, 5.0);
glColor3f(1.0, 0.0, 0.0);
float colorArr[] = {1.0, 0.0, 0.0};
glColor3fv(colorArr);
可以看到函数名,是以"gl"为前缀的,因为调用的是OpenGL标准库函数,如果函数分属于其他类库,前缀就会不一样,可能为"wgl"、“glx"等。中间"Vertex”、“Color"表示函数的基本含义。后缀前带有数字2、3、4,其中2代表二维、3代表三维,4代表alpha值。
有的后缀带一个字母"v”,表示函数参数可用于一个指针指向一个向量(或数组)来替代一系列单个参数值。以上代码中glColor3f与glColor3fv都代表红色,二者等价。
还有一种带“”星号的表示方法,例如glColor(),它表示可以用函数的各种方式来设置颜色。同理,glVertexv()表示用一个指针指向所有类型的向量来定义一系列顶点坐标值。
举个函数定义的例子:glUniformMatrix(2|3|4|2×3|3×2|2×4|4×2|3×4|4×3)fv(GLint, GLsizei, GLboolean, const GLfloat*)
数字2表示一个2×2矩阵,数字3表示一个3×3矩阵,数字4表示一个4×4矩阵;2×4表示一个2×4矩阵(即2列4行)(注:先列后行)。后缀中“f”代表是浮点数据类型,“v”代表函数参数可以是一个指向一个向量(vector)(或数组array)的指针来替代一系列单个参数值的集合。
详细还可参见:GLSL基础语法介绍
———————————————————————————————
使用 OpenGL 渲染 3D 图形通常需要将若干数据集发送给 OpenGL 着色器管线。
想要绘制一个简单的 3D 对象,比如一个立方体,你至少需要发送以下项目:
- 立方体模型的顶点
- 控制立方体在3D空间中朝向表现的变换矩阵
把数据发送给OpenGL管线还要更加复杂一点,有两种方式:
- 通过顶点属性的缓冲区
- 直接发送给统一变量
缓冲区和顶点属性
想要绘制一个对象,它的顶点数据需要被发送给顶点着色器。通常会把顶点数据在 C++端放入一个缓冲区,并把这个缓冲区和着色器中声明的顶点属性相关联。
要完成这件事,有好几个步骤,有些步骤只需要做一次,而如果是动画场景的话,有些步骤需要每帧都做一次:
只做一次的步骤——一般是在init()中:
(1) 创建一个缓冲区
(2) 将顶点数据复制到缓冲区
每帧都要做的步骤——一般是在display()中:
(1) 启用包含了顶点数据的缓冲区
(2) 将这个缓冲区和一个顶点属性相关联
(3) 启用这个顶点属性
(4) 使用glDrawArrays()绘制对象
在OpenGL中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object, VBO)
中。当glDrawArrays()被执行时,缓冲区中的数据开始流动,从缓冲区的开头开始,按顺序流过顶点着色器,顶点着色器对每个顶点执行一次。
3D空间中的顶点需要3个数值,所以着色器中的顶点属性常常会以vec3类型接收到这3个数值。然后,对缓冲区中的每组这3个数值,着色器会被调用。
OpenGL 3.0版本还引入了一种相关的结构,叫顶点数组对象(Vertex Array Object, VAO)
。VAO作为一种组织缓冲区的方法,让缓冲区在复杂场景中更容易操控。
OpenGL要求至少创建一个VAO。
假设我们要显示两个对象,在C++端,我们可以声明一个VAO和两个相关的VBO(每个对象一个):
GLuint vao[1];// OpenGL要求这些数值以数组的形式指定
GLuint vbo[2];// 两个对象对应两个VBO
...
glGenVertexArrays(1, vao);// 创建VAO,返回整数ID存进vao[0]
glBindVertexArray(vao[0]);// 绑定VAO,将第0个VAO标记为“活跃”
glGenBuffers(2, vbo);// 创建两个VBO,返回两个整数ID分别存进vbo[0]、vbo[1]
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);// 将第0个VBO标记为“活跃”
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArr), vertexArr, GL_STATIC_DRAW);// 将顶点数组数据复制进活跃缓冲区(此处为第0个VBO)
标记为“活跃”的意思是:将生成的缓冲区和这个VAO / VBO相关联。
为什么用“缓冲区”?目的就是加快运算速度,直接在内存中存取肯定比从硬盘存取速度快得多,而OpenGL对算力是有很高甚至是极致要求的。
void glGenBuffers(GLsizei n, GLuint* buffers)
n - 指定要生成的缓冲区对象名称的数目。
buffers - 指定存储生成的缓冲区对象名称的数组。
在buffer中返回n个buffer对象名称。
void glBindBuffer(GLenum target, GLuint buffer)
target - 指定buffer对象绑定的目标。(该目标必须是下表中的buffer绑定目标之一)
buffer - 指定缓冲区对象的名称。
buffer绑定目标 用途 GL_ARRAY_BUFFER 顶点属性 GL_ATOMIC_COUNTER_BUFFER 原子计数器 GL_COPY_READ_BUFFER 复制源 GL_COPY_WRITE_BUFFER 复制目的地 GL_DISPATCH_INDIRECT_BUFFER 间接计算调度命令 GL_DRAW_INDIRECT_BUFFER 间接命令参数 GL_ELEMENT_ARRAY_BUFFER 顶点数组索引 GL_PIXEL_PACK_BUFFER 像素读取目标 GL_PIXEL_UNPACK_BUFFER 纹理数据来源 GL_QUERY_BUFFER 查询结果 GL_SHADER_STORAGE_BUFFER 着色器读写 GL_TEXTURE_BUFFER 纹理数据 GL_TRANSFORM_FEEDBACK_BUFFER 转换反馈 GL_UNIFORM_BUFFER 统一块 glBindBuffer将一个缓冲区对象绑定到指定的缓冲区绑定点。调用glBindBuffer时,将target设置为一个可接受的符号常量,并将buffer设置为一个缓冲区对象的名称,从而将该缓冲区对象的名称绑定到目标。
如果不存在名称为buffer的buffer对象,则创建一个以该名称命名的buffer对象。当一个缓冲区对象绑定到一个目标时,该目标的前一个绑定将自动中断。
缓冲区对象名称是无符号整数。0值是保留的,但是每个缓冲区对象目标没有缺省的缓冲区对象。相反,将buffer设置为0可以有效地解除之前绑定的任何buffer对象,并恢复该buffer对象目标的客户端内存使用(如果该目标支持的话)。
缓冲区对象名称和相应的缓冲区对象内容对于当前GL渲染上下文(rendering context)的共享对象空间(shared object space)是局部的(local);只有当两个渲染上下文通过适当的GL windows接口函数显式地启用上下文之间的共享时,它们才共享缓冲区对象名称。
必须使用glGenBuffers生成一组未使用的缓冲区对象名称。
缓冲区对象第一次绑定后的状态是一个未映射的零大小内存缓冲区,具有GL_READ_WRITE访问(access)权限和GL_STATIC_DRAW使用(usage)权限。
当非零缓冲区对象绑定到GL_ARRAY_BUFFER目标时,顶点数组指针参数将被解释为缓冲区对象内的偏移量(单位:基本机器单元)。
GL_ARRAY_BUFFER,即数组缓冲区,用于存储颜色、位置、纹理坐标等顶点属性,或者其他自定义属性。
GL_STATIC_DRAW:"Static”意味着VBO中的数据不会被改变(一次修改,多次使用),其他值 "dynamic” 意味着数据可以被频繁修改(多次修改,多次使用),"stream”意味着数据每帧都不同(一次修改,一次使用)。"Draw”意味着数据将会被送往GPU进行绘制,其他值 "read”意味着数据会被用户的应用读取,"copy”意味着数据会被用于绘制和读取。
注意在使用VBO时,只有“Draw”是有效的,而“copy”和“read”主要将会在像素缓冲区(PBO)和帧缓冲区(FBO)中发挥作用。
每个缓冲区需要有在顶点着色器中声明的相应的顶点属性变量。
用来接收立方体顶点的顶点属性可以在顶点着色器中这样声明:
layout(location = 0) in vec3 position;
“in”:输入,表示这个顶点属性将会从缓冲区中接收数值(顶点属性也可以用来“输出”)。
“vec3”:着色器的每次调用会抓到3个浮点类型数值(分别表示x、y、z,它们组成一个顶点数据)。
“position”:变量的名字。
“layout(location=0)”:“layout修饰符”,是把顶点属性和特定缓冲区关联起来的方法,在这里这个顶点属性的识别号是0。
假设立方体的顶点数据在C++/OpenGL应用程序中由数组直接指定。在这种情况下,我们需要:
(1) 将这些值复制到之前生成的两个缓冲区中的一个之中。这此,需要使用glBindBuffer()命令将缓冲区(如,第0个缓冲区)标记为“活跃”。
(2) 使用glBufferData()命令将包含顶点数据的数组复制进活跃缓冲区(这里应该是第0个VBO)。
假设顶点存储在名为vertexArr的浮点类型数组中,以下C++代码会将[这些值]复制到第0个VBO中:
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
// vertexArr数组数据复制到GLuint类型的vbo[0]中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArr), vertexArr, GL_STATIC_DRAW);
接下来,我们向display()中添加代码,将缓冲区中的值发送到着色器中的顶点属性。
我们通过以下3个步骤来实现:
(1) 使用glBindBuffer()命令标记这个缓冲区为“活跃”;
(2) 将活跃缓冲区与着色器中的顶点属性相关联;
(3) 启用顶点属性。
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);// (1):标记第0个缓冲区为“活跃”
glVertexAttribPointer(【0】, 3, GL_FLOAT, GL_FALSE, 0, 0);// (2):将第0个属性关联到缓冲区
glEnableVertexAttribArray(【0】);// (3):启用第0个顶点属性
// GLSL
layout(location = 【0】) in vec3 position;
void glVertexAttribPointer(
GLuint index,
// 指定要修改的通用顶点属性的索引。
GLint size,
// 指定每个通用顶点属性的组件数量。必须是1 2 3 4。此外,符号常量GL_BGRA被接受。初始值为4。
GLenum type,
// 指定数组中每个组件的数据类型。
GLboolean normalized,
// 指定定点数据值在访问时是应该被规范化(GL_TRUE)还是直接转换为定点值(GL_FALSE)。
GLsizei stride,
// 指定连续的通用顶点属性之间的字节偏移量。如果stride为0,则一般的顶点属性可以被理解为紧密地打包在数组中。初始值为0。
const void * pointer
// 指定当前绑定到GL_ARRAY_BUFFER目标的缓冲区的数据存储数组中第一个通用顶点属性的第一个组件的偏移量。初始值为0。
)
定义一个通用顶点属性数据数组。
注意,上述代码中C++和GLSL代码中用【】括起来的数值是相互关联的,都代表顶点属性索引值,所以它们是相同的值。
当执行glDrawArrays()时,第0个VBO中的数据将被传输给拥有位置(location)为0的layout修饰符的顶点属性中。这会将立方体的顶点数据发送到着色器。
统一变量
要想渲染一个场景以使它看起来是3D的,需要构建适当的变换矩阵,并将它们应用于模型的每个顶点。
而在顶点着色器(而非C++/OpenGL应用程序)中应用所需的矩阵运算是最有效的。
所以,习惯上会将这些[矩阵]从C++/OpenGL应用程序发送给着色器中的统一变量。
使用“uniform”关键字在着色器中声明统一变量。
以下示例声明了用于存储模型-视图和投影矩阵的变量,足够立方体程序使用:
uniform mat4 mv_matrix;// 用来保存模型-视图矩阵MV
uniform mat4 proj_matrix;// 用来保存投影矩阵([projection:投影])
将数据从C++/OpenGL应用程序发送到统一变量需要执行以下步骤:
(1) 获取统一变量的引用;
(2) 将指向所需数值的指针与获取的统一引用相关联。
假设[链接的渲染程序]保存在名为“renderingProgram”的变量中,把模型-视图和投影矩阵发送到两个统一变量mv_matrix和proj_matrix中:
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");// 返回着色器程序中统一变量的位置,存进GLint类型的mvLoc变量中
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat);// 将矩阵数据发送到GLSL类型为mat4的统一变量中
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat);
上述程序代码中,mvMat和pMat是利用GLM工具构建的模型-视图和投影矩阵。GLM调用函数value_ptr()返回对矩阵数据的引用。glUniformMatrix4fv()需要将这些矩阵值传递给统一变量(这里统一变量是mat4类型)。
void glUniformMatrix4fv(
GLint location,
// 指定要修改的统一变量的位置。
GLsizei count,
// 对于向量(glUniformv)命令,指定要修改的元素的数量。如果目标统一变量不是数组,则为1;如果是数组,则为1或更多。对于矩阵(glUniformMatrix)命令,指定要修改的矩阵的数量。如果目标统一变量不是一个矩阵数组,这个值应该是1,如果它是一个矩阵数组,这个值应该是1或更多。
GLboolean transpose,
// 对于矩阵命令,指定是否在将值加载到统一变量时对矩阵进行转置。
const GLfloat *value
// 对于向量和矩阵命令,指定一个指向计数值数组的指针,该数组将用于更新指定的统一变量。(注:“数组”指的是向量数组或矩阵数组)
)
为当前程序对象指定一个统一变量的值。(如,将glm:value_ptr(mvMat)指定给着色器mat4类型的统一变量mv_matrix)
统一变量的行为类似于初始化过的常量,并且在每次顶点着色器调用(即从缓冲区发送的每个顶点)中保持不变。统一变量本身不是插值的;无论有多少个顶点,它始终包含相同的值。
顶点属性插值(光栅着色器)
统一变量的行为类似于初始化过的常量,并且在每次顶点着色器调用(即从缓冲区发送的每个顶点)中保持不变。统一变量本身不是插值的;无论有多少顶点,它始终包含相同的值。
在片段着色器光栅化之前,由顶点定义的图元(例如,三角形)被转换为片段。光栅化过程会线性插值顶点属性值,以便显示的像素能无缝连接建模的曲面。
光栅着色器对顶点属性进行的插值在很多方面都很有用。我们将使用光栅化来插值颜色、纹理坐标和曲面法向量。
重要的是要理解通过缓冲区发送到顶点属性的所有值都将在管线中被进一步插值。
“in”表示它们从缓冲区接收值;“out”表示它们会将值发送到管线中的下一个阶段。
OpenGL有一个内置的vec4类型的“out”变量gl_Position。在顶点着色器中,将矩阵变换应用于传入的顶点(之前声明为位置的顶点),并将结果赋值给gl_Position:
// layout(location = 0) in vec3 【position】;
// out vec4 【gl_Position】;
gl_Position = proj_matrix * mv_matrix * position;
然后,变换后的顶点将==【自动】输出到[光栅着色器],【最终】将相应的 [像素位置] 发送到[片段着色器]。
OpenGL管线:[顶点着色器] -> [光栅化] -> [片段着色器] -> [像素操作]
在glDrawArrays()函数中指定GL_TRIANGLES时,光栅化是逐个三角形完成的==。
首先沿着连接顶点的线开始插值,其精度级别和像素显示密度相关,然后通过沿连接边缘像素的水平线插值来填充三角形的内部空间中的像素。
模型-视图和透视矩阵
渲染3D对象的一个基础步骤是创建适当的变换矩阵并将它们发送到统一变量。首先定义3个矩阵:
- 一个模型矩阵;
- 一个视图矩阵;
- 一个透视矩阵。
模型矩阵M在世界空间中表示对象的位置和朝向。每个模型都有自己的模型矩阵,如果模型移动,则需要不断重建该矩阵。
视图矩阵V在世界空间中移动并旋转模型,以模拟相机在所需位置的效果。
OpenGL相机在位置(0, 0, 0)并且面向负Z轴。为了模拟以某种方式移动相机的表现,需要向相反的方向移动物体本身。
例如,将摄像机向右移动会导致场景中的物体看起来是向左移动;虽然OpenGL相机是固定的,但我们可以通过把对象向左移动的方式,让摄像机看起来向右移动了。(这也为什么矩阵变换是“[负]相机位置/旋转角”的原因,上一章已有讨论过)
透视矩阵P是一种变换,它根据所需的视锥提供3D效果。
永远不会改变的矩阵可以在 init()中构建,但那些会改变的矩阵需要在 display()中构建,以便为每个帧重建它们。
我们假设模型是动画的,相机是可移动的,那么:
- 需要为每个模型和每个帧都创建模型矩阵;
- 视图矩阵需要每帧创建一次(因为相机可以移动),但是对于在这一帧期间渲染的所有对象,它都是一样的;
- 透视矩阵只需要创建一次[在init()中],它需要使用屏幕窗口的宽度和高度(以及所需的视锥体参数),除非调整窗口大小,否则它通常保持不变。
然后在display()中生成模型和视图转换矩阵:
(1) 根据所需的摄像机位置和朝向构建视图矩阵;
(2) 对于每个模型,进行以下操作:
i. 根据模型的位置和朝向构建模型矩阵;
ii. 将模型和视图矩阵结合成单个“MV”矩阵;
iii. 将MV和投影矩阵发送到相应的着色器统一变量。
将模型和视图矩阵合并成一个矩阵,并保持透视矩阵分离。
由于复杂的模型可能有数百甚至上千个顶点,因此可以通过将模型和视图矩阵发送到顶点着色器之前预先相乘一次来提高性能。
之后,我们会看到为什么需要将透视矩阵分开以用于光照的目的。
第一个3D程序——一个3D立方体
// vertShader.glsl
#version 430
layout(location = 0) in vec3 position;// 拥有location为【0】的顶点属性
uniform mat4 mv_matrix;// mat4类型的统一变量
uniform mat4 proj_matrix;// mat4类型的统一变量
void main(void) {
/*
内置变量gl_Position用来设置顶点在3D空间的坐标位置,并发送至下一个管线阶段(out)
[式1]: vec4(position, 1.0)=vec4(vec3(x, y, z), 1.0) =>将坐标齐次化
[式2]: mv_matrix * [式1] =>得到物体在相机空间中的位置坐标
proj_matrix * [式2] =>得到透视化的物体位置坐标
*/
gl_Position = proj_matrix * mv_matrix * vec4(position, 1.0);
}
// fragShader.glsl
#version 430
out vec4 color;
void main(void) {
/*
接收上一管线阶段(顶点着色器)out变量gl_Position传来的位置数据,
然后对每一个位置都上色为红色,并发送到下一管线阶段(out) -> 像素操作
*/
color = vec4(1.0, 0.0, 0.0, 1.0);
}
#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、rotate、scale、perspective
#include "Utils.h"
using namespace std;
#define numVAOs 1
#define numVBOs 2// 对于本例,只需一个VBO
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;
Utils util = Utils();
void setupVertices(void) {
/*
OpenGL工作在一个叫做NDC(Normalized Device Coordinates)的坐标系统下,
在这个坐标系统中,x、y和z的值全部都坐落在[-1, +1]范围内,
超出这个范围的点会被OpenGL忽略。
因为我们直接使用OpenGL的NDC坐标系统,所以顶点坐标的值在-1到+1之间。
*/
float vertexPositions[108] = {// 108÷3=36个点
-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(numVAOs, vao);// 创建1个VAO,对象ID存进vao[0]
glBindVertexArray(vao[0]);// 将第0个VAO标记为“活跃”
glGenBuffers(numVBOs, vbo);// 创建2个VBO,返回2个对象ID分别存进vbo[0]、vbo[1]
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);// 将第0个VBO标记为“活跃”
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);// 将顶点数组数据复制进“活跃”缓冲区vbo[0]
}
void init(GLFWwindow* window) {
/*
Utils::createShaderProgram()函数内容:
glCreateShader 创建着色器句柄
glCreateProgram 创建着色器程序对象
glShaderSource 关联着色器句柄与GLSL代码
glCompileShader 编译着色器句柄
glAttachShader 将着色器附加到程序对象
glLinkProgram 链接程序对象
*/
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
// 以下为指定的camera的坐标位置,它在OpenGL默认方向为看向Z轴负方向
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);// 使用着色器程序对象
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");// 获取着色器程序中统一变量的位置
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
glfwGetFramebufferSize(window, &width, &height);// 获取window窗口的帧缓冲区的大小。这样一来,当窗口大小改变时,aspect参数也会变化。
aspect = (float)width / (float)height;
/*透视矩阵参数:
<fieldOfView>=FOV角,<aspectRatio>=W/H,<nearPlane>=Znear,<farPlane>=Zfar
Y方向视场角=60°,宽高比=aspect,近剪裁平面=0.1,远剪裁平面=1000
*/
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
/* 视图矩阵:构建由3个分量的向量创建的4×4平移矩阵。(世界空间中)
glm::mat4 m = glm::translate(glm::mat4(1.0f), glm::vec3(1.0f));
m[0][0] == 1.0f, m[0][1] == 0.0f, m[0][2] == 0.0f, m[0][3] == 0.0f
m[1][0] == 0.0f, m[1][1] == 1.0f, m[1][2] == 0.0f, m[1][3] == 0.0f
m[2][0] == 0.0f, m[2][1] == 0.0f, m[2][2] == 1.0f, m[2][3] == 0.0f
m[3][0] == 1.0f, m[3][1] == 1.0f, m[3][2] == 1.0f, m[3][3] == 1.0f
注:GLM中是按【列】组织数据的,即[X][Y]:第X-1列的第Y-1个元素。
所以上述数值用常规矩阵表示为:
1 0 0 1 1 0 0 -cameraX
0 1 0 1 0 1 0 -cameraY
0 0 1 1 0 0 1 -cameraZ
0 0 0 1 => vMat= 0 0 0 1
注:对相机坐标进行操作并不是要平移相机,相机坐标位置是固定的,
目的是为了得到视图矩阵vMat,用它来平移物体到相机空间。
*/
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
/*模型矩阵:
1 0 0 cubeLocX 1 0 0 0
0 1 0 cubeLocY 0 1 0 -2
0 0 1 cubeLocZ 0 0 1 0
0 0 0 1 == 0 0 0 1
*/
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
mvMat = vMat * mMat;// 构建一个模型-视图矩阵【注:乘数与被乘数顺序不能反】
// 将mvMat、pMat矩阵数据发送到GLSL类型为mat4的统一变量中
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);// 将第【0】个属性关联到缓冲区(第【0】个VBO)
glEnableVertexAttribArray(0);// 启用第【0】个顶点属性。对应于GLSL:layout(location=【0】)
glEnable(GL_DEPTH_TEST);// 启用深度测试
/*
glDepthFunc(GLenum func):
指定用于将每个传入像素深度值与深度缓冲区中的深度值进行比较的函数。
func:指定像素被绘制的条件。仅当启用深度测试时才执行比较。
GL_LEQUAL:如果传入的深度值小于或等于缓冲区中的深度值,则传递(即被绘制)
*/
glDepthFunc(GL_LEQUAL);
/*
当glDrawArrays()被执行时,缓冲区中的数据开始流动,从缓冲区的开头开始,
按顺序流过顶点着色器,顶点着色器对每个顶点执行一次。
以三角形为基元,从第0个点开始,共绘制36个顶点。以12个三角形组成一个立方体
*/
glDrawArrays(GL_TRIANGLES, 0, 36);
}
int main(Nvidia Nsight 4.0 无法在 OpenGL 4.3 中分析代码