3D Computer Grapihcs Using OpenGL - 11 Model View Projection Matrices
本节我们将绘制一个3维物体,立方体。
如果要渲染3D物体,我们需要了解MVP(Model View Projection),它表示三个转换矩阵。实际上这个名字不够明确,更加确切的释义如下:
- Model - Model to World 模型空间到世界空间
- View - World to View 世界空间到视图空间
- Projection - View to Projection 视图空间到投影空间
要实现这三个转换矩阵,我们需要借助glm数学库提供的一些方便的结构体和函数。
重构
我们先对程序结构进行修改,对工程右键>Add > New Filter, 创建一个Primitives 文件夹,在其中创建两个文件,一个Vertex.h,一个ShapeData.h
Vertex.h中定义了一个Vertex结构体,它包含两个glm::vec3成员,分别表示位置和颜色。
1 #pragma once 2 #include <glm\glm.hpp> 3 4 struct Vertex 5 { 6 glm::vec3 position; 7 glm::vec3 color; 8 };
ShapeData.h中定义了一个ShapeData结构体,包含四个成员变量,分别是
- Vertex* 类型:顶点数组指针
- Gluint类型:顶点数量
- GLushort* 类型:索引数组指针
- GLuint 类型:索引数组长度
另外还提供了构造函数,清理函数
1 #pragma once 2 #include <GL\glew.h> 3 #include "Vertex.h" 4 5 struct ShapeData 6 { 7 ShapeData() : 8 vertices(0), numVertices(0), indices(0), numIndices(0) {} 9 10 Vertex* vertices; 11 GLuint numVertices; 12 GLushort* indices; 13 GLuint numIndices; 14 15 GLsizeiptr vertexBufferSize() const 16 { 17 return numVertices * sizeof(Vertex); 18 } 19 GLsizeiptr indexBufferSize() const 20 { 21 return numIndices * sizeof(GLushort); 22 } 23 24 void cleanUp() 25 { 26 delete[] vertices; 27 delete[] indices; 28 numVertices = numIndices = 0; 29 } 30 };
此外还加入了一个新的类,ShapeGenerator
ShapeGenerator.h
1 #pragma once 2 #include <ShapeData.h> 3 4 class ShapeGenerator 5 { 6 public: 7 static ShapeData makeCube(); 8 };
ShapeGenerator.cpp
1 #include "ShapeGenerator.h" 2 #include "Vertex.h" 3 4 #define NUM_ARRAY_ELEMENTS(a) sizeof(a) / sizeof(*a) 5 6 ShapeData ShapeGenerator::makeCube() 7 { 8 ShapeData ret; 9 Vertex stackVerts[]= 10 { 11 glm::vec3(-1.0f, +1.0f, +1.0f), //0 12 glm::vec3(+1.0f, 0.0f, 0.0f), //Color 13 glm::vec3(+1.0f, +1.0f, +1.0f), //1 14 glm::vec3(0.0f, +1.0f, 0.0f), //Color 15 glm::vec3(+1.0f, +1.0f, -1.0f), //2 16 glm::vec3(0.0f, 0.0f, +1.0f), //Color 17 glm::vec3(-1.0f, +1.0f, -1.0f), //3 18 glm::vec3(+1.0f, +1.0f, +1.0f), //Color 19 20 glm::vec3(-1.0f, +1.0f, -1.0f), //4 21 glm::vec3(+1.0f, 0.0f, +1.0f), //Color 22 glm::vec3(+1.0f, +1.0f, -1.0f), //5 23 glm::vec3(0.0f, 0.5f, 0.2f), //Color 24 glm::vec3(+1.0f, -1.0f, -1.0f), //6 25 glm::vec3(0.8f, 0.6f, 0.4f), //Color 26 glm::vec3(-1.0f, -1.0f, -1.0f), //7 27 glm::vec3(0.3f, +1.0f, +0.5f), //Color 28 29 glm::vec3(+1.0f, +1.0f, -1.0f), //8 30 glm::vec3(0.2f, 0.5f, 0.2f), //Color 31 glm::vec3(+1.0f, +1.0f, +1.0f), //9 32 glm::vec3(0.9f, 0.3f, 0.7f), //Color 33 glm::vec3(+1.0f, -1.0f, +1.0f), //10 34 glm::vec3(0.3f, 0.7f, 0.5f), //Color 35 glm::vec3(+1.0f, -1.0f, -1.0f), //11 36 glm::vec3(0.5f, 0.7f, 0.5f), //Color 37 38 glm::vec3(-1.0f, +1.0f, +1.0f), //12 39 glm::vec3(0.7f, 0.8f, 0.2f), //Color 40 glm::vec3(-1.0f, +1.0f, -1.0f), //13 41 glm::vec3(0.5f, 0.7f, 0.3f), //Color 42 glm::vec3(-1.0f, -1.0f, -1.0f), //14 43 glm::vec3(0.8f, 0.6f, 0.4f), //Color 44 glm::vec3(-1.0f, -1.0f, +1.0f), //15 45 glm::vec3(0.3f, +1.0f, +0.5f), //Color 46 47 glm::vec3(+1.0f, +1.0f, +1.0f), //16 48 glm::vec3(0.7f, 0.8f, 0.2f), //Color 49 glm::vec3(-1.0f, +1.0f, +1.0f), //17 50 glm::vec3(0.5f, 0.7f, 0.3f), //Color 51 glm::vec3(-1.0f, -1.0f, +1.0f), //18 52 glm::vec3(0.8f, 0.6f, 0.4f), //Color 53 glm::vec3(+1.0f, -1.0f, +1.0f), //19 54 glm::vec3(0.3f, +1.0f, +0.5f), //Color 55 56 glm::vec3(+1.0f, -1.0f, -1.0f), //20 57 glm::vec3(0.7f, 0.8f, 0.2f), //Color 58 glm::vec3(-1.0f, -1.0f, -1.0f), //21 59 glm::vec3(0.5f, 0.7f, 0.3f), //Color 60 glm::vec3(-1.0f, -1.0f, +1.0f), //22 61 glm::vec3(0.8f, 0.6f, 0.4f), //Color 62 glm::vec3(+1.0f, -1.0f, +1.0f), //23 63 glm::vec3(0.3f, +1.0f, +0.5f), //Color 64 }; 65 66 ret.numVertices = NUM_ARRAY_ELEMENTS(stackVerts); 67 ret.vertices = new Vertex[ret.numVertices]; 68 memcpy(ret.vertices, stackVerts, sizeof(stackVerts)); 69 70 unsigned short stackIndices[] = 71 { 72 0,1,2,0,2,3, 73 4,5,6,4,6,7, 74 8,9,10,8,10,11, 75 12,13,14,12,14,15, 76 16,17,18,16,18,19, 77 20,22,21,20,23,22, 78 }; 79 80 ret.numIndices = NUM_ARRAY_ELEMENTS(stackIndices); 81 ret.indices = new GLushort[ret.numIndices]; 82 memcpy(ret.indices, stackIndices, sizeof(stackIndices)); 83 return ret; 84 }
主要作用是提供了一个静态方法 makeCube,返回一个立方体的数据。
修改MyGlWindow类
1 #include <gl\glew.h> 2 #include "MyGlWindow.h" 3 #include <iostream> 4 #include <fstream> 5 #include <glm\gtc\matrix_transform.hpp> 6 #include <ShapeGenerator.h> 7 8 9 10 GLuint programID; 11 GLuint numIndices; 12 13 void MyGlWindow::sendDataToOpenGL() 14 { 15 16 ShapeData shape = ShapeGenerator::makeCube(); 17 18 GLuint vertexBufferID; 19 glGenBuffers(1, &vertexBufferID); 20 glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); 21 glBufferData(GL_ARRAY_BUFFER, shape.vertexBufferSize(), shape.vertices, GL_STATIC_DRAW); 22 23 GLuint indexBufferID; 24 glGenBuffers(1, &indexBufferID); 25 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); 26 glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape.indexBufferSize(), shape.indices, GL_STATIC_DRAW); 27 28 glEnableVertexAttribArray(0); 29 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0); 30 31 glEnableVertexAttribArray(1); 32 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(GLfloat) * 3)); 33 34 numIndices = shape.numIndices; 35 shape.cleanUp(); 36 37 } 38 39 void MyGlWindow::installShaders() 40 { 41 GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER); 42 GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); 43 44 std::string tmp = ReadShaderCode("VertexShaderCode2.glsl"); 45 const char* vertexShaderCode = tmp.c_str(); 46 glShaderSource(vertexShaderID, 1, &vertexShaderCode, 0); 47 48 tmp = ReadShaderCode("FragmentShaderCode2.glsl"); 49 const char* fragmentShaderCode = tmp.c_str(); 50 glShaderSource(fragmentShaderID, 1, &fragmentShaderCode, 0); 51 52 glCompileShader(vertexShaderID); 53 glCompileShader(fragmentShaderID); 54 55 programID = glCreateProgram(); 56 glAttachShader(programID, vertexShaderID); 57 glAttachShader(programID, fragmentShaderID); 58 59 glLinkProgram(programID); 60 61 glUseProgram(programID); 62 } 63 64 void MyGlWindow::initializeGL() 65 { 66 glewInit(); 67 glEnable(GL_DEPTH_TEST); 68 sendDataToOpenGL(); 69 installShaders(); 70 } 71 72 void MyGlWindow::paintGL() 73 { 74 glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); 75 glViewport(0, 0, width(), height()); 76 77 //更新:最新版本的glm中,glm::mat4()生成的是不是单位矩阵,而是零矩阵,这里要使用glm::mat4(1.0f)才可以 78 glm::mat4 modelTransformMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f,-3.0f)); 79 glm::mat4 projectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f); 80 81 GLint modelTransformUniformLocation = glGetUniformLocation(programID, "modelMatrix"); 82 GLint projectionMatrixUniformLocation = glGetUniformLocation(programID, "projectionMatrix"); 83 84 glUniformMatrix4fv(modelTransformUniformLocation, 1, GL_FALSE, &modelTransformMatrix[0][0]); 85 glUniformMatrix4fv(projectionMatrixUniformLocation, 1, GL_FALSE, &projectionMatrix[0][0]); 86 87 glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0); 88 89 } 90 91 92 std::string MyGlWindow::ReadShaderCode(const char* fileName) 93 { 94 std::ifstream myInput(fileName); 95 if (!myInput.good()) 96 { 97 std::cout << "File failed to load..." << fileName; 98 exit(1); 99 } 100 return std::string( 101 std::istreambuf_iterator<char>(myInput), 102 std::istreambuf_iterator<char>()); 103 }
Vertex Shader :
1 #version 430 2 3 in layout(location=0) vec3 position; 4 in layout(location=1) vec3 vertexColor; 5 6 uniform mat4 modelMatrix; 7 uniform mat4 projectionMatrix; 8 9 out vec3 passingColor; 10 11 void main() 12 { 13 vec4 v = vec4(position,1.0); 14 vec4 newPosition = modelMatrix * v; 15 gl_Position = projectionMatrix * newPosition; 16 passingColor= vertexColor; 17 }
Fragment Shader:
1 #version 430 2 3 in vec3 passingColor; 4 out vec4 finalColor; 5 6 7 void main() 8 { 9 finalColor = vec4(passingColor,1.0); 10 }
注意MyGlWindow的78-85行,是使用Uniform 变量的通用方法,使用的是Vertex Shader中第6-7行的两个uniform。
使用Uniform变量的步骤总结:
- 使用glGetUniformLocation获取Uniform变量的ID,并储存在一个GLint 变量中
- 使用glUnifomxxxx()类的函数和刚才得到的ID给Uniform赋值。
另外要注意85-86行,函数的最后一参数需要一个const GLfloat * 类型的变量,所以我们使用[0][0]获取矩阵的第一个元素,它是个GLfloat类型的,再对他使用取地址符&得到它的地址。
编译运行以后得到一个平面(实际上是立方体的一个面):
我们在最开始提到了3个矩阵,但是这里只用到了两个,实际上少了第二个矩阵,World to View矩阵,这也正是为什么我们现在无法移动观察视角的原因,我们的相机被假设在世界原点,朝向-z的方向看去,这是默认的设置。后面我们会学习world to view的转换矩阵。