主要内容:
画一个立方体,然后设计一个camera类。本文涉及的内容有点多,请读者耐心的看下去,这里不仅有软件的配置,还有shader的应用,以及摄像机类的编写,还包括一些矩阵变换之类的,闲话少说,具体内容一步一步展开。
按照我们的思路,首先需要的是数据,顶点的数据,画一个立方体,需要八个顶点的数据。一个正方体如何画出来,需要一个面一个面的哈,那么正方体有6个面,而每个面呢?是一个正方形,我们把正方形划分为两个三角形,这个三角形是opengl中最小的片元了。如下图所示:
讲到这里,可能有点过早了,所以还是要从基本的三角形开始。
三角形的顶点数据准备:
数据如下:
float vertices[]={
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.0f,0.5f,0.0f
};
我们为什么画一个逆时针的指示图呢?这个只是代表,上面的数组的是从左下角开始到右下角再到上顶点结束。这个数据是逆时针的,与是否是正方向无关。
然后我们定义一个顶点着色器,负责接收这些数据。
vertexShader内容如下:
#version 330 core
layout (location=0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);
}
layout=0,因为这里只有一个顶点位置属性,所以location=0。
shader用于接收数据,那么如何将数据传输到shader呢?是通过显卡内部的一块缓存区域,暂时这么理解。对应到程序,那就是vbo啦。
首先,我们创建一个vbo对象:
unsigned int VBO;
glGenBuffers(1,&VBO);
定义一个无符号数字,用于保存创建的VBO的唯一标识。
然后绑定这个对象:
glBindBuffer(GL_ARRAY_BUFFER,VBO);
然后拷贝数据到这个对象:
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
然后数据是拷贝进去了,那么如何解析这些数据呢?
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3sizeof(float),(void)0);
第1个参数:0,因为这里只有一个属性,所以从0开始。
第2个参数:3,表示某个属性包含的分量的个数。这里有3个顶点,但是每个顶点包含3个分量,也就是x、y、z。
第3个参数:顶点属性中每个分量的数据类型是什么,这里是float类型。
第4个参数:表示是否将数据标准化,这里不需要,为啥不需要,我也不知道。
第5个参数:是每个属性的长度,也就是步长,什么是步长,也就是第一个属性从多少道多少,第2个属性从多少到多少,每当我们要新的属性的时候,一步要走多少,这里每个属性包含3个float长度的数据,所以步长为3。
第6个参数:表示属性的偏移,因为我们这里的属性只有一个也就是顶点位置属性,没有其他的属性,所以这里的偏移为0。
接下来就是将这个属性的状态变为使能状态,也就是可用的状态。
glEnableVertexAttribArray(0);
下面是创建shader,并做连接处理,以渲染这个三角形。
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
然后将上面的shader字符串与这个shader相互关联起来。
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
编译这个shader。
glCompileShader(vertexShader);
然后我们在创建片段着色器。顶点着色器,负责接收我们的顶点数据,对顶点处理之后,就要传递给片段着色器进行上色了,说到这里,我们就要回顾下渲染管线的几个阶段了。
最初的阶段,我们只有各个顶点的数据,顶点数据这里说明一下,它不仅仅可以包含位置信息,还可以有颜色信息、纹理坐标信息。这些都是以单独的数据存在的比如上面我们的三角形的三个顶点数据。
有了各自独立的顶点数据,我们并没有给出任何的指令要求将这些数据呈现出来,到底是一条线、还是一个三角形、还是就是一个顶点,于是接下来的渲染管线做得就是组装顶点。这个阶段之后,我们就知道,到底得到了什么样的图形了。
下一个阶段,就是几何渲染阶段,这个阶段主要是产生新的顶点,然后呢将新生成的顶点连接起来,得到新的形状。
几何阶段之后,就是离散化的过程,也就是所谓的光栅化阶段,啥意思呢?我们上面得到的就是一整块的面片,一个面,那么在电脑中如何染出这一个面呢?我们知道屏幕上只有一个一个点,由这些点组合而成,所以在几何阶段之后就是将一个面,分成多个点的过程。也就是说,我们有最初的少量的点,最终变换处理多个点,由这多个点,展示一个面。再接下来就是上色的过程,我们要对这些离散出来的点进行染色。
再接下来就是,颜色的最后处理,比如透明测试、混合处理等,最终把满足条件的、混合处理之后的颜色输出。
经过这些阶段之后,由最初的顶点数据变为了有颜色的图形。
具体的图示如下:
所以经过上面的回顾之后,我们要自定义的,自己实现的,就是顶点处理阶段和像素处理阶段。
那么接下来我们就看看像素着色器的处理方式:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f,0.5f,0.2f,1.0f);
}
功能只有一个就是简单的着色。
下面是创建一个片段着色器的代码:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
下面是将两个shader连接起来,并且使用shader program程序进行编译。
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
到这里,似乎可以画出一个三角形了,但是还是少了主循环。
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
解释上面的代码:
1、processInput(window);
处理所有的输入,比如下面的输入实现。
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
}
这个函数的主要目的是处理键盘的输入,比如上面的函数,当我们输入Esc键之后,就会关闭窗口。
2、
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
这两句代码的意思是使用glClearColor指定的颜色来设置缓冲区。
3、glUseProgram(shaderProgram);
使用shader的应用程序。
4、glBindVertexArray(VAO);
绑定一个VAO对象,那么接下来的VBO、EBO、其他的解析属性的操作都将储存在VAO对象中,简化绑定操作。
5、glDrawArrays(GL_TRIANGLES,0,3);
指定opengl画三角形,0表示从数据的那个位置开始,3表示画3个顶点的数据。
6、glfwSwapBuffers(window);
下面的我们来看看如何把窗口显示出来,我们使用的glfw的方式显示窗口。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
while (!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
上面的这段代码,是最简单的将窗口显示出来的方式。我们试图将上面的代码再次精简,发现已经不能再精简了,比如我们把glfwInit();改为如下的代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
//glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
return 0;
}
发现没有任何窗口,只有一个控制台。而如果将glfwInit()打开,运行之后会发现白色窗口一闪而过。原因是我们少了一个主循环,所以经过上面的测试,我们将while循环加到上面去,最终运行之后得到一个窗口了。
那么下面是不是就要将shader使用上去了呢?再次整合代码如下:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化和创建窗口
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
//设置
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//三角形的三个顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
至此我们完成了一个三角形的绘制了。上面的代码只考虑的正确的情况,对于编译shader的失败等等其他报错信息我们一概不问,只关心起无意外的情况。
接下来我们应该画一个矩形,接着上面问题,一个矩形包含两个三角形,我们可以再增加一个顶点数据,如下所示:
数据如下:
//三角形的三个顶点数据
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
};
然后只要改动while循环中的一句代码即可:
glDrawArrays(GL_TRIANGLES, 0, 6);
意思是从0索引开始画6个点组成三角形。这里你也可以把0改为其他的值,比如3看看画的形状是什么,答案是上半个三角形。完整的代码如下:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化和创建窗口
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
//设置
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//三角形的三个顶点数据
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 3, 6);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
那么下面我们就来介绍一下如何使用ebo的方式来减少重复数据的存储问题,例如上面的矩形四个顶点,而使用画三角形的方式画矩形的时候,有两个顶点重复存储了,分别是B和D两点。此时我们用EBO的方式记录顶点的索引来解决这个问题。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化和创建窗口
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
//设置
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//三角形的三个顶点数据
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
-0.5f,0.5f,0.0f,
};
unsigned int indices[] = {
0, 1, 2, //第一个三角形
0, 2, 3 //第二个三角形
};
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);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //改为glDrawElements即可。6为索引个数,0表示偏移。
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
看看结果:
下面,我们再来看看,能否画一个立方体呢?在画一个立方体之前,我们要思考几个问题。
一个是数据问题,数据是怎样的,前面分析过,立方体,六个面,每个面两个三角形,也就是12个三角形,每个三角形3个顶点,于是要定义36个顶点。
如上图所示,我们对每个面再分割为两个三角形,于是有了下面的数组:
//三角形的三个顶点数据
float vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
于是我们将上面的画三角形的程序稍微改动一下,有如下的代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化和创建窗口
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
//设置
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//三角形的三个顶点数据
float vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
其中唯一改动的是glDrawArrays(GL_TRIANGLES, 0, 36);变为了画36个顶点。
那么运行的结果为:
并没有出现立方体,而只是一个平面,为什么呢?因为我们是正对着物体的,后面的、侧面的、左右两的两个面都看不到,所以只看到一个面,于是我们想把这个立方体自动旋转一下看看有没有立方体的效果。这就涉及到旋转的操作了,那么旋转怎么做呢?
在前面http://blog.csdn.net/wodownload2/article/details/72637897
章节中我们已经推到了,如果是左手坐标系,绕y轴旋转的旋转矩阵的推导过程,那么这个矩阵最终为:
写成四维矩阵的即为:
那么在opengl中如何构建这个矩阵,又如何将这个矩阵和shader中的顶点着色器进行交互呢?这将是我们下面讨论的内容。
首先来看opengl中单位矩阵的产生方法,就是使用一句代码:glm::mat4 trans;即可,如下所示:
下面就是这个单位矩阵进行旋转一定的角度比如30度,代码如下:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
using namespace std;
int main()
{
glm::mat4 trans;
for (int i = 0; i < 4; ++i)
{
cout << trans[i].x << " " << trans[i].y << " " << trans[i].z << " " << trans[i].w << endl;
}
trans = glm::rotate(trans, 30.0f, glm::vec3(0.0f, 1.0f, 0.0f));
cout << endl;
for (int i = 0; i < 4; ++i)
{
cout << trans[i].x << " " << trans[i].y << " " << trans[i].z << " " << trans[i].w << endl;
}
return 0;
}
经过验证,代入上面的旋转矩阵,验证正确:
然后下面是将这个矩阵怎么传递给shader的顶点着色器。
在顶点着色器中我们定义一个类型为uniform类型的变量,这个变量的可以在C++程序中进行赋值,也就是说shader程序的和c++程序的交互可以通过uniform类型的变量建立起桥梁。比如下面的顶点着色器程序:
#version 330 core
layout (location=0) in vec3 aPos;
uniform mat4 transform;
void main()
{
gl_Position = transform * vec4(aPos,1.0);
}
而片段着色器仍然使用上面的代码,如下:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
上面的片段着色器仅仅是赋了一个颜色而已。
下面紧接着,顶点着色器中有了uniform类型mat4变量,那么c++程序中如何把这个矩阵传递给shader呢?
glm::mat4 transform;
transform = glm::rotate(transform, 10*(float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); //绕y轴旋转
unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
这个c++程序中,创建单位矩阵,然后绕y轴旋转指定角度,然后通过glGetUniformLocation找到shader中的transform变量,然后将矩阵与此变量关联即可。总的代码如下:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
using namespace std;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"uniform mat4 transform;\n"
"void main()\n"
"{\n"
" gl_Position = transform * vec4(aPos,1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
//初始化和创建窗口
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
//设置
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
//三角形的三个顶点数据
//三角形的三个顶点数据
float vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glm::mat4 transform;
transform = glm::rotate(transform, 10*(float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); //绕y轴旋转
unsigned int transformLoc = glGetUniformLocation(shaderProgram, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
运行的结果是,你会看到矩形由宽变窄,又由窄变宽。你就会感到很奇怪,所以我们稍微改下旋转的轴,让立方体绕着(1.0,1.0,0.0)这个轴旋转即可。
glm::mat4 transform;
transform = glm::rotate(transform, 10*(float)glfwGetTime(), glm::vec3(1.0f, 1.0f, 0.0f));
结果你可以自己将这段代码贴上去即可,这里贴上一个图:
ok,到此,我们已经将一个立方体完整的画出来了,并且实现了旋转操作。
那么下面我们就要对这个立方体进行贴一张图片了,也就是使用纹理,让其变得好看一点,而不是单一的一个颜色。