LearnOpenGL学习笔记—入门07:Coordinate Systems

0 前言

本节笔记对应的内容 坐标系统
入门01中我们配置好了环境
入门02中我们可以检测输入并出现一个有颜色的窗口
入门03中我们初步学习了图形渲染管线,尝试用不同方法画出了三角形和四边形
入门04(上)中我们学习了shader和GLSL的相关知识,并给图形加上了变换的颜色以及彩色。
入门04(下)中我们建立自己的shader类,并能够从外部读取shader的内容。
入门05中我们了解了有关材质的内容
入门06中,我们尝试用旋转矩阵以及四元数的方法组成变换矩阵,让它动起来,并对四元数的理解进行了一定阐述。
这一节我们会讲讲坐标系统,讲讲从3D空间得到2D平面的过程

1 坐标系统概述

在上一个教程中,我们学习了如何有效地利用矩阵的变换来对所有顶点进行变换,但是OpenGL希望在每次顶点着色器运行后,所有可见的所有顶点都为标准化设备坐标(Normalized Device Coordinate, NDC)。

  • 标准化设备坐标,即每个顶点的x,y,z坐标都应该在-1.0到1.0之间,超出这个坐标范围的顶点都将不可见。
  • 将坐标变换为标准化设备坐标,接着再转化为屏幕坐标,这个过程通常是分步进行的,也就是类似于流水线那样子。
  • 在流水线中,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统(Coordinate System)。
    将物体的坐标变换到几个过渡坐标系(Intermediate Coordinate System)的优点在于,在特定的坐标系统中,一些操作或运算更加方便和容易。
  • 对我们来说比较重要的总共有5个不同的坐标系统:
    局部空间(Local Space,或者称为物体空间(Object Space))
    世界空间(World Space)
    观察空间(View Space,或者称为视觉空间(Eye Space))
    裁剪空间(Clip Space)
    屏幕空间(Screen Space)
    这就是一个顶点在最终被转化为片段之前需要经历的所有不同状态。

为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是

  • 模型(Model)矩阵、观察(View)矩阵、投影(Projection)矩阵。联合起来就是我们常说的MVP矩阵
  • 我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。

下面的这张图展示了整个流程以及各个变换过程做了什么:
在这里插入图片描述

  • 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  • 世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  • 将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  • 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
  • 最后,将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。
  • 视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。
  • 最后变换出来的坐标将会送到光栅器,将其转化为片段。

我们之所以将顶点变换到各个不同的空间的原因是有些操作在特定的坐标系统中才有意义且更方便。例如,当需要对物体进行修改的时候,在局部空间中来操作会更说得通;如果要对一个物体做出一个相对于其它物体位置的操作时,在世界坐标系中来做这个才更说得通,等等。

1.1 局部空间

  • 拿拍照来举例,给一群人拍照,里面每个人都会调整各自的仪容仪表,各自调整就是在局部空间。

局部空间是指物体所在的坐标空间,即对象最开始所在的地方。
在一个建模软件中创建了一个立方体,创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。
有可能创建的所有模型都以(0, 0, 0)为初始位置(译注:然而它们会最终出现在世界的不同位置)。
所以,模型的所有顶点都是在局部空间中,它们相对于物体来说都是局部的。

1.2 世界空间/模型矩阵

  • 给一群人拍个照片,我们要把他们各自整理好后,我们要把他们先集中起来,然后调整站位,角度。
  • 集中起来调整,这一过程就是模型矩阵,所在的空间就是世界空间。

如果我们将我们所有的物体导入到程序当中,它们有可能会全挤在世界的原点(0, 0, 0)上,这并不是我们想要的结果。
我们想为每一个物体定义一个位置,从而能在更大的世界当中放置它们。
世界空间中的坐标正如其名:是指顶点相对于(游戏)世界的坐标。
如果想将物体分散在世界上摆放,这个空间就是我们希望物体变换到的空间。
物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的。
模型矩阵是一种变换矩阵,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。

1.3 观察空间/观察矩阵

  • 找一群人拍个照片,一切顺利后,摄像机对好方位。
  • 摄像机也就是观察空间,而调整摄影机的过程,就是观察矩阵。

观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。
观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。
因此观察空间就是从摄像机的视角所观察到的空间。
这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。
这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间。

关于相机,我们将在下一节中进行讨论如何创建一个这样的观察矩阵来模拟一个摄像机。

1.4 裁剪空间/投影矩阵

  • 我们对着这群人拍照,按下快门,将三维的场景变成了平面照片,同时摄影机屏幕外的部分不要。
  • 这一投影过程用到的就是投影矩阵,摄影机屏幕也就是裁剪空间。

在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。
被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。

因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系。

为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的-1000到1000。
投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。
所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。

将特定范围内的坐标转化到标准化设备坐标系的过程被称之为投影(Projection)。

将观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体((Frustum,即为由投影矩阵创建的观察箱(Viewing Box))。

我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。
在这里插入图片描述

1.4.1 正射投影

在这里插入图片描述
正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉,投影完没有透视效果。
创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。
它的平截头体看起来像一个容器:
在这里插入图片描述
由这个平截头体映射到标准化设备坐标,我们会这么做,利用位移和缩放矩阵,先将它移动到场景中心,然后缩放到标准大小。
在这里插入图片描述

要创建一个正射投影矩阵,我们可以使用GLM的内置函数glm::ortho

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
  • 前两个参数指定了平截头体的左右坐标,第三和第四参数指定了平截头体的底部和顶部。通过这四个参数我们定义了近平面和远平面的大小。
    第五和第六个参数则定义了近平面和远平面的距离。
    这个投影矩阵会将处于这些x,y,z值范围内的坐标变换为标准化设备坐标。

因为正射投影没有使用透视,远处的物体不会显得更小,所以产生奇怪的视觉效果。由于这个原因,正射投影主要用于二维渲染以及一些建筑或工程的程序,在那些我们希望顶点不会被透视所干扰的场景里。

1.4.2 透视投影

  • 对透视投影进行概述
    在这里插入图片描述
    在这里插入图片描述

  • 在这里插入图片描述在这里插入图片描述

  • 经过上述说明,我们明白我们要做的是把透视投影变成正射投影,再用之前正射投影的办法得到标准化设备坐标。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

带入之前正射投影的结果,得到透视投影矩阵最终结果(这个结果中是以“挤压矩阵”是以-z方向算的,所以正负号有一些差异)
在这里插入图片描述
关于z值的变化我们其实可以带入算出来
在这里插入图片描述
以下是一些查到的方法
在这里插入图片描述
在GLM中可以这样创建一个透视投影矩阵:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
  • 它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小,也就是张角大小。
    如果想要一个真实的观察效果,通常设置为45.0f,但想要一个末日风格的结果可以将其设置一个更大的值。
  • 第二个参数设置了宽高比,由视口的宽除以高所得。
  • 第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f,而远距离设为100.0f。
  • 所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。

1.5 总结

在这里插入图片描述

2 代码实现

我们为上述的每一个步骤都创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。
一个顶点坐标将会根据以下过程被变换到裁剪坐标:
V c l i p = M p r o j e c t i o n ⋅ M v i e w ⋅ M m o d e l ⋅ V l o c a l V_{clip}=M_{projection}⋅M_{view}⋅M_{model}⋅V_{local} Vclip=MprojectionMviewMmodelVlocal
矩阵运算的顺序是相反的(我们需要从右往左阅读矩阵的乘法)。
最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

  • 我们刚才使用变换矩阵,让顶点着色器的输出要求所有的顶点,都在裁剪空间内。
    OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。
    OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中是一个800x600的屏幕)。
    这个过程称为视口变换。

  • 我们首先创建一个模型矩阵。
    这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换它们到全局的世界空间。
    让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。
    这个模型矩阵看起来是这样的:

glm::mat4 modelMat;
modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
  • 接下来我们需要创建一个观察矩阵。
    我们想要在场景里面稍微往后移动,以使得物体变成可见(当在世界空间时,我们位于原点(0,0,0))。
    将摄像机向后移动,和将整个场景向前移动是一样的。
    这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。
    因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。
    我们会通过将场景沿着z轴负方向平移来实现,它会给我们一种我们在往后移动的感觉。
glm::mat4 viewMat;
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
  • 最后我们需要做的是定义一个投影矩阵。我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵:
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);

合并起来即

//M
glm::mat4 modelMat;
modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
//V
glm::mat4 viewMat;
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
//P
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

在顶点着色器中改一下

#version 330 core									  
layout(location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
layout(location = 2) in vec2 aTexCoord; // uv变量的属性位置值为 2

out vec4 vertexColor;
out vec2 TexCoord;

//uniform mat4 transform;
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
void main(){										   
   gl_Position =  projMat * viewMat * modelMat * vec4(aPos.x, aPos.y, aPos.z, 1.0);   
   vertexColor = vec4(aColor,1.0);	
   TexCoord = aTexCoord;
}

然后在渲染循环里加上

glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

最终的物体应该会:

  • 稍微向后倾斜至地板方向。
  • 离我们有一些距离。
  • 有透视效果(顶点越远,变得越小)。
    在这里插入图片描述

2.1 更多的3d

到目前为止,我们一直都在使用一个2D平面,而且在3D空间里!
所以,让我们大胆地拓展我们的2D平面为一个3D立方体。
要想渲染一个立方体,我们一共需要36个顶点(6个面 x 每个面有2个三角形组成 x 每个三角形有3个顶点),可以从这里获取顶点位置,代替原本的vertices数组
并且修改顶点属性

// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性
//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
//glEnableVertexAttribArray(1);
// uv属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);

把和EBO有关的全部注释掉,用直接画数组的方式画出图形

glDrawArrays(GL_TRIANGLES, 0, 36);

并且在开启窗口(glViewport(0, 0, 800, 600);)的后面启动深度缓存

glEnable(GL_DEPTH_TEST);

在渲染循环中,每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  • Z缓冲
    OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。
    GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。
    深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。
    这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。

在这里插入图片描述

2.2 更多的立方体!

现在我们想在屏幕上显示10个立方体。
每个立方体看起来都是一样的,区别在于它们在世界的位置及旋转角度不同。
立方体的图形布局已经定义好了,所以当渲染更多物体的时候我们不需要改变我们的缓冲数组和属性数组,我们唯一需要做的只是改变每个对象的模型矩阵来将立方体变换到世界坐标系中。

首先,让我们为每个立方体定义一个位移向量来指定它在世界空间的位置。我们将在一个glm::vec3数组中定义10个立方体位置

glm::vec3 cubePositions[] = {
  glm::vec3( 0.0f,  0.0f,  0.0f), 
  glm::vec3( 2.0f,  5.0f, -15.0f), 
  glm::vec3(-1.5f, -2.2f, -2.5f),  
  glm::vec3(-3.8f, -2.0f, -12.3f),  
  glm::vec3( 2.4f, -0.4f, -3.5f),  
  glm::vec3(-1.7f,  3.0f, -7.5f),  
  glm::vec3( 1.3f, -2.0f, -2.5f),  
  glm::vec3( 1.5f,  2.0f, -2.5f), 
  glm::vec3( 1.5f,  0.2f, -1.5f), 
  glm::vec3(-1.3f,  1.0f, -1.5f)  
};

现在,在渲染循环中,我们调用glDrawArrays 10次,但这次在我们渲染之前每次传入一个不同的模型矩阵到顶点着色器中。
我们将会在游戏循环中创建一个小的循环用不同的模型矩阵渲染我们的物体10次。
我们也对每个箱子也稍微加了一点旋转:

for (unsigned int i = 0; i < 10; i++)
{
		glm::mat4 modelMat;
		modelMat = glm::translate(modelMat, cubePositions[i]);
		float angle = 20.0f * i;
		modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));


		myShader->use();
		glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0);
		glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 1);

		//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));
		glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
		glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
		glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glDrawArrays(GL_TRIANGLES, 0, 36);
}

在这里插入图片描述

2.2.1 关于DrawCall

我们调用glDrawArrays 10次,对应Unity中有个叫DrawCall的东西
我们调用glDrawArrays 10次也就是会发出10个DrawCall,每画一次物体就是呼叫一个DrawCall

2.2.2 main代码

mian.cpp的代码如下

#include <iostream>

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "Shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


//float vertices[] = {
//	//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
//		 0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
//		 0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
//		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
//		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
//};
//0 1 2   2 3 0
//unsigned int indices[] = {
//	0,1,2,
//	2,3,0
//};

float vertices[] = {
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

glm::vec3 cubePositions[] = {
  glm::vec3(0.0f,  0.0f,  0.0f),
  glm::vec3(2.0f,  5.0f, -15.0f),
  glm::vec3(-1.5f, -2.2f, -2.5f),
  glm::vec3(-3.8f, -2.0f, -12.3f),
  glm::vec3(2.4f, -0.4f, -3.5f),
  glm::vec3(-1.7f,  3.0f, -7.5f),
  glm::vec3(1.3f, -2.0f, -2.5f),
  glm::vec3(1.5f,  2.0f, -2.5f),
  glm::vec3(1.5f,  0.2f, -1.5f),
  glm::vec3(-1.3f,  1.0f, -1.5f)
};

void processInput(GLFWwindow* window){
	if (glfwGetKey(window, GLFW_KEY_ESCAPE )== GLFW_PRESS)
	{
		glfwSetWindowShouldClose(window, true);
	}
}

int main() {
	

	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	
	//Open GLFW Window
	GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL);
	if(window == NULL)
	{
		printf("Open window failed.");
		glfwTerminate();
		return - 1;
	}
	glfwMakeContextCurrent(window);

	//Init GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK) 
	{
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}
	
	glViewport(0, 0, 800, 600);
	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_CULL_FACE);
	//glCullFace(GL_BACK);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//unsigned int EBO;
	//glGenBuffers(1, &EBO);
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	// 颜色属性
	//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	//glEnableVertexAttribArray(1);
	// uv属性
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(2);

	stbi_set_flip_vertically_on_load(true);
	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);

	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	// 加载并生成纹理
	int width, height, nrChannel;
	unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		printf("Failed to load texture"); 
	}
	stbi_image_free(data);


	unsigned int TexBufferB;
	glGenTextures(1, &TexBufferB);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, TexBufferB);


	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	// 加载并生成纹理
	unsigned char *data2 = stbi_load("awesomeface.png", &width, &height, &nrChannel, 0);
	if (data2) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		printf("Failed to load texture");
	}
	stbi_image_free(data2);

	计算变换矩阵
	glm::mat4 trans;
 //   trans = glm::translate(trans, glm::vec3(1.0f, 0.0f, 0.0f));
	trans = glm::rotate(trans, glm::radians(45.0f), glm::vec3(0.0, 0.0, 1.0));
	glm::quat MyQuaternion = angleAxis(glm::radians(45.0f), glm::vec3(0.0, 0.0, 1.0));
	//glm::quat MyQuaternion = glm::quat(cos(glm::radians(22.5f)),0,0,sin(glm::radians(22.5f)));
	//glm::mat4 RotationMatrix = glm::mat4_cast(MyQuaternion);
	//trans *= RotationMatrix;
	//trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
	
	//M
	//glm::mat4 modelMat;
	//modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
	//V
	glm::mat4 viewMat;
	// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
	viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
	//P
	glm::mat4 projMat;
	projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

	while (!glfwWindowShouldClose(window)) 
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexBufferA);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, TexBufferB);

		glBindVertexArray(VAO);
		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		for (unsigned int i = 0; i < 10; i++)
		{
			glm::mat4 modelMat;
			modelMat = glm::translate(modelMat, cubePositions[i]);
			float angle = 20.0f * i;
			modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));


			myShader->use();
			glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0);
			glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 1);

			//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

			//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
			glDrawArrays(GL_TRIANGLES, 0, 36);
		}


		

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwTerminate();
	return 0;
	
}


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值