首页 > Learn OpenGL (五):向量

Learn OpenGL (五):向量

叉乘

叉乘只在3D空间中有定义,它需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量。接下来的教程中这会非常有用。下面的图片展示了3D空间中叉乘的样子:

不同于其他运算,如果你没有钻研过线性代数,可能会觉得叉乘很反直觉,所以只记住公式就没问题啦(记不住也没问题)。下面你会看到两个正交向量A和B叉积:

是不是看起来毫无头绪?不过只要你按照步骤来了,你就能得到一个正交于两个输入向量的第三个向量。

缩放

位移

齐次坐标(Homogeneous Coordinates)

向量的w分量也叫齐次坐标。想要从齐次向量得到3D向量,我们可以把x、y和z坐标分别除以w坐标。我们通常不会注意这个问题,因为w分量通常是1.0。使用齐次坐标有几点好处:它允许我们在3D向量上进行位移(如果没有w分量我们是不能位移向量的),而且下一章我们会用w值创建3D视觉效果。

如果一个向量的齐次坐标是0,这个坐标就是方向向量(Direction Vector),因为w坐标是0,这个向量就不能位移(译注:这也就是我们说的不能位移一个方向)。

旋转

大多数旋转函数需要用弧度制的角,但幸运的是角度制的角也可以很容易地转化为弧度制的:

  • 弧度转角度:角度 = 弧度 * (180.0f / PI)
  • 角度转弧度:弧度 = 角度 * (PI / 180.0f)

GLM

GLM是OpenGL Mathematics的缩写,它是一个只有头文件的库,也就是说我们只需包含对应的头文件就行了,不用链接和编译。GLM可以在它们的网站上下载。把头文件的根目录复制到你的includes文件夹,然后你就可以使用这个库了。

我们来看看是否可以利用我们刚学的变换知识把一个向量(1, 0, 0)位移(1, 1, 0)个单位(注意,我们把它定义为一个glm::vec4类型的值,齐次坐标设定为1.0):

glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
// 译注:下面就是矩阵初始化的一个例子,如果使用的是0.9.9及以上版本
// 下面这行代码就需要改为:
// glm::mat4 trans = glm::mat4(1.0f)
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;

我们先用GLM内建的向量类定义一个叫做vec的向量。接下来定义一个mat4类型的trans,默认是一个4×4单位矩阵。下一步是创建一个变换矩阵,我们是把单位矩阵和一个位移向量传递给glm::translate函数来完成这个工作的(然后用给定的矩阵乘以位移矩阵就能获得最后需要的矩阵)。

之后我们把向量乘以位移矩阵并且输出最后的结果。如果你仍记得位移矩阵是如何工作的话,得到的向量应该是(1 + 1, 0 + 1, 0 + 0),也就是(2, 1, 0)。这个代码片段将会输出210,所以这个位移矩阵是正确的。

我们来做些更有意思的事情,让我们来旋转和缩放之前教程中的那个箱子。首先我们把箱子逆时针旋转90度。然后缩放0.5倍,使它变成原来的一半大。我们先来创建变换矩阵:

glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5)); 

首先,我们把箱子在每个轴都缩放到0.5倍,然后沿z轴旋转90度。GLM希望它的角度是弧度制的(Radian),所以我们使用glm::radians将角度转化为弧度。注意有纹理的那面矩形是在XY平面上的,所以我们需要把它绕着z轴旋转。因为我们把这个矩阵传递给了GLM的每个函数,GLM会自动将矩阵相乘,返回的结果是一个包括了多个变换的变换矩阵。

如何把矩阵传递给着色器?我们在前面简单提到过GLSL里也有一个mat4类型。所以我们将修改顶点着色器让其接收一个mat4的uniform变量,然后再用矩阵uniform乘以位置向量:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;out vec2 TexCoord;uniform mat4 transform;void main()
{gl_Position = transform * vec4(aPos, 1.0f);TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
}

GLSL也有mat2mat3类型从而允许了像向量一样的混合运算。前面提到的所有数学运算(像是标量-矩阵相乘,矩阵-向量相乘和矩阵-矩阵相乘)在矩阵类型里都可以使用。当出现特殊的矩阵运算的时候我们会特别说明。

在把位置向量传给gl_Position之前,我们先添加一个uniform,并且将其与变换矩阵相乘。我们的箱子现在应该是原来的二分之一大小并(向左)旋转了90度。当然,我们仍需要把变换矩阵传递给着色器:

unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

我们首先查询uniform变量的地址,然后用有Matrix4fv后缀的glUniform函数把矩阵数据发送给着色器。第一个参数你现在应该很熟悉了,它是uniform的位置值。第二个参数告诉OpenGL我们将要发送多少个矩阵,这里是1。第三个参数询问我们我们是否希望对我们的矩阵进行置换(Transpose),也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局,叫做列主序(Column-major Ordering)布局。GLM的默认布局就是列主序,所以并不需要置换矩阵,我们填GL_FALSE。最后一个参数是真正的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种,因此我们要先用GLM的自带的函数value_ptr来变换这些数据。

我们创建了一个变换矩阵,在顶点着色器中声明了一个uniform,并把矩阵发送给了着色器,着色器会变换我们的顶点坐标。最后的结果应该看起来像这样:

完美!我们的箱子向左侧旋转,并是原来的一半大小,所以变换成功了。我们现在做些更有意思的,看看我们是否可以让箱子随着时间旋转,我们还会重新把箱子放在窗口的右下角。要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它在每一次渲染迭代中都要更新。我们使用GLFW的时间函数来获取不同时间的角度:

glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));

要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一次迭代中创建它,从而保证我们能够不断更新旋转角度。这也就意味着我们不得不在每次游戏循环的迭代中重新创建变换矩阵。通常在渲染场景的时候,我们也会有多个需要在每次渲染迭代中都用新值重新创建的变换矩阵。

练习:

尝试再次调用glDrawElements画出第二个箱子,使用变换将其摆放在不同的位置。让这个箱子被摆放在窗口的左上角,并且会不断的缩放(而不是旋转)

#include 
#include 
#include #include 
#include 
#include #include 
#include #include void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;int main()
{// glfw: initialize and configure// ------------------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif// glfw window creation// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader zprogram// ------------------------------------Shader ourShader("5.2.transform.vs", "5.2.transform.fs");// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions           // texture coords0.5f,  0.5f, 0.0f,    1.0f, 1.0f, // top right0.5f, -0.5f, 0.0f,    1.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f,    0.0f, 0.0f, // bottom left-0.5f,  0.5f, 0.0f,    0.0f, 1.0f  // top left };unsigned int indices[] = {0, 1, 3, // first triangle1, 2, 3  // second triangle};unsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// texture coord attributeglVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// load and create a texture // -------------------------unsigned int texture1, texture2;// texture 1// ---------glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);// set the texture wrapping parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.unsigned char *data = stbi_load(FileSystem::getPath("resources/textures/container.jpg").c_str(), &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// texture 2// ---------glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);// set the texture wrapping parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsdata = stbi_load(FileSystem::getPath("resources/textures/awesomeface.png").c_str(), &width, &height, &nrChannels, 0);if (data){// note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBAglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);// tell opengl for each sampler to which texture unit it belongs to (only has to be done once)// -------------------------------------------------------------------------------------------ourShader.use();ourShader.setInt("texture1", 0);ourShader.setInt("texture2", 1);// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// bind textures on corresponding texture unitsglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);glm::mat4 transform;// first container// ---------------transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f));transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));// get their uniform location and set matrix (using glm::value_ptr)unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));// with the uniform matrix set, draw the first containerglBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// second transformation// ---------------------transform = glm::mat4(); // reset it to an identity matrixtransform = glm::translate(transform, glm::vec3(-0.5f, 0.5f, 0.0f));float scaleAmount = sin(glfwGetTime());transform = glm::scale(transform, glm::vec3(scaleAmount, scaleAmount, scaleAmount));glUniformMatrix4fv(transformLoc, 1, GL_FALSE, &transform[0][0]); // this time take the matrix value array's first element as its memory pointer value// now with the uniform matrix being replaced with new transformations, draw it again.glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose:// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);// glfw: terminate, clearing all previously allocated GLFW resources.// ------------------------------------------------------------------glfwTerminate();return 0;
}// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height);
}

 

更多相关:

  • 嘿,我已经看过了一个老问题,但它不能回答我的问题我已经安装了libpng,然后尝试安装autopy并得到complie错误。在我对python还不是很在行,所以我不确定如何修复它们。在Ashley:~ ashleyhughes$ sudo easy_install autopySearching for autopyReading h...

  • OpenGL所有渲染的结果都输出显示在窗口中,设置OpenCV的图像cv::Mat outimg,并使用OpenGL的glReadPixels从帧缓冲区中加载像素至内存: //use fast 4-byte alignment (default anyway) if possible glPixelStorei(GL_PACK_AL...

  • 在主程序中我们先设置3个采样器名称 pbrShader.use();pbrShader.setInt("irradianceMap", 0);pbrShader.setInt("prefilterMap", 1);pbrShader.setInt("brdfLUT", 2);其中setint是封装函数:void setInt...

  • 操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。 帧缓存帮助我们离屏渲染,提高渲染速度 unsigned int captureFBO; glGenFramebuffers(1, &captureFBO); glBindFramebuffer(GL_FRAMEBUFF...

  • 为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),...

  • 英语的重要性,毋庸置疑!尤其对广大职场人士,掌握英语意味着就多了一项竞争的技能。那,对于我们成人来说,时间是最宝贵的。如何短时间内在英语方面有所突破,这是我们最关心的事情。英语学习,到底有没有捷径可以走,是否可以速成?周老师在这里明确告诉大家,英语学习,没有绝对的捷径走,但是可以少走弯路。十多年的教学经验告诉我们,成功的学习方法可以借...

  • 展开全部 其实IDLE提供了一个显32313133353236313431303231363533e78988e69d8331333365663438示所有行和所有字符的功能。 我们打开IDLE shell或者IDLE编辑器,可以看到左下角有个Ln和Col,事实上,Ln是当前光标所在行,Col是当前光标所在列。 我们如果想得到文件代码...

  • 前言[1]从 Main 方法说起[2]走进 Tomcat 内部[3]总结[4]《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spri...

  • 【本文摘要】【注】本文所述内容为学习Yjango《学习观》相关视频之后的总结,观点归Yjango所有,本文仅作为学习之用。阅读本节,会让你对英语这类运动类知识的学习豁然开朗,你会知道英语学习方面,我们的症结所在。学习英语这类运动类知识,需要把握四个原则第一,不要用主动意识。第二,关注于端对端第三,输入输出符合实际情况第四,通过多个例子...

  • 点云PCL免费知识星球,点云论文速读。文章:RGB-D SLAM with Structural Regularities作者:Yanyan Li , Raza Yunus , Nikolas Brasch , Nassir Navab and Federico Tombari编译:点云PCL代码:https://github.co...

  • 看龙书的时候发现一个矩阵在传入Shader之前都要转置一下,很好奇为什么要有一步这样的操作。行主序和列主序行主序指矩阵在内存中逐行存储,列主序指矩阵在内存中逐列存储。行主序矩阵内存布局:列主序矩阵内存布局:行向量和列向量行向量指的是把向量当成一个一行n列的矩阵,列向量指的是把向量当成一个n行一列的矩阵。左乘和右乘矩阵“左乘”:矩阵和向...

  • ORB-SLAM点云地图中相机的位姿初始化,无论算法工作在平面场景,还是非平面场景下,都能够完成初始化的工作。其中主要是使用了适用于平面场景的单应性矩阵H和适用于非平面场景的基础矩阵F,程序中通过一个评分规则来选择适合的模型,恢复相机的旋转矩阵R和平移矩阵t那么下面主要讲解关于对极几何中的基础矩阵,本质矩阵,和单应矩阵之间的区别与联...

  • 矩阵可分为稠密矩阵和稀疏矩阵,对于稀疏矩阵而言,使用同样的内存来存储这个矩阵显然是对内存的浪费,那么我们就可以想办法将矩阵中所有的o元素挥着不相关元素剔除,怎么剔除,第一种方法是通过三个一维矩阵来存储原二维矩阵中的所有非0元素,三个矩阵分别为value、column、row, value 数组存储所有的非零元素, column 数...

  • void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const; m – 目标矩阵。如果m在运算前没有合适的尺寸或类型,将被重新分配。rtype – 目标矩阵的类型。因为目标矩阵的通道数与源矩阵一样,所以rtype也可以看做是目标...

  • https://blog.csdn.net/jiangdf/article/details/8460012 glMatrixMode()函数的参数,这个函数其实就是对接下来要做什么进行一下声明,也就是在要做下一步之前告诉计算机我要对“什么”进行操作了,这个“什么”在glMatrixMode的“()”里的选项(参数)有3种模式: GL...