OpenGL系列文章目录
文章目录
前言
当在openGL中使用glm库报错:
assert(abs(aspect - std::numeric_limits::epsilon()) > static_cast(0));
一、matrix_transform.inl文件报错
1.报错位置
下图中三个绿色框中的代码出错了,
2.定位问题
原来这句代码引起问题:
projMat = glm::perspective(glm::radians(60.0f), aspect, 0.01f, 1000.f);
上图中x值发生了异常,-nan(ind)
可能的情况:
1.分母为”0”,如果分母为零,自然时不能得到一个确定的数字的。
2.对负数开平方。
3.有些编译器在对无穷大与无穷小的计算时也会出现此类情况。
3.问题解决
估计是在以下计算中得到无穷大或者无穷小或者分母无穷接近0的情况了
rMat = glm::rotate(glm::mat4(1.f), 1.75f * tf, glm::vec3(1.f, 0.f, 0.f));
rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 1.f, 0.f));
rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 0.f, 1.f));
projMat = glm::perspective(glm::radians(60.0f), aspect, 0.01f, 1000.f);
我把代码修改成下面,就没有报错了
//旋转矩阵,绕x轴旋转
rMat = glm::rotate(glm::mat4(1.f), glm::radians(40.f) * tf, glm::vec3(1.f, 0.f, 0.f));
//rMat = glm::rotate(glm::mat4(1.f), 1.75f * tf, glm::vec3(1.f, 0.f, 0.f));
//旋转矩阵,绕y轴旋转
rMat = glm::rotate(rMat, glm::radians(50.f) * tf, glm::vec3(0.f, 1.f, 0.f));
//rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 1.f, 0.f));
//旋转矩阵,绕z轴旋转
rMat = glm::rotate(rMat, glm::radians(60.f) * tf, glm::vec3(0.f, 0.f, 1.f));
//rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 0.f, 1.f));
代码
#include "glew/glew.h"
#include "glfw/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "Utils.h"
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
//#define GLM_FORCE_RADIANS
static const int Screen_Width = 1920;
static const int Screen_Height = 1080;
static const int numberVAOs = 1;
static const int numberVBOs = 2;
GLuint renderingProgram = 0;
GLuint vao[numberVAOs] = { 0 };
GLuint vbo[numberVBOs] = { 0 };
float cameraX = 0.f, cameraY = 0.f, cameraZ = 0.f;
float cubeX = 0.f, cubeY = 0.f, cubeZ = 0.f;
int g_width = 0, g_height = 0, g_displayLoopi = 0;
float aspect = 0.f;
float tf = 0.f;
GLuint mvLoc = 0, projLoc = 0;
glm::mat4 mMat(1.f), vMat(1.f), mvMat(1.f), projMat(1.f), tMat(1.f), rMat(1.f);
void setupVertices()
{
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(numberVAOs, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numberVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
}
//窗口大小改变时比例位置不变
void window_size_callback(GLFWwindow* window, int newWidth, int newHeight)
{
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
//必须要创建透视投影矩阵计算公式,否则第一帧渲染时,立方体不在视口中 :1.0472 radians = 60 degrees
//glm::perspective使用模板定义,所以函数各个参数类型要一致 1.0472f
projMat = glm::perspective(glm::radians(60.0f), aspect, 0.01f, 1000.f);
//projMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.f);
}
void init(GLFWwindow* window)
{
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
/*GLFW在这里和这里解释文档中的两个坐标系。
简而言之,窗口坐标是相对于监视器和 / 或窗口的,并且以不一定对应于真实屏幕像素的人造单元给出。 当DPI缩放被激活时(例如,在带有视网膜显示器的Mac上),情况尤其如此。
与窗口坐标相比,帧缓冲区的大小与像素相关,以便与glViewport OpenGLs要求相匹配。
请注意,在某些系统上,窗口坐标和像素坐标可以相同,但这不一定是正确的。*/
glfwGetFramebufferSize(window, &g_width, &g_height);
aspect = (float)g_width / (float)g_height;
//必须要创建透视投影矩阵计算公式,否则第一帧渲染时,立方体不在视口中 :1.0472 radians = 60 degrees
//glm::perspective使用模板定义,所以函数各个参数类型要一致
projMat = glm::perspective(1.0472f, aspect, 0.01f, 1000.f);
cameraX = 0.f;
cameraY = 0.f;
cameraZ = 32.f;
setupVertices();
}
void display(GLFWwindow* window, float currentTime)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.f, 0.7f, 1.f, 1.f);
//启动着色器程序,在GPU上安装GLSL代码,这不会运行着色器程序,
glUseProgram(renderingProgram);
//获取uniform变量在着色器程序中的位置序号,通过该序号可以设置一致变量的值,如果没有该变量则返回-1
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
//移动相机矩阵
vMat = glm::translate(glm::mat4(1.f), glm::vec3(cameraX, cameraY, cameraZ));
//更改一个uniform矩阵变量或数组的值。要更改的uniform变量的位置由location指定,location的值应该由glGetUniformLocation函数返回
// 将透视矩阵和MV 矩阵复制给相应的统一变量
/*通过一致变量(uniform修饰的变量)引用将一致变量值传入渲染管线。
location : uniform的位置。
count : 需要加载数据的数组元素的数量或者需要修改的矩阵的数量。
transpose : 指明矩阵是列优先(column major)矩阵(GL_FALSE)还是行优先(row major)矩阵(GL_TRUE)。
value : 指向由count个元素的数组的指针。
*/
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projMat));
//glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
for (g_displayLoopi = 0; g_displayLoopi < 36; g_displayLoopi++)
{
tf = currentTime + g_displayLoopi;
//平移矩阵
tMat = glm::translate(glm::mat4(1.f), glm::vec3(glm::sin(.35f * tf) * 8.f, glm::cos(.52f * tf) * 8.f, glm::sin(.70f * tf) * 8.f));
//旋转矩阵,绕x轴旋转
rMat = glm::rotate(glm::mat4(1.f), glm::radians(40.f) * tf, glm::vec3(1.f, 0.f, 0.f));
//rMat = glm::rotate(glm::mat4(1.f), 1.75f * tf, glm::vec3(1.f, 0.f, 0.f));
//旋转矩阵,绕y轴旋转
rMat = glm::rotate(rMat, glm::radians(50.f) * tf, glm::vec3(0.f, 1.f, 0.f));
//rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 1.f, 0.f));
//旋转矩阵,绕z轴旋转
rMat = glm::rotate(rMat, glm::radians(60.f) * tf, glm::vec3(0.f, 0.f, 1.f));
//rMat = glm::rotate(rMat, 1.75f * tf, glm::vec3(0.f, 0.f, 1.f));
/*请注意最后一行中的矩阵乘法——
操作中tMat 和rMat 的顺序很重要。它计算两个变换的结合,平移放在左边,旋转放在右边。
当顶点随后乘以此矩阵时,计算从右到左进行,这意味着首先完成旋转,然后才是平移。
变换的应用顺序很重要,改变顺序会导致不同的行为。图4.7 显示了为立方体设置了动画后
显示的一些帧。*/
mMat = tMat * rMat;
//如果mMat和vMat位置变化,将看不到立方体
mvMat = mMat * vMat;
//更改一个uniform矩阵变量或数组的值。要更改的uniform变量的位置由location指定,location的值应该由glGetUniformLocation函数返回
// 将透视矩阵和MV 矩阵复制给相应的统一变量
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projMat));
//glBindVertexArray(vao[0]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
//指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置
/*Parameters
index
指定要修改的顶点属性的索引值
size
指定每个顶点属性的组件数量。必须为1、2、3或者4。初始值为4。(梦维:如position是由3个(x, y, z)组成,而颜色是4个(r, g, b, a))
type
指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
normalized
指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。
stride
指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。
pointer
指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0。
*/
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
//开启深度测试
glEnable(GL_DEPTH_TEST);
//指定用于深度缓冲比较值;
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
int main(int argc, char** argv)
{
int glfwStates = glfwInit();
if (glfwStates == GLFW_FALSE)
{
cout << "GLFW initialize failed, invoke glfwInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << std::endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
/*因为我们要使用OpenGL 4.6,所以我们把GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR对应的hint都设置为4和6。
因为我们要使用OpenGL核心模式(这个后面会提到更多),所以我们把GLFW_OPENGL_PROFILE对应的hint设置为GLFW_OPENGL_CORE_PROFILE,
表示使用OpenGL核心模式。最后,把GLFW_RESIZABLE对应的hint设置为GLFW_FALSE,表示窗口不允许用户调整大小。
之所以这样做是因为如果允许用户调整大小,大小发生变化后,窗口的绘制区域默认不变(依然是原来窗口的区域),
也就是说窗口上绘制的图像的大小、位置不会发生改变。为了避免这种现象发生,我们就简单地不让用户调整窗口大小
(当然也有更好的方法,就是用GLFW设置一个窗口大小的回调函数,但这样比较简单)。*/
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_PROFILE);
GLFWwindow* window = glfwCreateWindow(Screen_Width, Screen_Height, "Draw multiple cubes", nullptr, nullptr);
if (!window)
{
cout << "GLFW create window failed, invoke glfwCreateWindow()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << std::endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
/*此函数使调用线程上的指定窗口的 OpenGL 或 OpenGL ES 上下文成为当前上下文。
一次只能在单个线程上使上下文成为当前上下文,并且每个线程一次只能有一个当前上下文。
在线程之间移动上下文时,必须先使其在旧线程上变为非当前状态,然后再在新线程上变为当前状态。
*/
glfwMakeContextCurrent(window);
//glfwSetFramebufferSizeCallback()和glfwSetWindowSizeCallback区别
//glfwSetFramebufferSizeCallback(window, window_size_callback);
glfwSetWindowSizeCallback(window, window_size_callback);
int glewStates = glewInit();
if (GLEW_OK != glewStates)
{
cout << "GLEW initialize failed, invoke glewInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << std::endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
/*此函数设置当前 OpenGL 或 OpenGL ES 上下文的交换间隔,即从调用glfwSwapBuffers开始等待的屏幕更新次数,
然后再交换缓冲区并返回。这有时称为垂直同步、垂直回扫同步或仅vsync。
支持WGL_EXT_swap_control_tear和GLX_EXT_swap_control_tear扩展的上下文也接受负交换间隔,这允许驱动程序立即交换,
即使帧到达有点晚。您可以使用glfwExtensionSupported检查这些扩展。
上下文必须在调用线程上是最新的。在没有当前上下文的情况下调用此函数将导致GLFW_NO_CURRENT_CONTEXT错误。
此功能不适用于 Vulkan。如果您使用 Vulkan 进行渲染,请改为查看交换链的当前模式。
*/
glfwSwapInterval(1);
init(window);
while (!glfwWindowShouldClose(window))
{
display(window, (float)glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
return 0;
}