关键词:C++、OpenGL
未完待施工…
一、OpenGL安装与使用
Reference:计算机图形学编程(使用OpenGL和C++)(第2版)
1.1 认识OpenGL
在 C++中使用 OpenGL 需要配置多个库。此处,使用到:
C++开发环境;
OpenGL / GLSL:2004 年,2.0 版本中引入了 OpenGL 着色语言 GLSL,使得“着色器程序”可以被直接安装到图形管线的各个阶段并执行;
窗口管理库:OpenGL会将图像渲染到一个帧缓冲区,然后由计算机将帧缓冲区的内容绘制到屏幕的窗口中,GLFW 库是最流行的选择之一。
扩展库:常用如 GLEW 库;
数学库:3D 图形编程会大量使用向量和矩阵代数,可使用 OpenGL Mathematics(GLM)库、 Eigen 库或 vmath 库;
纹理图像加载库:比如 FreeImage、DevIL、GLI、Glraw 和 SOIL。
1.2 安装和配置OpenGL
基于Visual Studio 2022进行
1.2.1 准备OpenGL/GLSL
了解计算机支持什么版本的OpenGL,可使用 GLView 查看。
1.2.2 准备GLFW
下载 GLFW 源代码,下载地址
使用 CMake 进行编译源码
接着找到 GLFW.sln,使用VS打开并编译为64位应用程序。
构建完成后得到下面两个内容:lib静态库文件和include文件夹
1.2.3 准备GLEW
下载 GLEW 的二进制文件,下载地址 。解压后内有库文件和头文件等。
1.2.4 准备GLM
下载 GLM 压缩包,下载地址 ,解压后即可。
1.2.5 准备SOIL2
下载并解压 premake ,包含 premake5.exe。
下载 SOIL2 的源码,下载地址 ,解压。
将 premake5.exe 复制到 soil2 目录下。
在 soil2 目录下使用cmd,运行命令:
打开 make/windows 文件夹的 SOIL2.sln 文件,右键 soil2-static-lib 在 x64 下选择进行生成。
1.2.6 准备共享的lib和include
选择合适的位置创建文件夹,并设置 lib 和 include 子文件夹。
文件结构如下图:
1.3 在VS中配置OpenGL项目
创建 VS 自定义项目模板
启动 VS, 创建空白项目,选择 x64。
在 Debug 模式下,进入“项目属性”,在“VC++”目录下的包含目录添加上共享文件夹的 include 文件夹;在“链接器”下,“常规->附加库目录”(或“VC+±>库目录”)添加上共享文件夹的 lib 文件夹,“输入->附加依赖项”添加以下文件名: glfw3.lib
、glew32.lib
、soil2-debug.lib
和 opengl32.lib
。
在 Release 模式下,重复上述步骤。
“项目->导出模板”,选择项目模板,命名自定义,此处为“OpenGL project”。
创建 OpenGL C++ 项目时,启动 VS 时,点击“新建项目”,在左上方选择 OpenGL 模板即可使用。
需要注意的是,在新建OpenGL C++项目时,需要把 glew32.dll
放到项目目录下。
1.4 创建一个窗口
这部分可以用来测试上面的配置是否成功。下面给出一个 OpenGL C++ 创建窗口的代码:
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> GLFWwindow *window; using namespace glm;int main () { if (!glfwInit ()) { return -1 ; } glfwWindowHint (GLFW_SAMPLES, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3 ); glfwWindowHint (GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); window = glfwCreateWindow (1024 , 768 , "New Window" , NULL , NULL ); if (window == NULL ) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) { glfwTerminate (); return -1 ; } glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE); glClearColor (0.0f , 1.0f , 0.0f , 0.0f ); do { glClear (GL_COLOR_BUFFER_BIT); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose (window) == 0 ); glfwTerminate (); return 0 ; }
运行结果:
二、OpenGL图像管线
C++/OpenGL 应用程序的一个重要任务是让程序员的 GLSL 代码运行于 GPU 上。
OpenGL 提供了一个多级图形管线,可以使用 GLSL 语言进行部分编程。GLSL 是一种着色器语言。着色器语言主要运行于 GPU 上,在图形管线上下文中。
2.1 管线
OpenGL 图像管线是 OpenGL 的核心,它是一个图像处理的管线。图像管线的工作原理是将图像数据从原始的纹理中渲染到屏幕上。
3D 图形编程会使用管线的概念,在管线中,将 3D 场景转换成 2D 图形的过程被分割成许多步骤。
C++/OpenGL 应用程序发送图形数据到顶点着色器,随着管线处理,最终生成在显示器上显示的像素点。
2.1.1 第一个C++/OpenGL应用程序
即详细解释之前的创建窗口代码
该程序将会使用一些扩展库:GLEW、GLM、SOIL2 和 GLFW。
GLFW 库提供了 GLFWwindow 类,可在其上进行3D场景绘制。
该程序的大概操作有:
初始化 GLFW
1 2 3 4 5 if (!glfwInit ()){ return -1 ; }
创建窗口的设置
1 2 3 glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 );glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 );
创建一个 GLFWwindow 实例并创建窗口
1 2 3 4 5 6 7 8 9 10 11 GLFWwindow *window = glfwCreateWindow (1024 , 768 , "New Window" , NULL , NULL ); if (window == NULL ){ glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window);
设置窗口属性
1 2 3 4 5 6 glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE);glClearColor (0.0f , 1.0f , 0.0f , 0.0f );
初始化 GLEW 库
1 2 3 4 5 6 if (glewInit () != GLEW_OK){ glfwTerminate (); return -1 ; }
循环显示窗口
1 2 3 4 5 6 7 8 9 10 do { glClear (GL_COLOR_BUFFER_BIT); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose (window) == 0 );
退出程序
1 2 3 glfwDestroyWindow (window);glfwTerminate ();
完整程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> using namespace glm;int main () { if (!glfwInit ()) { return -1 ; } glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (1024 , 768 , "New Window" , NULL , NULL ); if (window == NULL ) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE); glClearColor (0.0f , 1.0f , 0.0f , 0.0f ); if (glewInit () != GLEW_OK) { glfwTerminate (); return -1 ; } do { glClear (GL_COLOR_BUFFER_BIT); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose (window) == 0 ); glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
2.2 顶点着色器和片段着色器
多数 3D 模型通常由多个三角形图元构成。如点、线、三角形这些简单的图形称作图元。
图元由顶点组成。
在加载顶点之前,C++/OpenGL 应用程序必须编译并链接合适的 GLSL 顶点着色器和片段着色器程序,之后将它们载入管线。
比如绘画三角形时,可以通过该函数实现:
1 glDrawArrays (GLenum mode, Glint first, Glsizei count);
mode:表示图元的类型;
first:表示从哪个顶点开始绘制;
count:表示绘制的顶点数。
以绘画三角形为例子,步骤如下:
注: GLuint
是 OpenGL 提供的 unsigned int
初始化阶段,对 GLFW 和 GLEW 库进行初始化,同时创建窗口;
创建顶点数组对象VAO和缓存区;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 GLuint VertexArrayID; glGenVertexArrays (1 , &VertexArrayID);glBindVertexArray (VertexArrayID);static const GLfloat g_vertex_buffer_data[] = { -1.0f , -1.0f , 0.0f , 1.0f , -1.0f , 0.0f , 0.0f , 1.0f , 0.0f }; GLuint vertexbuffer; glGenBuffers (1 , &vertexbuffer); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer);glBufferData (GL_ARRAY_BUFFER, sizeof (g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
循环中绘画三角形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 do { glClear (GL_COLOR_BUFFER_BIT); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 ); glDrawArrays (GL_TRIANGLES, 0 , 3 ); glDisableVertexAttribArray (0 ); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS and glfwWindowShouldClose (window) == 0 );
后处理,删除申请的资源
1 2 3 4 glDeleteBuffers (1 , &vertexbuffer);glDeleteVertexArrays (1 , &VertexArrayID);glfwTerminate ();
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> int main () { if (!glfwInit ()) { return -1 ; } glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (1024 , 768 , "triangle" , NULL , NULL ); if (window == NULL ) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE); glClearColor (0.0f , 1.0f , 0.0f , 0.0f ); if (glewInit () != GLEW_OK) { glfwTerminate (); return -1 ; } GLuint VertexArrayID; glGenVertexArrays (1 , &VertexArrayID); glBindVertexArray (VertexArrayID); static const GLfloat g_vertex_buffer_data[] = { -1.0f , -1.0f , 0.0f , 1.0f , -1.0f , 0.0f , 0.0f , 1.0f , 0.0f }; GLuint vertexbuffer; glGenBuffers (1 , &vertexbuffer); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glBufferData (GL_ARRAY_BUFFER, sizeof (g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW); do { glClear (GL_COLOR_BUFFER_BIT); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 ); glDrawArrays (GL_TRIANGLES, 0 , 3 ); glDisableVertexAttribArray (0 ); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS and glfwWindowShouldClose (window) == 0 ); glDeleteBuffers (1 , &vertexbuffer); glDeleteVertexArrays (1 , &VertexArrayID); glfwTerminate (); return 0 ; }
效果展示:
这个三角形是白色的,因为其并没有进行任何着色。
使用顶点着色器和片段着色器可以进行着色。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> GLuint createShadeProgram () { const char *vshaderSource = "#version 460 core\n" "layout(location = 0) in vec3 vertexPosition_modelspace;\n" "void main()\n" "{ gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; }" ; const char *fshaderSource = "#version 460 core\n" "out vec3 color;\n" "void main()\n" "{ color = vec3(0, 0, 0.3); }" ; GLuint vShader = glCreateShader (GL_VERTEX_SHADER); GLuint fShader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vShader, 1 , &vshaderSource, NULL ); glShaderSource (fShader, 1 , &fshaderSource, NULL ); glCompileShader (vShader); glCompileShader (fShader); GLuint vProgram = glCreateProgram (); glAttachShader (vProgram, vShader); glAttachShader (vProgram, fShader); glLinkProgram (vProgram); return vProgram; } int main () { if (!glfwInit ()) { return -1 ; } glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (1024 , 768 , "triangle" , NULL , NULL ); if (window == NULL ) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE); glClearColor (0.0f , 1.0f , 0.0f , 0.0f ); if (glewInit () != GLEW_OK) { glfwTerminate (); return -1 ; } GLuint VertexArrayID; glGenVertexArrays (1 , &VertexArrayID); glBindVertexArray (VertexArrayID); static const GLfloat g_vertex_buffer_data[] = { -1.0f , -1.0f , 0.0f , 1.0f , -1.0f , 0.0f , 0.0f , 1.0f , 0.0f }; GLuint vertexbuffer; glGenBuffers (1 , &vertexbuffer); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glBufferData (GL_ARRAY_BUFFER, sizeof (g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW); GLuint programID = createShadeProgram (); do { glClear (GL_COLOR_BUFFER_BIT); glUseProgram (programID); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 ); glDrawArrays (GL_TRIANGLES, 0 , 3 ); glDisableVertexAttribArray (0 ); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS and glfwWindowShouldClose (window) == 0 ); glDeleteBuffers (1 , &vertexbuffer); glDeleteVertexArrays (1 , &VertexArrayID); glDeleteProgram (programID); glfwTerminate (); return 0 ; }
效果展示:
观察着色器部分的代码:
顶点着色器:所有顶点着色器的主要目标都是将顶点发送给管线。
1 2 3 4 "#version 460 core\n" "layout(location = 0) in vec3 vertexPosition_modelspace;\n" "void main()\n" "{ gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; }"
第一行表示OpenGL版本,同时也使用该版本的语法,我这里是4.6.0;
第二行
layout(location = 0)
指用来赋给 vertexPosition_modelspace
这个属性的缓冲区。每个顶点能有多种属性:位置,一种或多种颜色,一个或多个纹理坐标,等等;通过将 glvertexAttribPointer
函数的第一个参数值赋给layout,就完成了这一点。参数值“0”并不重要,它可以是12(但是不大于 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v)
);重要的是两边参数值保持一致
in
表示表明变量是输入变量;
vec3
在GLSL中是一个三维向量;
vertexPosition_modelspace
是变量名,将包含每个顶点着色器运行所需的顶点位置值。
第三行表示主函数,与C++语法相似;
第四行内置变量 gl_Position 用来设置顶点在 3D 空间中的坐标位置,并将其发送至下一个管线阶段。
片段着色器:所有片段着色器的目的都是给为要展示的像素赋予颜色。
1 2 3 4 "#version 460 core\n" "out vec3 color;\n" "void main()\n" "{ color = vec3(0, 0, 0.3); }"
第一行表示OpenGL版本,同时也使用该版本的语法,我这里是4.6.0;
第二行 out 标签表明 color 变量是输出变量;
第三行表示主函数,与C++语法相似;
第四行表示设置颜色RGB值。
解释 glvertexAttribPointer
函数,函数使用如下:
1 glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 );
第一参数:指定要配置的顶点属性。在顶点着色器中使用 layout(location = 0)
定义了顶点属性的位置值,它可以把顶点属性的位置值设置为0,所以这里传入0;
第二参数:指定顶点属性的大小;
第三参数:指定数据的类型;
第四参数:是否希望数据被标准化(Normalize);
第五参数:步长,告诉在连续的顶点属性组之间的间隔;
第六参数:数组缓冲区偏移量。
此示例中着色器代码较短,可以直接编写到程序中。但当着色器代码较为复杂时,希望能文件分开,从文件中读入着色器代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 std::string readShaderSource (const char *filePath) { std::string content = "" ; std::ifstream fileStream (filePath, std::ios::in) ; std::string line = "" ; while (!fileStream.eof ()) { getline (fileStream, line); content.append (line + "\n" ); } fileStream.close (); return content; }
相应地 createShadeProgram()
改成:
1 2 3 4 std::string vs = readShaderSource ("vertex.glsl" ); std::string fs = readShaderSource ("fragment.glsl" ); const char *vshaderSource = vs.c_str ();const char *fshaderSource = fs.c_str ();
2.3 GLSL代码检查
编译和运行 GLSL 代码的过程与普通代码的不同,GLSL 的编译发生在 C++运行时。另外一个复杂的点是 GLSL 代码并没有运行在 CPU 中(它运行在 GPU 中),因此操作系统并不总能捕获 OpenGL 运行时的错误。着色器运行时错误的常见结果是输出屏幕上完全空白,根本没有输出。
但是 GLSL 函数 glGetShaderiv()
和 glGetProgramiv()
可提供有关编译过的 GLSL 着色器和程序的信息。
设计三个函数:
checkOpenGLError()
:既用于检测 GLSL 代码编译错误,又用于检测 OpenGL 运行时的错误。
printShaderLog()
:当 GLSL 代码编译失败时,显示 OpenGL 日志内容。
printProgramLog()
:当 GLSL 链接失败时,显示 OpenGL 日志内容。
详细代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 void printShaderLog (GLuint shader) { int len = 0 ; int chWrittn = 0 ; char *log; glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetShaderInfoLog (shader, len, &chWrittn, log); std::cout << "Shader Info: " << log << "\n" ; delete [] log; } } void printProgramLog (GLuint program) { int len = 0 ; int chWrittn = 0 ; char *log; glGetProgramiv (program, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetProgramInfoLog (program, len, &chWrittn, log); std::cout << "Program Info: " << log << "\n" ; delete [] log; } } bool checkOpenGLerror () { bool err = false ; int glErr = glGetError (); while (glErr != GL_NO_ERROR) { std::cout << "glError: " << glErr << "\n" ; err = true ; glErr = glGetError (); } return err; }
加入错误检查,创建着色器程序代码更改为:
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 GLuint createShadeProgram () { GLint vertCompiled, fragCompiled, linked; std::string vs = readShaderSource ("vertex.glsl" ); std::string fs = readShaderSource ("fragment.glsl" ); const char *vshaderSource = vs.c_str (); const char *fshaderSource = fs.c_str (); GLuint vShader = glCreateShader (GL_VERTEX_SHADER); GLuint fShader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vShader, 1 , &vshaderSource, NULL ); glShaderSource (fShader, 1 , &fshaderSource, NULL ); glCompileShader (vShader); checkOpenGLerror (); glGetShaderiv (vShader, GL_COMPILE_STATUS, &vertCompiled); if (vertCompiled == GL_FALSE) { std::cout << "vertex compilation failed\n" ; printShaderLog (vShader); } glCompileShader (fShader); checkOpenGLerror (); glGetShaderiv (fShader, GL_COMPILE_STATUS, &fragCompiled); if (fragCompiled == GL_FALSE) { std::cout << "fragment compilation failed\n" ; printShaderLog (fShader); } GLuint vProgram = glCreateProgram (); glAttachShader (vProgram, vShader); glAttachShader (vProgram, fShader); glLinkProgram (vProgram); checkOpenGLerror (); glGetProgramiv (vProgram, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { std::cout << "linking failed\n" ; printProgramLog (vProgram); } return vProgram; }
2.4 让图像动起来
实际上,在主函数的循环中,每一次循环都会刷新一次显示。但由于显示内容相同,故看起来是静态的。
对于上面的三角形,添加上些许偏移量,则会自由移动。
效果如下:
主函数代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 int main () { if (!glfwInit ()) { return -1 ; } glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (1024 , 768 , "triangle" , NULL , NULL ); if (window == NULL ) { glfwTerminate (); return -1 ; } glfwMakeContextCurrent (window); glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE); glClearColor (0.0f , 1.0f , 0.0f , 0.0f ); if (glewInit () != GLEW_OK) { glfwTerminate (); return -1 ; } GLuint VertexArrayID; glGenVertexArrays (1 , &VertexArrayID); glBindVertexArray (VertexArrayID); static const GLfloat g_vertex_buffer_data[] = { -1.0f , -1.0f , 0.0f , 1.0f , -1.0f , 0.0f , 0.0f , 1.0f , 0.0f }; GLuint vertexbuffer; glGenBuffers (1 , &vertexbuffer); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glBufferData (GL_ARRAY_BUFFER, sizeof (g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW); GLuint programID = createShadeProgram (); float x = 0.0f , inc = 0.001f ; do { glClear (GL_COLOR_BUFFER_BIT); glUseProgram (programID); x += inc; if (x > 1.0f ) inc = -0.001f ; if (x < -1.0f ) inc = 0.001f ; GLuint offsetLoc = glGetUniformLocation (programID, "offset" ); glProgramUniform1f (programID, offsetLoc, x); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 ); glDrawArrays (GL_TRIANGLES, 0 , 3 ); glDisableVertexAttribArray (0 ); glfwSwapBuffers (window); glfwPollEvents (); } while (glfwGetKey (window, GLFW_KEY_ESCAPE) != GLFW_PRESS and glfwWindowShouldClose (window) == 0 ); glDeleteBuffers (1 , &vertexbuffer); glDeleteVertexArrays (1 , &VertexArrayID); glDeleteProgram (programID); glfwTerminate (); return 0 ; }
而顶点着色器也需要根据偏移量作修改:
1 2 3 4 5 6 7 8 9 10 #version 460 core uniform float offset ;layout (location = 0 ) in vec3 vertexPosition_modelspace;void main(){ gl_Position = vec4 (vertexPosition_modelspace.x + offset , vertexPosition_modelspace.y, vertexPosition_modelspace.z, 1.0 ); }
其中 uniform
表示统一变量。
三、OpenGL数学基础
3.1 坐标系
3D 空间中常常使用三个坐标轴组成的坐标系,如左手坐标系和右手坐标系。
此学习记录使用右手坐标系
3.2 点
齐次坐标:具有四个值,分别是 x 坐标、y 坐标、z 坐标、w 坐标。其中 w 总是非零值。
OpenGL 中可用 vec3
或 vec4
类表示点数据。
3.3 向量
向量表示大小和方向。它们没有特定位置。在 3D 图形学中,向量一般用空间中的单个点表示,向量的大小是原点到该点的距离,方向则是原点到该点的方向。
GLSL 和 GLM 所提供的 vec3
和 vec4
类型既能用来存储点,又能用来存储向量。
设有向量 A = ( x , y , z ) A=(x,y,z) A = ( x , y , z ) 和 向量 B = ( u , v , w ) B=(u,v,w) B = ( u , v , w ) :
A ^ = A ∣ A ∣ = A x 2 + y 2 + z 2 \hat{A}=\frac A{\vert A\vert}=\frac A{\sqrt{x^2+y^2+z^2}}
A ^ = ∣ A ∣ A = x 2 + y 2 + z 2 A
向量点积: A ⋅ B = x u + y v + z w A\cdot B = xu+yv+zw A ⋅ B = x u + y v + z w 。GLM 库提供了函数 glm::dot(vec3,vec3)
向量叉积: A × B = ( y w − v z , u z − x w , x v − u y ) A\times B=(yw-vz, uz-xw, xv-uy) A × B = ( y w − v z , u z − x w , x v − u y ) 。GLM 库提供了函数 glm::cross(vec3,vec3)
3.3.1 点积的应用
点积的基本应用是求两向量夹角。
cos θ = V ⋅ W ∣ V ∣ ∣ W ∣ \cos\theta=\frac{V\cdot W}{\vert V\vert\vert W\vert}
cos θ = ∣ V ∣ ∣ W ∣ V ⋅ W
求向量的大小: V ⋅ V \sqrt{V\cdot V} V ⋅ V
判断向量是否正交,若正交,则 V ⋅ W = 0 V\cdot W=0 V ⋅ W = 0
判断向量是否平行,若平行,则 V ⋅ W = ∣ V ∣ ∣ W ∣ V\cdot W=\vert V\vert\vert W\vert V ⋅ W = ∣ V ∣ ∣ W ∣
3.3.2 叉积的应用
3.4 矩阵
矩阵式矩形的值的阵列,跟很多领域上的矩阵概念相似。
GLSL中的 mat4
数据类型便是用来存储 4 x 4 矩阵。同样 GLM 中的 mat4
类也是用来实例化并表示 4 x 4 矩阵。
比如单位矩阵是对角线值全为1,其余值都为0的矩阵。在 GLM 中,调用构造函数 glm::mat4 m(1.0f)
可以在变量 m 中生成单位矩阵。
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] (单位矩阵) \left[
\begin{matrix}
1 & 0 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & 1 & 0\\
0 & 0 & 0 & 1
\end{matrix}
\right]
\tag{单位矩阵}
⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ( 单 位 矩 阵 )
矩阵转置:矩阵的列与行交换。GLM 库和 GLSL 库都有转置函数,分别是 glm::transpose(mat4)
和 transpose(mat4)
。
矩阵加法:两个矩阵对应值相加。在GLSL中,+
作了重载,支持矩阵加法。
矩阵乘法:与线性代数中的矩阵乘法一致。
在 3D 图形学中,点与矩阵相乘通常将点视作列向量,并从右向左计算,得到点。
矩阵相乘也叫作合并。
N e w P o i n t = M 1 × [ M 2 × ( M 3 × P o i n t ) ] = ( M 1 × M 2 × M 3 ) × P o i n t NewPoint = M_1\times[M_2\times(M_3\times Point)]=(M_1\times M_2\times M_3)\times Point
N e w P o i n t = M 1 × [ M 2 × ( M 3 × P o i n t ) ] = ( M 1 × M 2 × M 3 ) × P o i n t
GLSL 和 GLM 都支持 vec4 与矩阵相乘,并使用 *
操作符表示。
逆矩阵: M M − 1 = M − 1 M = 单位矩阵 MM^{-1}=M^{-1}M=单位矩阵 M M − 1 = M − 1 M = 单 位 矩 阵 ,GLSL 和 GLM 都提供了 mat4.inverse()
函数。
3.5 变换矩阵
变换矩阵基于 4x4 矩阵,使用齐次坐标系,包括平移矩阵、缩放矩阵、旋转矩阵、投影矩阵和 LookAt 矩阵。
3.5.1 平移矩阵
平移矩阵 A 用于将物体从一个位置移至另一位置。
在单位矩阵的基础上,将 x、y、z 的移动量记录在 A 03 A_{03} A 0 3 、A 13 A_{13} A 1 3 、A 23 A_{23} A 2 3 位置。
( X + T x Y + T y Z + T z 1 ) = [ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] ( X Y Z 1 ) \left(
\begin{matrix}
X+T_x\\
Y+T_y\\
Z+T_z\\
1
\end{matrix}
\right)
=
\left[
\begin{matrix}
1 & 0 & 0 & T_x\\
0 & 1 & 0 & T_y\\
0 & 0 & 1 & T_z\\
0 & 0 & 0 & 1
\end{matrix}
\right]
\left(
\begin{matrix}
X\\
Y\\
Z\\
1
\end{matrix}
\right)
⎝ ⎜ ⎜ ⎜ ⎛ X + T x Y + T y Z + T z 1 ⎠ ⎟ ⎟ ⎟ ⎞ = ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 T x T y T z 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎝ ⎜ ⎜ ⎜ ⎛ X Y Z 1 ⎠ ⎟ ⎟ ⎟ ⎞
结果就是点 ( X , Y , Z ) (X,Y,Z) ( X , Y , Z ) 平移到位置 ( X + T x , Y + T y , Z + T z ) (X+T_x,Y+T_y,Z+T_z) ( X + T x , Y + T y , Z + T z )
平移矩阵的构建可以使用 GLM 中 glm::translate(x,y,z)
函数。接着矩阵与点相乘。
3.5.2 缩放矩阵
缩放矩阵用于改变物体的大小或者将点沿朝向或远离原点的方向移动。
缩放矩阵 A 在单位矩阵的基础上,将位于 A 00 A_{00} A 0 0 ,A 11 A_{11} A 1 1 , A 22 A_{22} A 2 2 的值替换为 x、y、z 缩放因子。
( X S x Y S y Z S z 1 ) = [ S x 0 0 0 0 S y 0 0 0 0 S z 0 0 0 0 1 ] ( X Y Z 1 ) \left(
\begin{matrix}
X\ S_x\\
Y\ S_y\\
Z\ S_z\\
1
\end{matrix}
\right)
=
\left[
\begin{matrix}
S_x & 0 & 0 & 0\\
0 & S_y & 0 & 0\\
0 & 0 & S_z & 0\\
0 & 0 & 0 & 1
\end{matrix}
\right]
\left(
\begin{matrix}
X\\
Y\\
Z\\
1
\end{matrix}
\right)
⎝ ⎜ ⎜ ⎜ ⎛ X S x Y S y Z S z 1 ⎠ ⎟ ⎟ ⎟ ⎞ = ⎣ ⎢ ⎢ ⎢ ⎡ S x 0 0 0 0 S y 0 0 0 0 S z 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎝ ⎜ ⎜ ⎜ ⎛ X Y Z 1 ⎠ ⎟ ⎟ ⎟ ⎞
GLM 的 glm::scale(x,y,z)
函数用于构建依照 ( x , y , z ) (x,y,z) ( x , y , z ) 缩放的矩阵,接着矩阵与点相乘。
缩放还可以用来切换坐标系,比如在右手坐标系下确定左手坐标系的坐标,只需把 z 坐标反转即可。切换坐标系的缩放矩阵:
[ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ] \left[
\begin{matrix}
1 & 0 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & -1& 0\\
0 & 0 & 0 & 1
\end{matrix}
\right]
⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤
3.5.3 旋转矩阵
3D 空间中旋转物体需要指定旋转轴和旋转角(以度或弧度为单位)。
数学上可证明:围绕任何轴的旋转都可以表示为绕 x 轴、y
轴、z 轴旋转的组合。围绕这 3 个轴的旋转角度被称为欧拉角。
( X Y ′ Z ′ 1 ) = [ 1 0 0 0 0 cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 ] ( X Y Z 1 ) (绕x轴旋转) \left(\begin{matrix}X \\ Y'\\ Z'\\ 1\end{matrix}\right)=\left[\begin{matrix}1 & 0 & 0 & 0\\ 0 & \cos\theta & -\sin\theta & 0\\ 0 & \sin\theta & \cos\theta & 0\\ 0 & 0 & 0 & 1\end{matrix}\right]\left(\begin{matrix}X\\ Y\\ Z\\ 1\end{matrix}\right)\tag{绕x轴旋转}
⎝ ⎜ ⎜ ⎜ ⎛ X Y ′ Z ′ 1 ⎠ ⎟ ⎟ ⎟ ⎞ = ⎣ ⎢ ⎢ ⎢ ⎡ 1 0 0 0 0 cos θ sin θ 0 0 − sin θ cos θ 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎝ ⎜ ⎜ ⎜ ⎛ X Y Z 1 ⎠ ⎟ ⎟ ⎟ ⎞ ( 绕 x 轴 旋 转 )
( X ′ Y Z ′ 1 ) = [ cos θ 0 sin θ 0 0 1 0 0 − sin θ 0 cos θ 0 0 0 0 1 ] ( X Y Z 1 ) (绕y轴旋转) \left(\begin{matrix}X' \\ Y\\ Z'\\ 1\end{matrix}\right)=\left[\begin{matrix}\cos\theta & 0 & \sin\theta & 0\\ 0 & 1 & 0 & 0\\ -\sin\theta & 0 & \cos\theta & 0\\ 0 & 0 & 0 & 1\end{matrix}\right]\left(\begin{matrix}X\\ Y\\ Z\\ 1\end{matrix}\right)\tag{绕y轴旋转}
⎝ ⎜ ⎜ ⎜ ⎛ X ′ Y Z ′ 1 ⎠ ⎟ ⎟ ⎟ ⎞ = ⎣ ⎢ ⎢ ⎢ ⎡ cos θ 0 − sin θ 0 0 1 0 0 sin θ 0 cos θ 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎝ ⎜ ⎜ ⎜ ⎛ X Y Z 1 ⎠ ⎟ ⎟ ⎟ ⎞ ( 绕 y 轴 旋 转 )
( X ′ Y ′ Z 1 ) = [ cos θ − sin θ 0 0 sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ] ( X Y Z 1 ) (绕z轴旋转) \left(\begin{matrix}X' \\ Y'\\ Z\\ 1\end{matrix}\right)=\left[\begin{matrix}\cos\theta & -\sin\theta & 0 & 0\\ \sin\theta & \cos\theta & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\end{matrix}\right]\left(\begin{matrix}X\\ Y\\ Z\\ 1\end{matrix}\right)\tag{绕z轴旋转}
⎝ ⎜ ⎜ ⎜ ⎛ X ′ Y ′ Z 1 ⎠ ⎟ ⎟ ⎟ ⎞ = ⎣ ⎢ ⎢ ⎢ ⎡ cos θ sin θ 0 0 − sin θ cos θ 0 0 0 0 1 0 0 0 0 1 ⎦ ⎥ ⎥ ⎥ ⎤ ⎝ ⎜ ⎜ ⎜ ⎛ X Y Z 1 ⎠ ⎟ ⎟ ⎟ ⎞ ( 绕 z 轴 旋 转 )
旋转变换有 3 种,分别是绕 x 轴、y 轴、z 轴旋转。GLM 提供了函数 glm::rotate(mat4,θ,x,y,z)
构建绕 ( x , y , z ) (x,y,z) ( x , y , z ) 旋转 θ \theta θ 度的矩阵,接着矩阵与点相乘。
通过 cos ( − θ ) = cos ( θ ) \cos(−θ) = \cos(θ) cos ( − θ ) = cos ( θ ) 和 sin ( − θ ) = − sin ( θ ) \sin(−θ) = −\sin(θ) sin ( − θ ) = − sin ( θ ) 即可验证反向旋转的矩阵恰等于其转置矩阵。
当在 3D 空间中旋转轴不穿过原点时,物体使用欧拉角进行旋转需要几个额外的步骤。一般有:
平移旋转轴以使它经过原点;
绕 x 轴、y 轴、z 轴旋转适当的欧拉角;
复原步骤 1 中的平移。
3.6 视觉空间和合成相机
3D 空间中,需要找到一点并确立观察方向,这个点叫作视图或视觉空间或合成相机。
观察 3D 世界需要以下步骤
确定相机的位置;
调整相机的角度,通常需要一套它自己的直角坐标轴u、v、n;
定义一个视体。视体是从相机的位置出发,沿着相机的视线朝向的可视范围。
将视体内的对象投影到投影平面上。
OpenGL 具有一个固定在原点 ( 0 , 0 , 0 ) (0,0,0) ( 0 , 0 , 0 ) 且朝向 z 轴负方向的相机。所以当使用这个相机时,由于相机自身不能移动,所以需要将对象移动到适合的位置并调整合适的方向。
假设有世界空间(类似于全局的空间)中某点 P W P_W P W ,需要对该点进行变换转换成相机空间中适合的点 P C P_C P C 。构建一个单一变换矩阵以完成旋转和平移,这个矩阵叫作视图变换矩阵,记作 V(V合并了平移矩阵和旋转矩阵)。
P C = V × P W P_C=V\times P_W
P C = V × P W
将 V 矩阵与模型矩阵 M 的积定义为 模型-视图(Model-View, MV) 矩阵,记作MV,M V = V M MV=VM M V = V M 。
之后,对于某点 P M P_M P M 就可以从自己的模型空间直接转换到相机空间:
P C = M V P M P_C=MVP_M
P C = M V P M
3.7 投影矩阵
透视投影矩阵和正射投影矩阵
3.7.1 透视投影矩阵
透视投影通过使用透视概念模仿我们看真实世界的方
式,尝试让 2D 图像看起来像是 3D 的。
使用叫作 透视矩阵 或者 透视变换 的变换矩阵将平行线变为恰当的不平行线来实现这个效果。定义 4 个参数来构建视体:
纵横比:远近剪裁平面的宽度与高度之比;
视场:可视空间的纵向角度;
近剪裁平面(也称投影平面)
远剪裁平面。
透视矩阵:
[ A 0 0 0 0 q 0 0 0 0 B C 0 0 − 1 0 ] (透视矩阵) \left[
\begin{matrix}
A & 0 & 0 & 0\\
0 & q & 0 & 0\\
0 & 0 & B & C\\
0 & 0 &-1 & 0
\end{matrix}
\right]
\tag{透视矩阵}
⎣ ⎢ ⎢ ⎢ ⎡ A 0 0 0 0 q 0 0 0 0 B − 1 0 0 C 0 ⎦ ⎥ ⎥ ⎥ ⎤ ( 透 视 矩 阵 )
其中:
q = 1 tan ( 视场 2 ) q = \frac{1}{\tan{(\frac{视场}{2})}}
q = tan ( 2 视 场 ) 1
A = q 纵横比 A = \frac{q}{纵横比}
A = 纵 横 比 q
B = Z n e a r + Z f a r Z n e a d − Z f a r B = \frac{Z_{near}+Z_{far}}{Z_{nead}-Z_{far}}
B = Z n e a d − Z f a r Z n e a r + Z f a r
C = 2 Z n e a r Z f a r Z n e a r − Z f a r C = \frac{2Z_{near}Z_{far}}{Z_{near}-Z_{far}}{}
C = Z n e a r − Z f a r 2 Z n e a r Z f a r
GLM 库提供了构建透视矩阵函数 glm::perspective()
。
3.7.2 正射投影矩阵
在正射投影中,平行线仍然是平行的。
构建正射矩阵需要以下参数:
从相机到投影平面的距离 z n e a r z_{near} z n e a r
从相机到远剪裁平面的距离 z f a r z_{far} z f a r
投影平面左边界 x 坐标 L, 投影平面右边界 x 坐标 R,投影平面上边界 y 坐标 T, 投影平面下边界 y 坐标 B
正射矩阵:
[ 2 R − L 0 0 − R + L R − L 0 2 T − B 0 − T + B T − B 0 0 − 2 z f a r − z n e a r − z f a r + z n e a r z f a r − z n e a r 0 0 0 1 ] (正射矩阵) \left[
\begin{matrix}
\frac{2}{R-L} & 0 & 0 & -\frac{R+L}{R-L}\\
0 & \frac{2}{T-B} & 0 & -\frac{T+B}{T-B}\\
0 & 0 & \frac{-2}{z_{far}-z_{near}} & -\frac{z_{far}+z_{near}}{z_{far}-z_{near}}\\
0 & 0 &0 & 1
\end{matrix}
\right]
\tag{正射矩阵}
⎣ ⎢ ⎢ ⎢ ⎡ R − L 2 0 0 0 0 T − B 2 0 0 0 0 z f a r − z n e a r − 2 0 − R − L R + L − T − B T + B − z f a r − z n e a r z f a r + z n e a r 1 ⎦ ⎥ ⎥ ⎥ ⎤ ( 正 射 矩 阵 )
3.8 LookAt矩阵
当把相机放在某处并看向一个特定的位置时,就需要用到 LookAt 矩阵。LookAt 变换依然由相机旋转决定。
通过指定大致旋转朝向的向量(如世界空间 y 轴)。可以通过一系列叉积获得一组向量,代表相机的正面(向量 fwd)、侧面(向量 side),以及上面(向量 up)。
由相机位置(点eye)、目标位置(点target)、初始向上向量 Y 构建 LookAt 矩阵:
[ s i d e x s i d e y s i z e z − ( s i d e ⋅ e y e ) u p x u p y u p z − ( u p ⋅ e y e ) − f w d x − f w d y − f w d z − ( − f w d ⋅ e y e ) 0 0 0 1 ] (LookAt矩阵) \left[
\begin{matrix}
side_x & side_y & size_z & -(side\cdot eye)\\
up_x & up_y & up_z & -(up\cdot eye)\\
-fwd_x & -fwd_y & -fwd_z & -(-fwd\cdot eye)\\
0 & 0 &0 & 1
\end{matrix}
\right]
\tag{LookAt矩阵}
⎣ ⎢ ⎢ ⎢ ⎡ s i d e x u p x − f w d x 0 s i d e y u p y − f w d y 0 s i z e z u p z − f w d z 0 − ( s i d e ⋅ e y e ) − ( u p ⋅ e y e ) − ( − f w d ⋅ e y e ) 1 ⎦ ⎥ ⎥ ⎥ ⎤ ( L o o k A t 矩 阵 )
其中:
f w d = n o r m a l i z e ( e y e − t a r g e t ) fwd = normalize(eye-target)
f w d = n o r m a l i z e ( e y e − t a r g e t )
s i d e = n o r m a l i z e ( − f w d × Y ) side = normalize(-fwd \times Y)
s i d e = n o r m a l i z e ( − f w d × Y )
u p = n o r m a l i z e ( s i d e × ( − f w d ) ) up = normalize(side \times (-fwd))
u p = n o r m a l i z e ( s i d e × ( − f w d ) )
四、OpenGL管理3D图形数据
使用 OpenGL 渲染一个 3D 图形 需要将若干数据发送给 OpenGL 着色器管线。
比如一个立方体,需要发送:
立方体模型的顶点;
控制立方体在3D空间中朝向的变换矩阵。
然而,发送数据到 OpenGL 管线的方式有:
4.1 缓冲区和顶点属性
绘制对象时,它的顶点数据需要发送给顶点着色器。通常:
顶点数据被发送到一个缓冲区中;
把缓冲区和着色器中声明的顶点属性相关联。
完成这件事,有一系列的步骤,有的步骤只需要做一次,有的步骤每帧都需做一次:
只做一次的步骤:
创建缓冲区
将顶点数据复制到缓冲区
每帧刷新需要的步骤:
启用包含顶点数据的缓冲区
将缓冲区和一个顶点属性相关联
启用这个顶点属性
使用 glDrawArrays()
绘制对象
在 OpenGL 中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object,VBO)中,VBO 在 C++/OpenGL 应用程序中被声明和实例化。一个场景可能需要很多 VBO,所以常常会在一个地方中生成并填充若干个 VBO,以备程序需要时直接使用。
缓冲区使用特定的方式和顶点属性交互。当 glDrawArrays()
执行时,缓冲区中的数据开始流动,从缓冲区的开头开始,按顺序流过顶点着色器,顶点着色器对每个顶点执行一次。
OpenGL 中还有一种相关的结构,叫作顶点数组对象(Vertex Array Object,VAO)。OpenGL的 3.0 版本引入了 VAO,作为一种组织缓冲区的方法,让缓冲区在复杂场景中更容易操控。OpenGL 要求至少创建一个 VAO。
举个例子,假设想显示两个对象,那么声明一个 VAO 和两个相关的 VBO:
1 2 3 4 5 6 GLuint vao[1 ]; GLuint vbo[2 ]; glGenVertexArrays (1 , vao);glBindVertexArray (vao[0 ]);glGenBuffers (2 , vbo);
glGenVertexArrays()
:生成一个 VAO,返回 VAO 的整型 ID 并将存到vao
第一参数表示创建 ID 个数
第二参数表示保存返回 ID 的数组
glBindVertexArray()
:将 vao[0] 设为当前对象,这样生成的缓冲区就会和这个 VAO 相关联
glGenBuffers()
:生成两个 VBO,返回 VBO 的整型 ID 并将它们存到vbo
第一参数表示创建 ID 个数
第二参数表示保存返回 ID 的数组
缓冲区大概完成之后,顶点着色器需要有相应地顶点属性变量。如:
1 layout (location = 0 ) in vec3 position;
layout (location = 0)
:layout修饰符,顶点属性和特定缓冲区关联的方法,这里表示顶点属性的识别号是0
in
:表示顶点属性将会从缓冲区中接收数值
vec3
:表示顶点属性的类型是三维向量
position
:顶点属性的名字
继续假设绘制一个立方体,假定立方体的顶点数据在代码中用数组直接指定,还需要进行以下步骤:
将顶点数据值复制到之前生成的两个缓冲区之一。使用 OpenGL 的函数 glBindBuffer()
将缓冲区标为活跃(使用)
使用 glBufferData()
函数将包含顶点数据的数组复制进活跃缓冲区
1 2 glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]);glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
上述是初始化时执行一次的步骤,接下来对于每一帧刷新,需要进行:
使用 glBindBuffer()
函数标记缓冲区为活跃
将活跃缓冲区与着色器中的顶点属性相关联
启用顶点属性
1 2 3 4 5 6 glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]);glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , (void *)0 );glEnableVertexAttribArray (0 );
4.2 统一变量
使用关键字 uniform
在着色器中声明统一变量。
将数据从 C++/OpenGL 应用程序发送到统一变量需要以下步骤:
获取统一变量的引用
将指向所需数值的指针与获取的统一变量引用相关联
比如在上面绘制立方体的例子,假设链接的渲染程序保存在名为 rendering
的变量中,则以下代码会把 MV 和投影矩阵发送到两个统一变量中:
1 2 3 4 5 6 7 mvLoc = glGetUniformLocation (rendering, "mv_m" ); projLoc = glGetUniformLocation (rendering, "proj_m" ); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mv));glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (proj));
value_ptr()
返回对矩阵数据的引用, glUniformMatrix4fv()
将矩阵数据传递给统一变量
4.3 顶点属性插值
在片段着色器栅格化之前,由顶点定义的图元被转换为片段。
栅格化:将 3D 空间信息表达在 2D 屏幕的栅格(像素阵列)中。
栅格化过程会线性插值顶点属性值,以便显示的像素能无缝连接建模后的曲面。
比如栅格化一个三角形时,将三角形顶点传入,首先沿着连接顶点的线开始插值,其精度级别和像素显示密度相关。
而统一变量在每次顶点着色器调用时保持不变,本身不是插值的,无论顶点数量有多少,变量都包含相同的值。
顶点着色器中,顶点属性被声明为 in
,表示从缓冲区接收,也可以被声明为 out
,表示值发送到管线的下一阶段。
OpenGL 内置一个 vec4
变量 gl_Position
,在顶点着色器中,将矩阵变换后的顶点赋值给它:
1 gl_Position = proj_matrix * mv_matrix * position;
4.4 MV 矩阵和透视矩阵
渲染 3D 对象的一个基础步骤是创建适当的变换矩阵并将它们发送到统一变量。
模型矩阵:在世界空间中表示对象的位置和朝向。
视图矩阵:移动并旋转世界中的模型,以模拟相机看到的效果。
透视矩阵:根据所需的视锥提供 3D 效果。
假设模型是变动的,相机是可移动的,那么:
需要每帧为每个模型创建模型矩阵;
需要每帧创建视图矩阵,对于这一帧所渲染的所有对象都是一致的;
需要创建一次透视矩阵,确定视锥。
4.5 构建一个 3D 立方体
由于 OpenGL 代码繁多,需要处理多文件,给出文件结构:
1 2 3 4 5 6 OpenGL项目 ├─ main.cpp // 主程序 ├─ fragment.glsl // 片段着色器 ├─ vertex.glsl // 顶点着色器 ├─ Util.h // 常用函数的声明 └─ Util.cpp // 常用函数的实现
效果展示:
源代码main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 1 ;GLuint vao[numVAO], vbo[numVBO]; double cameraX, cameraY, cameraZ;double cubeLocX, cubeLocY, cubeLocZ;float aspect;GLuint mvLoc, projLoc; int width, height;glm::mat4 pMat, vMat, mMat, mvMat; void makeVertexArray () { float vertexs[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 (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (vertexs), vertexs, GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 8.0f ; cubeLocX = 0.0f , cubeLocY = -2.0f , cubeLocZ = 0.0f ; makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 100.0f ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (cubeLocX, cubeLocY, cubeLocZ)); mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[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 () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Cube" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
解释 main.cpp
:
函数 makeVertexArray()
创建顶点数组,使用三角形绘制立方体,每个面需要两个三角形,共需要12个三角形,有36个顶点,合36×3=108个坐标(值)。
立方体定义在自己的坐标系上,中心为(0,0,0)。
建立一个 VAO 和 VBO,并把立方体顶点加载到VBO中。
函数 init()
读取着色器代码并构建渲染程序。
将立方体通过函数 makeVertexArray()
加载到 VBO 中。
定义立方体和相机在世界中的位置:(0,0,8)和(0,-2,0)。
函数 display()
不断重绘帧。
调用 glClear(GL_DEPTH_BUFFER_BIT)
清除深度缓冲区。
调用 glUseProgram()
启用着色器,在 GPU 上加载 GLSL 代码,但不会运行着色器程序。
获取统一变量位置,构建透视、视图矩阵和模型矩阵,并合成 MV 矩阵。
使用 glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
构建视图矩阵。为了模拟以某种方式移动的相机的表现,需要向相反的方向移动物体本身。
使用 glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
构建模型矩阵。
将透视矩阵和 MV 矩阵赋给相应的统一变量。
启用立方体顶点数据的缓冲区,并将其附加到第0个顶点属性。
调用 glDrawArrays()
绘制模型。
Util.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #pragma once #include <GL/glew.h> #include <GLFW/glfw3.h> #include <SOIL2/soil2.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #include <string> #include <iostream> #include <fstream> #include <cmath> #include <vector> class Util { public : static std::string readShaderSource (const std::string &path) ; static void printShaderLog (GLuint shader) ; static void printProgramLog (GLuint program) ; static bool checkOpenGLerror () ; static GLuint createShadeProgram (const std::string vpath, const std::string fpath) ; };
Util.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 #include "Util.h" std::string Util::readShaderSource (const std::string &path) { std::string content = "" ; std::ifstream fileStream (path, std::ios::in) ; std::string line = "" ; while (!fileStream.eof ()) { getline (fileStream, line); content.append (line + "\n" ); } fileStream.close (); return content; } void Util::printShaderLog (GLuint shader) { int len = 0 ; int chWrittn = 0 ; char *log; glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetShaderInfoLog (shader, len, &chWrittn, log); std::cout << "Shader Info: " << log << "\n" ; delete [] log; } } void Util::printProgramLog (GLuint program) { int len = 0 ; int chWrittn = 0 ; char *log; glGetProgramiv (program, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetProgramInfoLog (program, len, &chWrittn, log); std::cout << "Program Info: " << log << "\n" ; delete [] log; } } bool Util::checkOpenGLerror () { bool err = false ; int glErr = glGetError (); while (glErr != GL_NO_ERROR) { std::cout << "glError: " << glErr << "\n" ; err = true ; glErr = glGetError (); } return err; } GLuint Util::createShadeProgram (const std::string vpath, const std::string fpath) { GLint vertCompiled, fragCompiled, linked; std::string vs = readShaderSource (vpath); std::string fs = readShaderSource (fpath); const char *vshaderSource = vs.c_str (); const char *fshaderSource = fs.c_str (); GLuint vShader = glCreateShader (GL_VERTEX_SHADER); GLuint fShader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vShader, 1 , &vshaderSource, NULL ); glShaderSource (fShader, 1 , &fshaderSource, NULL ); glCompileShader (vShader); checkOpenGLerror (); glGetShaderiv (vShader, GL_COMPILE_STATUS, &vertCompiled); if (vertCompiled == GL_FALSE) { std::cout << "vertex compilation failed\n" ; printShaderLog (vShader); } glCompileShader (fShader); checkOpenGLerror (); glGetShaderiv (fShader, GL_COMPILE_STATUS, &fragCompiled); if (fragCompiled == GL_FALSE) { std::cout << "fragment compilation failed\n" ; printShaderLog (fShader); } GLuint vProgram = glCreateProgram (); glAttachShader (vProgram, vShader); glAttachShader (vProgram, fShader); glLinkProgram (vProgram); checkOpenGLerror (); glGetProgramiv (vProgram, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { std::cout << "linking failed\n" ; printProgramLog (vProgram); } return vProgram; }
vertex.glsl 和 fragment.glsl
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core layout (location = 0 ) in vec3 position;uniform mat4 mv_matrix; uniform mat4 proj_matrix; void main () { gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core out vec4 color; uniform mat4 mv_matrix; uniform mat4 proj_matrix; void main () { color = vec4 (0 , 0.6 , 0 , 1.0 ); }
关于着色器方面
着色器中传入的顶点属性的 position
变量上存在。layout
修饰符,位置指定为“0”,因此 display()
函数可以简单地通过在 glVertexAttribPointer()
函数调用中(第一个参数)和在 glEnableVertexAttribArray()
函数调用中使用 0 来引用此变量。
position
顶点属性被声明为 vec3
类型,需要将其转换为 vec4
类型,由 vec4(position,1.0)
完成。
顶点着色器中的乘法将矩阵变换应用于顶点,将其转换为相机空间,接着被放入内置的 OpenGL 输出变量 gl_Position
中,然后继续通过管线,并由光栅着色器进行插值。
插值后的像素位置(称为片段)被发送到片段着色器。
片段着色器中也有两个统一变量,但在该着色器中尚未使用。
通过修改着色器,实现彩色立方体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #version 460 core layout (location = 0 ) in vec3 position;uniform mat4 mv_matrix; uniform mat4 proj_matrix; out vec4 changingColor; void main () { gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); changingColor = vec4 (position, 1.0 ) * 0.5 + vec4 (0.5 , 0.5 , 0.5 , 0.5 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #version 460 core in vec4 changingColor; out vec4 color; uniform mat4 mv_matrix; uniform mat4 proj_matrix; void main () { color = changingColor; }
效果展示:
同样地,也可以让立方体动起来,只需要在刷新的每一帧加上平移变换和旋转变换:
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 100.0f ); glm::mat tMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (sin (0.35f * currentTime) * 2.0f , cos (0.52f * currentTime) * 2.0f , sin (0.7f * currentTime) * 2.0f )); glm::mat rMat = glm::rotate (glm::mat4 (1.0f ), 1.75f * static_cast <float >(currentTime), glm::vec3 (0.0f , 1.0f , 0.0f )); rMat = glm::rotate (rMat, 1.75f * static_cast <float >(currentTime), glm::vec3 (1.0f , 0.0f , 0.0f )); rMat = glm::rotate (rMat, 1.75f * static_cast <float >(currentTime), glm::vec3 (0.0f , 0.0f , 1.0f )); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = rMat * tMat; mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); }
效果展示:
4.6 渲染一个对象的多个副本
上面实现了一个翻滚的立方体,接着可以考虑实现多个翻滚的立方体。
可以将 display()
函数中用于构建 MV 矩阵并绘制立方体的代码移动到一个执行 24 次的循环中来完成此操作。
展开代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 32.0f ; makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 100.0f ); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); float tf; glm::mat4 tMat, rMat; for (int i = 0 ; i < 24 ; i ++) { tf = currentTime + i; tMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (sin (0.35f * tf) * 8.0f , cos (0.52f * tf) * 8.0f , sin (0.7f * tf) * 8.0f )); rMat = glm::rotate (glm::mat4 (1.0f ), 1.75f * tf, glm::vec3 (0.0f , 1.0f , 0.0f )); rMat = glm::rotate (rMat, 1.75f * tf, glm::vec3 (1.0f , 0.0f , 0.0f )); rMat = glm::rotate (rMat, 1.75f * tf, glm::vec3 (0.0f , 0.0f , 1.0f )); mMat = rMat * tMat; mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); } }
OpenGL 的实例化提供了一种机制,可以只用一个调用就渲染一个对象的多个副本。
把原来调用的 glDrawArrays()
改为 glDrawArraysInstanced()
。
同时实例化时,顶点着色器可以访问内置变量 gl_InstanceID
来获取当前正在处理对象的实例序号。
实例化的程序:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 1 ;GLuint vao[numVAO], vbo[numVBO]; double cameraX, cameraY, cameraZ;float aspect, timeFactor;GLuint mLoc, vLoc, projLoc, tfLoc; int width, height;glm::mat4 pMat, vMat, mMat; void makeVertexArray () { float vertexs[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 (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (vertexs), vertexs, GL_STATIC_DRAW); } void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 32.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); vLoc = glGetUniformLocation (renderProgram, "v_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); glUniformMatrix4fv (vLoc, 1 , GL_FALSE, glm::value_ptr (vMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); timeFactor = static_cast <float >(currentTime); tfLoc = glGetUniformLocation (renderProgram, "tf" ); glUniform1f (tfLoc, timeFactor); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArraysInstanced (GL_TRIANGLES, 0 , 36 , 24 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Cube" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
从之前的循环多个模型矩阵展示 改为了 在顶点着色器中构建模型矩阵。
将时间因子通过统一变量传递给顶点着色器,还需要将视图矩阵传递到单独的统一变量中
旋转计算被移动到了顶点着色器中
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #version 460 core layout (location = 0 ) in vec3 position;uniform mat4 v_matrix; uniform mat4 proj_matrix; uniform float tf; out vec4 changingColor; mat4 buildRotateX (float rad) { mat4 xrot = mat4 ( 1.0 , 0.0 , 0.0 , 0.0 , 0.0 , cos (rad), -sin (rad), 0.0 , 0.0 , sin (rad), cos (rad), 0.0 , 0.0 , 0.0 , 0.0 , 1.0 ); return xrot; } mat4 buildRotateY (float rad) { mat4 yrot = mat4 ( cos (rad), 0.0 , sin (rad), 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , -sin (rad), 0.0 , cos (rad), 0.0 , 0.0 , 0.0 , 0.0 , 1.0 ); return yrot; } mat4 buildRotateZ (float rad) { mat4 zrot = mat4 ( cos (rad), -sin (rad), 0.0 , 0.0 , sin (rad), cos (rad), 0.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 0.0 , 0.0 , 1.0 ); return zrot; } mat4 buildTranslate (float x, float y, float z) { mat4 trans = mat4 ( 1.0 , 0.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , x, y, z, 1.0 ); return trans; } void main () { float i = gl_InstanceID + tf; float a = sin (0.35 * i) * 8.0 ; float b = cos (0.52 * i) * 8.0 ; float c = sin (0.70 * i) * 8.0 ; mat4 localRotx = buildRotateX (1.75 * i); mat4 localRoty = buildRotateY (1.75 * i); mat4 localRotz = buildRotateZ (1.75 * i); mat4 localTrans = buildTranslate (a, b, c); mat4 newM_matrix = localTrans * localRotx * localRoty * localRotz; mat4 mv_matrix = v_matrix * newM_matrix; gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); changingColor = vec4 (position, 1.0 ) * 0.5 + vec4 (0.5 , 0.5 , 0.5 , 0.5 ); }
由于着色器内部无法调用 GLM 的函数,故需要编写矩阵变换的相关函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 #version 460 core in vec4 changingColor; out vec4 color; uniform mat4 proj_matrix; void main () { color = changingColor; }
片段着色器无需做改变。
如果渲染的立方体很多时,实例化极大的方便了程序的编写,只需要修改视野相关的常量:
比如想渲染 100000 个彩色立方体,只需修改:
1 2 3 glDrawArraysInstanced (GL_TRIANGLES, 0 , 36 , 100000 );
1 2 3 cameraX = 0.0f , cameraY = 0.0f , cameraZ = 402.0f ;
1 2 3 4 5 float a = sin (203.0 * i / 8000.0 ) * 403.0 ;float b = cos (301.0 * i / 4001.0 ) * 401.0 ;float c = sin (400.0 * i / 6003.0 ) * 405.0 ;
4.7 同一场景渲染多个对象
要在单个场景中渲染多个模型,简单的做法:
为每个模型使用单独的缓冲区;
每个模型都需要自己的模型矩阵,为渲染的每个模型生成一个新的 MV 矩阵;
为每个模型单独调用 glDrawArrays()
。
假如在场景中渲染立方体和四棱锥,且不考虑复杂的情况,复用着色器,效果如下图:
代码如下(自主研究,不过多解释):
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 2 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;float cubeLocx, cubeLocy, cubeLocz;float pyramidLocx, pyramidLocy, pyramidLocz;GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float vertexs[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 }; float pyramids[54 ] = { -1.0f , -1.0f , 1.0f , 1.0f , -1.0f , 1.0f , 0.0f , 1.0f , 0.0f , 1.0f , -1.0f , 1.0f , 1.0f , -1.0f , -1.0f , 0.0f , 1.0f , 0.0f , 1.0f , -1.0f , -1.0f , -1.0f , -1.0f , -1.0f , 0.0f , 1.0f , 0.0f , -1.0f , -1.0f , -1.0f , -1.0f , -1.0f , 1.0f , 0.0f , 1.0f , 0.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 (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (vertexs), vertexs, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (pyramids), pyramids, GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 8.0f ; cubeLocx = 0.0f , cubeLocy = -2.0f , cubeLocz = 0.0f ; pyramidLocx = 0.0f , pyramidLocy = 1.0f , pyramidLocz = 0.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (cubeLocx, cubeLocy, cubeLocz)); mMat *= glm::rotate (glm::mat4 (1.0f ), static_cast <float >(currentTime), glm::vec3 (0.25f , 1.0f , 0.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDrawArrays (GL_TRIANGLES, 0 , 36 ); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (pyramidLocx, pyramidLocy, pyramidLocz)); mMat *= glm::rotate (glm::mat4 (1.0f ), static_cast <float >(currentTime), glm::vec3 (0.25f , 1.0f , 0.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDrawArrays (GL_TRIANGLES, 0 , 18 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Cube" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #version 460 core layout (location = 0 ) in vec3 position;uniform mat4 mv_matrix; uniform mat4 proj_matrix; out vec4 changingColor; void main () { gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); changingColor = vec4 (position, 1.0 ) * 0.5 + vec4 (0.5 , 0.5 , 0.5 , 0.5 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 #version 460 core in vec4 changingColor; out vec4 color; uniform mat4 proj_matrix; void main () { color = changingColor; }
4.8 矩阵栈
前面渲染的模型都是由一组顶点构成,而实际上希望通过组装许多简单地模型来构建复杂的模型。以这种方式构建的对象称为分层模型。
构建分层模型的棘手部分是跟踪所有 MV 矩阵并保证矩阵完美协调。
矩阵栈:变换矩阵的栈。
使得创建和管理复杂分层对象和场景变得容易
使得变换可以构建在其他变换之上
通过 STL 的 stack
类构建 mat4
的栈。
对于 std::stack<glm::mat4>
的实例 s
:
s.push(mat4)
:将栈顶的矩阵复制一份,并作变换后把新的矩阵压入栈。
s.pop()
:弹出栈顶的矩阵。
s.top()
:返回栈顶的矩阵的引用。
s.top() *= rotate(args)
:直接对栈顶矩阵作旋转变换。
s.top() *= translate(args)
:直接对栈顶矩阵作平移变换。
s.top() *= scale(args)
:直接对栈顶矩阵作缩放变换。
在此之后,不再通过创建 mat4
的实例来构建变换,而是使用 push()
命令在栈顶部创建新的矩阵,然后根据需要将期望的变换应用于栈顶部的新创建的矩阵。
压入栈的第一个矩阵通常是视图矩阵。它上面的矩阵是复杂程度越来越高的 MV 矩阵,也就是说,它们应用了越来越多的模型变换。这些变换既可以直接应用,也可以先结合其他矩阵再应用。
假设一个例子,实现一个地球围绕太阳旋转和月球围绕地球旋转的场景:
首先压入视图矩阵 V;
压入太阳的 MV 矩阵;
压入地球的 MV 矩阵,由太阳的 MV 矩阵的副本和应用于其之上的地球模型矩阵变换组成;
压入月球的 MV 矩阵,由地球的 MV 矩阵的副本和应用于其之上的月球模型矩阵变换组成。
接着,渲染月球并把月球矩阵弹出,后面的地球和太阳只需要重复即可。步骤如下:
声明矩阵栈;
当相对于父对象创建新对象时,调用 s.push(s.top())
;
应用新对象所需的变换;
完成对象或子对象的绘制后,弹出栈顶,移除对应的 MV 矩阵。
在这个例子中,需要注意:
太阳的自转在它自己的局部坐标空间中进行,不应影响地球和月球。因此,太阳的旋转变换矩阵被推到栈上,但是在绘制太阳之后,它必须被从栈中弹出。
地球的公转将影响月球的运动,因此地球的平移变换矩阵被压入栈并保持到绘制月球。
地球的自转是局部的,不会影响月球,因此在绘制月球之前地球的旋转变换矩阵需要从栈中弹出。
效果如图:
顶点着色器与片段着色器无需改变,同上面渲染两个目标的程序。
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> GLuint renderProgram; const int numVAO = 1 , numVBO = 2 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; std::stack<glm::mat4> matrixStack; void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float vertexs[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 (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (vertexs), vertexs, GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 4.0f , cameraZ = 20.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); matrixStack.push (vMat); matrixStack.push (matrixStack.top ()); matrixStack.top () *= glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , 0.0f )); matrixStack.push (matrixStack.top ()); matrixStack.top () *= glm::rotate (glm::mat4 (1.0f ), static_cast <float >(currentTime), glm::vec3 (1.0f , 0.0f , 0.0f )); matrixStack.top () *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (2.0f , 2.0f , 2.0f )); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (matrixStack.top ())); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); matrixStack.pop (); matrixStack.push (matrixStack.top ()); matrixStack.top () *= glm::translate (glm::mat4 (1.0f ), glm::vec3 (sin (static_cast <float >(currentTime)) * 8.0 , 0.0 , cos (static_cast <float >(currentTime)) * 8.0 )); matrixStack.push (matrixStack.top ()); matrixStack.top () *= glm::rotate (glm::mat4 (1.0f ), static_cast <float >(currentTime) * 2.0f , glm::vec3 (0.0f , 2.0f , 0.0f )); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (matrixStack.top ())); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); matrixStack.pop (); matrixStack.push (matrixStack.top ()); matrixStack.top () *= glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0 , sin (static_cast <float >(currentTime)) * 2.0 , cos (static_cast <float >(currentTime)) * 2.0 )); matrixStack.top () *= glm::rotate (glm::mat4 (1.0f ), static_cast <float >(currentTime), glm::vec3 (0.0f , 0.0f , 1.0f )); matrixStack.top () *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (0.25f , 0.25f , 0.25f )); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (matrixStack.top ())); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); matrixStack.pop (); matrixStack.pop (); matrixStack.pop (); matrixStack.pop (); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "sun-earth-moon" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
注意的点:
加入缩放矩阵调节各个模型的大小
使用三角函数实现自转和公转
在 glUniformMatrix4fv()
命令中调用的 glm::value_ptr(mvStack.top())
函数,获取栈顶部矩阵中的值
4.9 应对“Z冲突”伪影
在渲染多个对象时,OpenGL 使用 Z-buffer 算法来进行隐藏面消除。通常情况下,通过选择最接近相机的相应片段的颜色作为像素的颜色,这种方法可决定哪些物体的曲面可见并呈现到屏幕,而位于其他物体后面的曲面不应该被渲染。
Z-buffer 算法思想:先将 Z 缓冲器中各单元的初始置设置为最小值。当要改变某个像素的颜色值时,首先检查当前多边形的深度值是否大于该像素原来的深度值(保存在该像素所对应的Z 缓冲器的单元中)。如果大于原来的 Z 值,说明当前多边形更靠近观察点,用它的颜色替换像素原来的颜色。
然而,有时候场景中的两个物体表面重叠并位于重合的平面中,这使得深度缓冲区算法难以确定应该渲染两个表面中的哪一个。
发生这种情况时,浮点舍入误差可能会导致渲染表面的某些部分使用其中一个对象的颜色,而其他部分则使用另一个对象的颜色。这种不自然的伪影称为 Z 冲突(Z-fighting)或深度冲突(depth-fighting)。
是渲染的片段在深度缓冲区中相互对应的像素条目上“斗争”的结果。
部分应对方法:
可以选择适当的近、远剪裁平面值来最小化两个平面之间的距离,同时仍然确保场景必需的所有对象都位于视锥内
建议避免选择太靠近相机的近剪裁平面
4.10 其他图元
目前一直使用三角形进行绘制,其实 OpenGL 还有其他选择。
4.10.1 三角形图元
名称
作用
示例
GL_TRIANGLES
管线中传递的每3个顶点数据组成一个三角形
GL_TRIANGLE_STRIP
管线中传递的每个顶点和之前的两个顶点组成一个三角形
GL_TRIANGLE_FAN
管线中传递的每对顶点和最开始的顶点组成一个三角形
GL_TRIANGLES_ADJACENCY
仅用于几何着色器,允许着色器访问当前三角形的顶点,以及额外的相邻顶点
GL_TRIANGLE_STRIP_ADJACENCY
仅用于几何着色器,类似于 GL_TRIANGLES_ADJACENCY
,但三角形顶点像在 GL_TRIANGLE_STRIP
中一样重叠
4.10.2 线图元
名称
作用
示例
GL_LINES
管线中传递的每两个顶点组成一条线
GL_LINE_STRIP
管线中每个顶点和前一个顶点组成一条线
GL_LINE_LOOP
类似于 GL_LINE_STRIP
,但最后的顶点和第一个顶点之间会形成一条线
GL_LINE_ADJACENCY
仅用于几何着色器,允许着色器访问当前线的顶点,以及额外的相邻顶点
GL_LINE_STRIP_ADJACENCY
仅用于几何着色器,类似于 GL_LINE_ADJACENCY
,但线顶点像在 GL_LINE_STRIP
中一样重叠
4.10.3 点图元
名称
作用
GL_POINTS
管线中传递的每个顶点组成一个点
补丁图元
名称
作用
GL_PATCHES
仅用于细分着色器,指示一组顶点从顶点着色器传递到细分控制着色器,在这里它们通常用于将曲面细分网格塑造成曲面
4.11 性能优先编程
随着 3D 场景逐渐复杂,性能方面不得不关心。
尽量减少动态内存空间分配
关键部分 display()
函数会被重复调用
C++ 栈类,每次入栈可能会导致动态内存分配
将数据从一种类型转换为另一种类型的函数调用在某些情况下可能会实例化并返回新转换的数据
GLM 库函数优化有限,可能引起动态内存分配
预先计算透视矩阵
将透视矩阵的计算移动到 init()
函数中
一般情况下不需要重新计算透视矩阵,但窗口大小变化时需要重新计算,可以将计算透视矩阵添加到回调函数中。
背面剔除
利用 OpenGL 的背面剔除能力:当 3D 模型完全闭合时,其内部永远不可见时,那么这些部分不需要被栅格化或渲染。
默认背面剔除时关闭的
使用函数 glEnable(GL_CULL_FACE)
开启
启用背面剔除时,默认情况下,三角形只有朝前时才会被渲染。此外,默认情况下,从 OpenGL相机的角度看,如果三角形的 3 个顶点是以逆时针顺序排列的(基于它们在缓冲区中定义的顺序),则三角形被视为朝前;顶点沿顺时针方向排列的三角形是朝后的,不会被渲染
定义“前向”的顶点顺序有时被称为缠绕顺序
使用 glFrontFace(GL_CW)
设置顺时针为正向,使用 glFrontFace(GL_CCW)
设置逆时针为正向
默认下,glCullFace(GL_BACK)
设置背向的三角形被剔除,但参数还有 GL_FRONT
和 GL_FRONT_AND_BACK
,分别设置前向或全部三角形被剔除
五、纹理贴图
纹理贴图是在栅格化的模型表面上覆盖图像的技术。
纹理图像可以是任何图像。
5.1 加载纹理图像文件
OpenGL/GLSL 中完成纹理贴图需要协调不同的数据集和机制:
保存纹理图像的纹理对象
特殊的统一采样器变量,用于顶点着色器访问纹理
保存纹理坐标的缓冲区
将纹理坐标传递给管线的顶点属性
显卡上的纹理单元
此处将用 SOIL2 库进行加载图像文件用于贴图:
使用 SOIL2 实例化 OpenGL 纹理对象并从图像文件中读入数据;
调用 glBindTexture()
使得新创建的纹理对象处于激活状态;
使用 glTexParameteri()
调整纹理设置;
最终得到的结果是可用的 OpenGL 纹理对象的整型 ID。
加载纹理的代码(加入在 Util 类中)如下:
1 2 3 4 5 6 7 8 GLuint Util::loadTexture (const std::string &imgPath) { GLuint textureID; textureID = SOIL_load_OGL_texture (imgPath.c_str (), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y); if (textureID == 0 ) std::cout << "Failed to load texture: " << imgPath << "\n" ; return textureID; }
代码先声明了一个 GLuint
类型的变量用于记录 OpenGL 纹理对象的 ID,接着调用 SOIL_load_OGL_texture()
函数生成纹理对象。
函数参数及返回值
第一个参数为文件名;
第二个说明符,SOIL_LOAD_AUTO
表示使用磁盘载入的;
第三个表示让SOIL为我们创建一个ID;
第四个参数 SOIL_FLAG_INVERT_Y
允许做是翻转Y轴
返回值,0表示失败,其他表示 OpenGL 上下文句柄
5.2 纹理坐标
通过为模型中的每个顶点指定纹理坐标来完成纹理贴图。
纹理坐标是对纹理图像(2D 图像文件)中像素的引用,用于将 3D 模型上的点映射到纹理中的位置。
模型除了 3D 空间中的定位坐标 (x, y, z) 外,表面上每个点还具有纹理坐标 (s, t),用来指定纹理图像中哪个像素为它提供颜色。
必须为要添加纹理的对象中每个顶点提供纹理坐标。
设置两个缓冲区,一个用于顶点坐标,另一个用于纹理坐标。每次调用顶点着色器会接收到两组坐标。
2D 纹理图像被设定为矩形,左下角坐标为 ( 0 , 0 ) (0,0) ( 0 , 0 ) ,右上角坐标为 ( 1 , 1 ) (1,1) ( 1 , 1 ) ,故纹理坐标取值区间为 [ 0 , 1 ] [0,1] [ 0 , 1 ] 。
由于之前渲染的立方体模型都是由三角形构成,每个面需要两个三角形。
对于立方体或四棱锥这样的简单模型,选择纹理坐标相对容易。但对于具有大量三角形的更复杂的弯曲模型,手动确定它们是不切实际的。在弯曲的几何形状(例如球形或环面)的情况下,可以通过算法或数学方式计算纹理坐标。使用 Maya 或 Blender 等建模工具构建模型时,可以使用“UV 映射”功能,使得确定纹理坐标的任务更容易完成
5.3 构建一个带纹理的四棱锥
首先创建纹理对象:
1 GLuint textureID = Util::loadTexture ("texture.jpg" );
接着构建纹理坐标:
四棱锥具有五个面,由6个三角形组成,每个三角形有3个顶点,共18个顶点
使图像的顶部中心对应四棱锥的顶(简单地贴上去)。
1 2 3 4 5 6 7 8 float textures[36 ] = { 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 1.0f , 0.0f , 1.0f , 1.0f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f };
然后将纹理载入缓冲区:载入缓冲区1。
1 2 glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]);glBufferData (GL_ARRAY_BUFFER, sizeof (textures), textures, GL_STATIC_DRAW);
在着色器中使用纹理:采样器变量和纹理单元。为了提高性能,在硬件中执行纹理处理。则需要通过统一采样器变量实现。
统一采样器变量:指示显卡上的纹理单元,从加载的纹理对象中提取或采样纹理图像中的像素。
1 layout (binding = 0 ) uniform sampler2D samp;
在 display()
函数中,调用 glActiveTexture()
函数激活纹理单元并调用 glBindTexture()
函数将纹理单元绑定到特定的纹理对象。
代码中使用 GL_TEXTURE0
让第0个纹理单元处于激活状态。
片段着色器中使用顶点着色器接收的插值纹理坐标来对纹理对象进行采样:
1 2 3 in vec2 tc; …… color = texture (samp, tc);
效果展示:
完整代码:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> GLuint renderProgram; const int numVAO = 1 , numVBO = 2 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint textureID[2 ]; GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; std::stack<glm::mat4> matrixStack; void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float pyramidVertexs[54 ] = { -1.0f , -1.0f , 1.0f , 1.0f , -1.0f , 1.0f , 0.0f , 1.0f , 0.0f , 1.0f , -1.0f , 1.0f , 1.0f , -1.0f , -1.0f , 0.0f , 1.0f , 0.0f , 1.0f , -1.0f , -1.0f , -1.0f , -1.0f , -1.0f , 0.0f , 1.0f , 0.0f , -1.0f , -1.0f , -1.0f , -1.0f , -1.0f , 1.0f , 0.0f , 1.0f , 0.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 }; float textures[36 ] = { 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f , 0.5f , 1.0f , 0.0f , 0.0f , 1.0f , 1.0f , 0.0f , 1.0f , 1.0f , 1.0f , 0.0f , 0.0f , 1.0f , 0.0f }; glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (pyramidVertexs), pyramidVertexs, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (textures), textures, GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 1.5f , cameraY = 0.0f , cameraZ = 5.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID[0 ] = Util::loadTexture ("brick1.jpg" ); textureID[1 ] = Util::loadTexture ("ice.jpg" ); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); for (int i = 0 ; i < 2 ; i ++) { vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (i * 3.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, -0.45f , glm::vec3 (1.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 0.61f , glm::vec3 (0.0f , 1.0f , 0.0f )); mMat = glm::rotate (mMat, 0.00f , glm::vec3 (0.0f , 0.0f , 1.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID[i]); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 18 ); } } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Pyramid with texture" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
Util.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #pragma once #include <GL/glew.h> #include <GLFW/glfw3.h> #include <SOIL2/soil2.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #include <string> #include <iostream> #include <fstream> #include <cmath> #include <vector> class Util { public : static std::string readShaderSource (const std::string &path) ; static void printShaderLog (GLuint shader) ; static void printProgramLog (GLuint program) ; static bool checkOpenGLerror () ; static GLuint createShadeProgram (const std::string vpath, const std::string fpath) ; static GLuint loadTexture (const std::string &imgPath) ; };
Util.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 #include "Util.h" std::string Util::readShaderSource (const std::string &path) { std::string content = "" ; std::ifstream fileStream (path, std::ios::in) ; std::string line = "" ; while (!fileStream.eof ()) { getline (fileStream, line); content.append (line + "\n" ); } fileStream.close (); return content; } void Util::printShaderLog (GLuint shader) { int len = 0 ; int chWrittn = 0 ; char *log; glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetShaderInfoLog (shader, len, &chWrittn, log); std::cout << "Shader Info: " << log << "\n" ; delete [] log; } } void Util::printProgramLog (GLuint program) { int len = 0 ; int chWrittn = 0 ; char *log; glGetProgramiv (program, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetProgramInfoLog (program, len, &chWrittn, log); std::cout << "Program Info: " << log << "\n" ; delete [] log; } } bool Util::checkOpenGLerror () { bool err = false ; int glErr = glGetError (); while (glErr != GL_NO_ERROR) { std::cout << "glError: " << glErr << "\n" ; err = true ; glErr = glGetError (); } return err; } GLuint Util::createShadeProgram (const std::string vpath, const std::string fpath) { GLint vertCompiled, fragCompiled, linked; std::string vs = readShaderSource (vpath); std::string fs = readShaderSource (fpath); const char *vshaderSource = vs.c_str (); const char *fshaderSource = fs.c_str (); GLuint vShader = glCreateShader (GL_VERTEX_SHADER); GLuint fShader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vShader, 1 , &vshaderSource, NULL ); glShaderSource (fShader, 1 , &fshaderSource, NULL ); glCompileShader (vShader); checkOpenGLerror (); glGetShaderiv (vShader, GL_COMPILE_STATUS, &vertCompiled); if (vertCompiled == GL_FALSE) { std::cout << "vertex compilation failed\n" ; printShaderLog (vShader); } glCompileShader (fShader); checkOpenGLerror (); glGetShaderiv (fShader, GL_COMPILE_STATUS, &fragCompiled); if (fragCompiled == GL_FALSE) { std::cout << "fragment compilation failed\n" ; printShaderLog (fShader); } GLuint vProgram = glCreateProgram (); glAttachShader (vProgram, vShader); glAttachShader (vProgram, fShader); glLinkProgram (vProgram); checkOpenGLerror (); glGetProgramiv (vProgram, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { std::cout << "linking failed\n" ; printProgramLog (vProgram); } return vProgram; } GLuint Util::loadTexture (const std::string &imgPath) { GLuint textureID; textureID = SOIL_load_OGL_texture (imgPath.c_str (), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y); if (textureID == 0 ) std::cout << "Failed to load texture: " << imgPath << "\n" ; return textureID; }
vertex.glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #version 460 core layout (location = 0 ) in vec3 position;layout (location = 1 ) in vec2 texCoord;uniform mat4 mv_matrix; uniform mat4 proj_matrix; layout (binding = 0 ) uniform sampler2D samp;out vec2 tc; void main () { gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); tc = texCoord; }
fragment.glsl
1 2 3 4 5 6 7 8 9 10 11 #version 460 core in vec2 tc; out vec4 color; layout (binding = 0 ) uniform sampler2D samp;void main () { color = texture (samp, tc); }
5.4 多级渐远纹理贴图
纹理贴图经常会在渲染图像中导致各种不利的伪影。
这是因为纹理图像的分辨率或长宽比很少与被纹理贴图的场景中区域的分辨率或长宽比匹配。
当图像分辨率小于所绘制区域的分辨率时,会出现一种很常见的伪影。在这种情况下,需要拉伸图像以覆盖整个区域,这样图像就会变得模糊(并且可能变形)。
根据纹理的性质,有时可以通过改变纹理坐标分配方式来应对这种情况,使得纹理需要较少的拉伸。
另一种解决方案是使用更高分辨率的纹理图像。
相反的情况是图像纹理的分辨率大于被绘制区域的分辨率。
在这种情况下,可能会出现明显的叠影,从而产生奇怪的错误图案,或移动物体中的“闪烁”效果。
叠影是由采样错误引起的。
使用多级渐远纹理贴图可以很大程度上矫正采样误差伪影。
使用各种分辨率创建纹理图像的不同版本。
被贴图的区域使用最适合的分辨率的纹理图像。
多级渐远纹理贴图的机制:
它在纹理图像中存储相同图像的连续的一系列较低分辨率的副本。
所用的纹理图像比原始图像大 1/3,其中图像的 RGB 值分别存储在纹理图像空间的 3 个 1/4 区域中来实现的。
剩余的 1/4 区域中迭代地将图像分辨率设置为原来的 1/4,直到剩余区域太小而不包含任何有用的图像数据。
使用代码进行构建多级渐远纹理:
1 2 3 4 5 glBindTexture (GL_TEXTURE_2D, textureID);glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);glGenerateMipmap (GL_TEXTURE_2D);
对于函数 glTexparameteri()
其第三个参数设置为所需的缩小方法来选择多级渐远纹理的采样方法,方法如下:
GL_NEAREST_MIPMAP_NEAREST
:选择具有与纹理坐标对应的纹理图像像素区域最相似的分辨率的多级渐远纹理,获取所需纹理坐标的最近的纹理图像像素。
GL_LINEAR_MIPMAP_NEAREST
:选择具有与纹理坐标对应的纹理图像像素区域最相似的分辨率的多级渐远纹理,获取最接近纹理坐标的4个纹理图像像素的插值。
GL_NEAREST_MIPMAP_LINEAR
:选择具有与纹理坐标对应的纹理图像像素区域最相似的分辨率的2个多级渐远纹理,从每个多级渐远纹理获取纹理坐标的最近的纹理图像像素进行插值。
GL_LINEAR_MIPMAP_LINEAR
:选择具有与纹理坐标对应的纹理图像像素区域最相似的分辨率的2多个渐远纹理,从每多个渐远纹理获取最接近纹理坐标的4个纹理图像像素进行插值,又称为“三线性过滤”。
通常选择三线性过滤。
对于特殊的应用场景,可以使用任何图像编辑软件自行构建多级渐远纹理,然后通过为每个多级渐远纹理级别重复调用 OpenGL 的 glTexImage2D()
函数来创建纹理对象,并将它们添加为多级渐远纹理级别
5.5 各向异性过滤
多级渐远纹理贴图有时看起来比非多级渐远纹理贴图更模糊。
这种细节的丢失是因为当物体倾斜时,其图元看起来在一个轴(即沿宽或高)上的尺寸比在另一个轴上更小。
当 OpenGL 为图元贴图时,它选择适合两个轴中尺寸较小的轴的多级渐远纹理)。
可以通过各向异性过滤恢复一些丢失的细节。
各向异性过滤比标准多级渐远纹理贴图的计算代价更高,并且不是 OpenGL 的必需部分。
只需添加以下代码:
1 2 3 4 5 6 if (glewIsSupported ("GL_EXT_texture_filter_anisotropic" )){ GLfloat anisoSetting = 0.0f ; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting); }
if
判断测试显卡是否支持各向异性过滤。如果支持,则设置为支持的最大采样程度,由 glGetFloatv()
函数获取。
使用 glTexParameterf()
函数激活并设置纹理的各向异性采样值。
5.6 环绕和平铺
当纹理坐标超出 [ 0 , 1 ] [0,1] [ 0 , 1 ] 时,可以使用 glTexParameteri()
函数设置:
GL_REPEAT
:重复纹理图像,忽略纹理坐标的整数部分,生成重复或平铺的图案,这是默认选项。
GL_MIRRORED_REPEAT
:重复纹理图像,忽略纹理坐标的整数部分,但是当整数部分为奇数时反转坐标,重复的图案在原图案和其镜像图案之间交替。
GL_CLAMP_TO_EDGE
:将纹理坐标限制在 [ 0 , 1 ] [0,1] [ 0 , 1 ] 之间,小于0的坐标设置为0,大于1的坐标设置为1。
GL_CLAMP_TO_BORDER
:将纹理坐标限制在 [ 0 , 1 ] [0,1] [ 0 , 1 ] 之间,区间以外设置为指定的边框颜色。
代码上:
1 2 3 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
修改为 GL_CLAMP_TO_BORDER
:
1 2 3 4 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);float redColor[4 ] = { 1.0f , 0.0f , 0.0f , 1.0f };glTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, redColor);
5.7 透视变形
在纹理坐标的情况下,线性插值可能导致在具有透视投影的 3D 场景中出现明显的失真。
默认情况下,OpenGL 在栅格化期间会应用透视校正算法。
可以在顶点属性中添加关键字 noperspective
来禁用透视校正。
顶点着色器:noperspective out vec2 texCoord;
片段着色器:noperspective in vec2 texCoord;
5.8 材质的更多细节
可以使用 C++ 和 OpenGL 函数直接将纹理图像文件数据加载到 OpenGL 中(不适用 SOIL2 库):
使用 C++ 工具读取图像文件数据。
生成 OpenGL 纹理对象。
使用 glGenTextures()
函数生成纹理对象。
glGenTextures(1, &textureID);
将图像文件数据复制到纹理对象中。
六、3D模型
对于四棱锥和立方体,可以通过在OpenGL中使用顶点数组来创建。但是大部分 3D 场景对象都比较复杂,无法手动构建顶点数组。
对于更为复杂的模型,此处关注:
6.1 程序构建模型
6.1.1 构建一个球体
像圆、球体等具有数学定义,其顶点数组可以通过数学计算而生成。
建立一个球体模型:
选择模型精度:一个表示将球体分成相应份数的圆形部分的数字,体现为切片数。
将每个圆形切片的圆周细分若干个点,此处每个切片具有相同数量的点。
将顶点分组为三角形:逐步遍历顶点,在每一步构建两个三角形。
根据纹理图像的性质选择纹理坐标。对于球体,想象让纹理图像围绕球体包裹,可以根据图像中像素的最终对应位置为每个顶点指定纹理坐标。
对于每个顶点,还应该生成法向量,用于光照。
在第3点中,存在顶点出现在多个三角形中,导致每个顶点被多次指定,这对程序不利。仅存储每个顶点一次,然后为三角形的每个角指定索引,引用所需的顶点。
需要存储每个顶点的位置、纹理坐标和法向量。
顶点存储在一维数组中,从最下面的水平切片开始。
索引数组包含相应三角形的每个角,将值设为顶点数组中的下标。
然后,从球体底部开始,围绕每个水平切片以圆形方式遍历顶点。访问每个顶点时,构建两个三角形,在其右上方形成一个方形区域。
1 2 3 4 5 6 7 8 9 10 for (int i = 0 ; i < slices; i++) { for (int j = 0 ; j < vertexs; j++) { } }
索引数组也加载到 VBO 中,指定 VBO 的类型为 GL_ELEMENT_ARRAY_BUFFER
(这会告诉 OpenGL 这个 VBO 包含索引)。
在 display()
中:
将 glDrawArrays()
调用替换为 glDrawElements()
调用,它会告诉 OpenGL利用索引 VBO 来查找要绘制的顶点。
还需要使用 glBindBuffer()
启用包含索引的 VBO,指定哪个 VBO 包含索引并且为 GL_ELEMENT_ARRAY_BUFFER
类型。
编写类 Sphere
,用于创建球体模型,球体中心位于原点。
效果截图:
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint textureID; GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; Sphere sphere (48 ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { std::vector<int > ind = sphere.getIndices (); std::vector<glm::vec3> vert = sphere.getVertices (); std::vector<glm::vec2> tex = sphere.getTexCoords (); std::vector<glm::vec3> norm = sphere.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; int numIndices = sphere.getNumIndices (); for (int i = 0 ; i < numIndices; ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * ind.size (), &ind[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 5.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("ice.jpg" ); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , -1.0f )); mMat *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (2.0f , 2.0f , 2.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap (GL_TEXTURE_2D); if (glewIsSupported ("GL_EXT_texture_filter_anisotropic" )) { GLfloat anisoSetting = 0.0f ; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting); } glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, sphere.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Sphere" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
球体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #pragma once #include <cmath> #include <vector> #include <glm/glm.hpp> class Sphere { private : int numVertices; int numIndices; std::vector<glm::vec3> vertices; std::vector<int > indices; std::vector<glm::vec3> normals; std::vector<glm::vec2> texCoords; void init (int ) ; float toRadians (float degrees) ; public : Sphere (int prec = 48 ); int getNumVertices () ; int getNumIndices () ; std::vector<glm::vec3> getVertices () ; std::vector<int > getIndices () ; std::vector<glm::vec3> getNormals () ; std::vector<glm::vec2> getTexCoords () ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include "Sphere.h" Sphere::Sphere (int prec) { init (prec); } void Sphere::init (int _prec) { numVertices = (_prec + 1 ) * (_prec + 1 ); numIndices = _prec * _prec * 6 ; for (int i = 0 ; i < numVertices; ++ i) vertices.push_back (glm::vec3 ()); for (int i = 0 ; i < numVertices; ++ i) texCoords.push_back (glm::vec2 ()); for (int i = 0 ; i < numVertices; ++ i) normals.push_back (glm::vec3 ()); for (int i = 0 ; i < numIndices; ++ i) indices.push_back (0 ); for (int i = 0 ; i <= _prec; ++ i) for (int j = 0 ; j <= _prec; ++ j) { float y = static_cast <float >(cos (toRadians (180.0f - i * 180.0f / _prec))); float x = - static_cast <float >(cos (toRadians (j * 360.0f / _prec)) * fabs (cos (asin (y)))); float z = static_cast <float >(sin (toRadians (j * 360.0f / _prec)) * fabs (cos (asin (y)))); vertices[i * (_prec + 1 ) + j] = glm::vec3 (x, y, z); texCoords[i * (_prec + 1 ) + j] = glm::vec2 (j * 1.0f / _prec, i * 1.0f / _prec); normals[i * (_prec + 1 ) + j] = glm::vec3 (x, y, z); } for (int i = 0 ; i < _prec; ++ i) for (int j = 0 ; j < _prec; ++ j) { indices[6 * (i * _prec + j) + 0 ] = i * (_prec + 1 ) + j; indices[6 * (i * _prec + j) + 1 ] = i * (_prec + 1 ) + j + 1 ; indices[6 * (i * _prec + j) + 2 ] = (i + 1 ) * (_prec + 1 ) + j; indices[6 * (i * _prec + j) + 3 ] = i * (_prec + 1 ) + j + 1 ; indices[6 * (i * _prec + j) + 4 ] = (i + 1 ) * (_prec + 1 ) + j + 1 ; indices[6 * (i * _prec + j) + 5 ] = (i + 1 ) * (_prec + 1 ) + j; } } float Sphere::toRadians (float degrees) { return degrees * 2.0f * 3.14159f / 360.0f ; } int Sphere::getNumVertices () { return numVertices; } int Sphere::getNumIndices () { return numIndices; } std::vector<glm::vec3> Sphere::getVertices () { return vertices; } std::vector<int > Sphere::getIndices () { return indices; } std::vector<glm::vec3> Sphere::getNormals () { return normals; } std::vector<glm::vec2> Sphere::getTexCoords () { return texCoords; }
着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #version 460 core layout (location = 0 ) in vec3 position;layout (location = 1 ) in vec2 texCoord;uniform mat4 mv_matrix; uniform mat4 proj_matrix; layout (binding = 0 ) uniform sampler2D samp;out vec2 tc; void main () { gl_Position = proj_matrix * mv_matrix * vec4 (position, 1.0 ); tc = texCoord; }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core in vec2 tc; out vec4 color; layout (binding = 0 ) uniform sampler2D samp;void main () { color = texture (samp, tc); }
6.1.2 构建一个环面
产生环面需要一定的算法。
构建一个环面如图:
将一个顶点定位到原点的右侧,然后在 xOy 平面上的圆中让这个顶点围绕 z 轴旋转,以形成“环”,最后将这个环“向外”移动“内径”的距离。
在构建这些顶点时,会为每个顶点计算纹理坐标和法向量,还会额外为每个顶点生成与环面表面相切的向量(称为切向量)。在顶点创建之后,逐环遍历所有顶点,并在每个顶点上生成两个三角形。两个三角形的 6 个索引表的生成方式和之前的球体类似。
效果截图:
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Torus.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint textureID; GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; Torus torus (48 , 0.5f , 0.2f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { std::vector<int > ind = torus.getIndices (); std::vector<glm::vec3> vert = torus.getVertices (); std::vector<glm::vec2> tex = torus.getTexCoords (); std::vector<glm::vec3> norm = torus.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; int numIndices = torus.getNumIndices (); for (int i = 0 ; i < numIndices; ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * ind.size (), &ind[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 5.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("ice.jpg" ); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , -0.5f )); mMat = glm::rotate (mMat, 30.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); mMat *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (2.0f , 2.0f , 2.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap (GL_TEXTURE_2D); if (glewIsSupported ("GL_EXT_texture_filter_anisotropic" )) { GLfloat anisoSetting = 0.0f ; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting); } glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, torus.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Torus" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
环面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #pragma once #include <cmath> #include <vector> #include <glm/gtc/matrix_transform.hpp> class Torus { private : int numVertices; int numIndices; int prec; float inner, outer; std::vector<int > indices; std::vector<glm::vec3> vertices; std::vector<glm::vec2> texCoords; std::vector<glm::vec3> normals; std::vector<glm::vec3> sTangents; std::vector<glm::vec3> tTangents; void init () ; float toRadians (float degrees) ; public : Torus (int _prec = 48 , float _inner = 0.5 , float _outer = 0.2 ); int getNumVertices () ; int getNumIndices () ; std::vector<int > getIndices () ; std::vector<glm::vec3> getVertices () ; std::vector<glm::vec2> getTexCoords () ; std::vector<glm::vec3> getNormals () ; std::vector<glm::vec3> getStangents () ; std::vector<glm::vec3> getTtangents () ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include "Torus.h" Torus::Torus (int _prec, float _inner, float _outer) { prec = _prec; inner = _inner; outer = _outer; init (); } void Torus::init () { numVertices = (prec + 1 ) * (prec + 1 ); numIndices = prec * prec * 6 ; for (int i = 0 ; i < numVertices; ++ i) vertices.push_back (glm::vec3 ()); for (int i = 0 ; i < numVertices; ++ i) texCoords.push_back (glm::vec2 ()); for (int i = 0 ; i < numVertices; ++ i) normals.push_back (glm::vec3 ()); for (int i = 0 ; i < numIndices; ++ i) indices.push_back (0 ); for (int i = 0 ; i < numVertices; ++ i) sTangents.push_back (glm::vec3 ()); for (int i = 0 ; i < numVertices; ++ i) tTangents.push_back (glm::vec3 ()); for (int i = 0 ; i < prec + 1 ; ++ i) { float amt = toRadians (i * 360.0f / prec); glm::mat4 rMat = glm::rotate (glm::mat4 (1.0f ), amt, glm::vec3 (0.0f , 0.0f , 1.0f )); glm::vec3 initPos (rMat * glm::vec4(0.0f , outer, 0.0f , 1.0f )) ; vertices[i] = glm::vec3 (initPos + glm::vec3 (inner, 0.0f , 0.0f )); texCoords[i] = glm::vec2 (0.0f , static_cast <float >(i) / prec); rMat = glm::rotate (glm::mat4 (1.0f ), amt + (3.14159f / 2.0f ), glm::vec3 (0.0f , 0.0f , 1.0f )); tTangents[i] = glm::vec3 (rMat * glm::vec4 (0.0f , -1.0f , 0.0f , 1.0f )); sTangents[i] = glm::vec3 (glm::vec3 (0.0f , 0.0f , -1.0f )); normals[i] = glm::cross (tTangents[i], sTangents[i]); } for (int ring = 1 ; ring < prec + 1 ; ++ ring) for (int i = 0 ; i < prec + 1 ; ++ i) { float amt = toRadians (ring * 360.0f / prec); glm::mat4 rMat = glm::rotate (glm::mat4 (1.0f ), amt, glm::vec3 (0.0f , 1.0f , 0.0f )); vertices[ring * (prec + 1 ) + i] = glm::vec3 (rMat * glm::vec4 (vertices[i], 1.0f )); texCoords[ring * (prec + 1 ) + i] = glm::vec2 (ring * 2.0f / prec, texCoords[i].t); rMat = glm::rotate (glm::mat4 (1.0f ), amt, glm::vec3 (0.0f , 1.0f , 0.0f )); sTangents[ring * (prec + 1 ) + i] = glm::vec3 (rMat * glm::vec4 (sTangents[i], 1.0f )); rMat = glm::rotate (glm::mat4 (1.0f ), amt, glm::vec3 (0.0f , 1.0f , 0.0f )); tTangents[ring * (prec + 1 ) + i] = glm::vec3 (rMat * glm::vec4 (tTangents[i], 1.0f )); rMat = glm::rotate (glm::mat4 (1.0f ), amt, glm::vec3 (0.0f , 1.0f , 0.0f )); normals[ring * (prec + 1 ) + i] = glm::vec3 (rMat * glm::vec4 (normals[i], 1.0f )); } for (int ring = 0 ; ring < prec; ++ ring) for (int i = 0 ; i < prec; ++ i) { indices[((ring * prec + i) * 2 ) * 3 + 0 ] = ring * (prec + 1 ) + i; indices[((ring * prec + i) * 2 ) * 3 + 1 ] = (ring + 1 ) * (prec + 1 ) + i; indices[((ring * prec + i) * 2 ) * 3 + 2 ] = ring * (prec + 1 ) + i + 1 ; indices[((ring * prec + i) * 2 + 1 ) * 3 + 0 ] = ring * (prec + 1 ) + i + 1 ; indices[((ring * prec + i) * 2 + 1 ) * 3 + 1 ] = (ring + 1 ) * (prec + 1 ) + i; indices[((ring * prec + i) * 2 + 1 ) * 3 + 2 ] = (ring + 1 ) * (prec + 1 ) + i + 1 ; } } float Torus::toRadians (float degrees) { return degrees * 2.0f * 3.14159f / 360.0f ; } int Torus::getNumVertices () { return numVertices; } int Torus::getNumIndices () { return numIndices; } std::vector<int > Torus::getIndices () { return indices; } std::vector<glm::vec3> Torus::getVertices () { return vertices; } std::vector<glm::vec2> Torus::getTexCoords () { return texCoords; } std::vector<glm::vec3> Torus::getNormals () { return normals; } std::vector<glm::vec3> Torus::getStangents () { return sTangents; } std::vector<glm::vec3> Torus::getTtangents () { return tTangents; }
当绕 y 轴旋转生成环时,指定一个从 1 开始并增加到指定精度的变量 ring
。然后将纹理坐标值设置为 ring*2.0/prec
,使其取值范围为 0.0~2.0,再前面描述的,将纹理的平铺模式设为 GL_REPEAT
。运用这种方法的目的是避免纹理图像在水平方向上过度“拉伸”。反之,如果确实希望纹理完全围绕环面拉伸,只需从纹理坐标计算代码中删除 *2.0
。
6.2 加载外部构建的模型
通过一些建模工具也可以构建模型,如:Maya、Blender、Cinema4D等,而一般模型导出也有许多格式。此处使用 OBJ 格式进行处理。
OBJ 文件中的行以字符标签开头,表明该行的数据类型,常见的标签如下:
v:几何数据(顶点位置)
vt:纹理坐标
vn:顶点法向量
f:面(通常是三角形中的顶点)
可能还有标签存储对象名称、使用的材质、曲线、阴影和许多其他细节。
使用 Blender 绘制一个四棱锥,并导出为 OBJ文件:
使用记事本打开一个 OBJ 文件,其内容如下:
以 #
开头的是 Blender 添加的注释,可以忽略。
以 o
开头的是对象的名称,导入时也可以忽略。
以 s
开头表明不应该平滑处理,此处同样忽略。
以 v
开头的是顶点位置,顶点位置的顺序是 x y z
。此处有8个顶点,原点在中心。
以 vt
开头的是各种纹理坐标,纹理坐标列表比顶点列表长的原因是一些顶点参与了多个三角形的构建,并且在这些情况下可能使用不同的纹理坐标。
以 vn
开头的是各种法向量,通常也是比顶点列表长,原因同上。
以 f
开头的行指定面(三角形)。每个面(三角形)具有三个元素,由 /
分隔。每个元素的值分别是顶点列表、纹理坐标和法向量的索引,如第三个面是:3/4/3 5/2/3 4/5/3
,表明顶点列表第3个、第5个、第4个组成一个面,相应的纹理坐标是第4项、第2项、第5项,法向量相同都是第3项。
OBJ 格式的模型并不要求具有法向量,甚至纹理坐标。
如果模型没有纹理坐标和法向量,则面的数值将仅指定顶点索引:
如果模型有纹理坐标,没有法向量,则格式为:
如果模型没有纹理坐标,有法向量,则格式为:
在互联网上可以获得各种能导入 OBJ 模型的程序,但此处手动编写程序进行简单地导入 OBJ 文件,局限如下:
仅支持包含 3 个面属性字段,即以 f #/#/# #/#/# #/#/#
的形式存在。
材质标签将被忽略。
仅支持由单个三角形网格组成的 OBJ 模型。
假设每行上的元素都只用一个空格分隔。
没有使用上索引机制。
定义两个类(置于同一个文件):
ModelImporter
类用于从 OBJ 文件中导入模型数据。
ImportedModel
类用于保存并构建一个模型,类似于上面的球体类、环面类。
对于四棱锥,加载 OBJ 与先前手动构建的模型差不多。通过 Windows 自带的 3D 查看器,可以获取一些模型(格式为 .glb
,将其放入 Blender 中,再导出为 OBJ 文件)。获取模型后在 OpenGL 程序中加载,如图:
导入模型后,对模型的大小和位置进行修改,移动适合的相机位置并加入材质,最后如图:
更多可以自己摸索。
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint textureID; GLuint mvLoc, projLoc; glm::mat4 pMat, vMat, mMat, mvMat; ImportedModel model (" Pyramid.obj" ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; int numObjVertices = model.getNumVertices (); for (int i = 0 ; i < numObjVertices; ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 4.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("ice.jpg" ); glBindTexture (GL_TEXTURE_2D, textureID); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 30.0f * 3.14159f / 180.0f , glm::vec3 (0.0f , 1.0f , 0.0f )); mMat *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (1.0f , 1.0f , 1.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap (GL_TEXTURE_2D); if (glewIsSupported ("GL_EXT_texture_filter_anisotropic" )) { GLfloat anisoSetting = 0.0f ; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting); } glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , model.getNumVertices ()); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Pyramid" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
导入模型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #pragma once #include <glm/glm.hpp> #include <string> #include <vector> #include <fstream> #include <sstream> class ImportedModel { private : int numVertices; std::vector<glm::vec3> vertices; std::vector<glm::vec2> texCoords; std::vector<glm::vec3> normals; public : ImportedModel (); ImportedModel (const std::string &filePath); int getNumVertices () ; std::vector<glm::vec3> getVertices () ; std::vector<glm::vec2> getTexCoords () ; std::vector<glm::vec3> getNormals () ; }; class ModelImporter { private : std::vector<float > vertVals; std::vector<float > stVals; std::vector<float > normalVals; std::vector<float > triangleVerts; std::vector<float > texCoords; std::vector<float > normals; public : ModelImporter (); void parseOBJ (const std::string &filePath) ; int getNumVertices () ; std::vector<float > getVertices () ; std::vector<float > getTexCoords () ; std::vector<float > getNormals () ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 #include "ImportedModel.h" ImportedModel::ImportedModel () {} ImportedModel::ImportedModel (const std::string &filePath) { ModelImporter modelImporter = ModelImporter (); modelImporter.parseOBJ (filePath); numVertices = modelImporter.getNumVertices (); std::vector<float > verts = modelImporter.getVertices (); std::vector<float > tcs = modelImporter.getTexCoords (); std::vector<float > nms = modelImporter.getNormals (); for (int i = 0 ; i < numVertices; ++ i) { vertices.push_back (glm::vec3 (verts[i * 3 ], verts[i * 3 + 1 ], verts[i * 3 + 2 ])); texCoords.push_back (glm::vec2 (tcs[i * 2 ], tcs[i * 2 + 1 ])); normals.push_back (glm::vec3 (nms[i * 3 ], nms[i * 3 + 1 ], nms[i * 3 + 2 ])); } } int ImportedModel::getNumVertices () { return numVertices; } std::vector<glm::vec3> ImportedModel::getVertices () { return vertices; } std::vector<glm::vec2> ImportedModel::getTexCoords () { return texCoords; } std::vector<glm::vec3> ImportedModel::getNormals () { return normals; } ModelImporter::ModelImporter () {} void ModelImporter::parseOBJ (const std::string &filePath) { float x, y, z; std::string content; std::ifstream fin (filePath, std::ios::in) ; std::string line = "" ; while (!fin.eof ()) { getline (fin, line); if (line.compare (0 , 2 , "v " ) == 0 ) { std::stringstream s (line.erase(0 , 1 )) ; s >> x >> y >> z; vertVals.push_back (x); vertVals.push_back (y); vertVals.push_back (z); } if (line.compare (0 , 2 , "vt" ) == 0 ) { std::stringstream s (line.erase(0 , 2 )) ; s >> x >> y; stVals.push_back (x); stVals.push_back (y); } if (line.compare (0 , 2 , "vn" ) == 0 ) { std::stringstream s (line.erase(0 , 2 )) ; s >> x >> y >> z; normalVals.push_back (x); normalVals.push_back (y); normalVals.push_back (z); } if (line.compare (0 , 2 , "f " ) == 0 ) { std::string oneCorner, v, t, n; std::stringstream s (line.erase(0 , 2 )) ; for (int i = 0 ; i < 3 ; ++ i) { std::getline (s, oneCorner, ' ' ); std::stringstream oneCornerSS (oneCorner) ; std::getline (oneCornerSS, v, '/' ); std::getline (oneCornerSS, t, '/' ); std::getline (oneCornerSS, n, '/' ); int vertRef = (std::stoi (v) - 1 ) * 3 ; int tcRef = (std::stoi (t) - 1 ) * 2 ; int nmRef = (std::stoi (n) - 1 ) * 3 ; triangleVerts.push_back (vertVals[vertRef]); triangleVerts.push_back (vertVals[vertRef + 1 ]); triangleVerts.push_back (vertVals[vertRef + 2 ]); texCoords.push_back (stVals[tcRef]); texCoords.push_back (stVals[tcRef + 1 ]); normals.push_back (normalVals[nmRef]); normals.push_back (normalVals[nmRef + 1 ]); normals.push_back (normalVals[nmRef + 2 ]); } } } } int ModelImporter::getNumVertices () { return triangleVerts.size () / 3 ; } std::vector<float > ModelImporter::getVertices () { return triangleVerts; } std::vector<float > ModelImporter::getTexCoords () { return texCoords; } std::vector<float > ModelImporter::getNormals () { return normals; }
七、光照
7.1 光照模型
光照模型有时也称为着色模型。
常见的光照模型称为 ADS 模型,基于标记为 A、D 和 S 三种类型的反射:
A:环境光反射(ambient reflection):模拟低级光照,影响场景中的所有物体。
D:漫反射(diffuse reflection):根据光线的入射角度调整物体亮度。
S:镜面反射(specular reflection):展示物体的光泽,通过在物体表面上,光线直接地反射到我们的眼睛的位置,策略性地放置适当大小的高光来体现。
使用 ADS 光照模型需要指定用于像素输出的 RGBA 值上因光照
而产生的分量。因素包括:
光源类型及其环境光反射、漫反射和镜面反射特性;
对象材质的环境光反射、漫反射和镜面反射特征;
对象材质的“光泽度”;
光线照射物体的角度;
从中查看场景的角度。
7.2 光源
常见的光源类型有:
全局光:通常称为全局环境光,仅包含环境光组件
定向光:远距离光
位置光:点光源
聚光灯
全局环境光用于显示对象的每个像素都有相同的光照。
全局环境光模拟了现实世界光线经过多次反射,无法确定光源和方向 。
仅有环境光反射分量,没有漫反射或镜面反射分量。
通常被建模为偏暗的白光。
用 RGBA 值设定:float globalAmbient[4] = {0.6f, 0.6f, 0.6f, 1.0f};
RGBA 值的范围是 0.0 到 1.0。A 设置为1。
定向光或远距离光
没有源位置但具有方向 。用于模拟光源距离非常远,光线接近平行的情况。
定向光对物体的影响取决于光照角度,物体朝向定向光的一侧比对侧更亮。
建模定向光需要指定其方向(向量)及其环境、漫反射和镜面特征。
用 RGBA 设定,如指向 z 轴负方向的红色定向光代码:
1 2 3 4 float dirLightAmbient[4 ] = {0.1f , 0.0f , 0.0f , 1.0f };float dirLightDiffuse[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float dirLightSpecular[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float dirLightDirection[3 ] = {0.0f , 0.0f , -1.0f };
位置光在 3D 场景中具有特定位置,用以体现靠近场景的光源。
位置光没有方向 ,因为它对场景中每个顶点的光照方向都不同。
位置光还可以包含衰减因子,用于模拟它们的强度随着距离减小的程度。
用 RGBA 设定,如位置(5,2,-3)处的红色位置光可以指定为:
1 2 3 4 float posLightAmbient[4 ] = {0.1f , 0.0f , 0.0f , 1.0f };float posLightDiffuse[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float posLightSpecular[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float posLightPosition[3 ] = {5.0f , 2.0f , -3.0f };
衰减因子有许多建模方式,其中一种是使用恒定衰减、线性衰减和二次方衰减,引入3个非负可调参数,与离光源的距离 d d d 结合进行计算:
a t t e n u a t i o n F a c t o r = 1 k c + k l d + k q d 2 attenuationFactor = \frac{1}{k_c + k_l d + k_q d^2}
a t t e n u a t i o n F a c t o r = k c + k l d + k q d 2 1
聚光灯
具有衰减指数模拟随光束角度的强度变化,具有衰减因子模拟光束距离衰减。
当 φ \varphi φ 小于 θ \theta θ 时,计算 φ \varphi φ 的余弦的衰减指数次幂来计算强度因子。
当 φ \varphi φ 大于 θ \theta θ 时,强度因子为0。
强度因子取值范围为 [ 0 , 1 ] [0,1] [ 0 , 1 ] ,将强度因子乘光的强度即可模拟锥形效果。
用 RGBA 设定,如位置(5,2,-3)处向下照射 z 轴负方向的红色聚光灯可以表示为:
1 2 3 4 5 6 7 float spotLightAmbient[4 ] = {0.1f , 0.0f , 0.0f , 1.0f };float spotLightDiffuse[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float spotLightSpecular[4 ] = {1.0f , 0.0f , 0.0f , 1.0f };float spotLightPosition[3 ] = {5.0f , 2.0f , -3.0f };float spotLightDirection[3 ] = {0.0f , 0.0f , -1.0f };float spotLightCutoff = 20.0f ;float spotLightExponent = 10.0f ;
当设计拥有许多光源的系统时,程序员应该考虑创建相应的类结构,如定义 Light 类及其子类 GlobalAmbient、Directional、Positional、Spotlight
7.3 材质
场景中给物体的外观添加光照,产生反射特性。即对象与 ADS 光照模型相互作用。
通过指定4个值,在 ADS 光照模型中模拟材质:
如锡铝合金的效果,指定:
1 2 3 float pewterMatAmbient[4 ] = {0.11f , 0.06f , 0.11f , 1.0f };float pewterMatDiffuse[4 ] = {0.43f , 0.47f , 0.54f , 1.0f };float pewterMatSpecular[4 ] = {0.33f , 0.33f , 0.52f , 1.0f };
没有纹理的物体在渲染时,可以指定材质特性。此处预定义一些可选择的材质(置于Util类中):
黄金、白银、青铜材质代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 float *Util::goldAmbient () { static float ambient[4 ] = { 0.2473f , 0.1995f , 0.0745f , 1.0f }; return ambient; } float *Util::goldDiffuse () { static float diffuse[4 ] = { 0.7516f , 0.6065f , 0.2265f , 1.0f }; return diffuse; } float *Util::goldSpecular () { static float specular[4 ] = { 0.6283f , 0.5559f , 0.3661f , 1.0f }; return specular; } float Util::goldShininess () { return 51.2f ; } float *Util::silverAmbient () { static float ambient[4 ] = { 0.1923f , 0.1923f , 0.1923f , 1.0f }; return ambient; } float *Util::silverDiffuse () { static float diffuse[4 ] = { 0.5075f , 0.5075f , 0.5075f , 1.0f }; return diffuse; } float *Util::silverSpecular () { static float specular[4 ] = { 0.5083f , 0.5083f , 0.5083f , 1.0f }; return specular; } float Util::silverShininess () { return 51.2f ; } float *Util::bronzeAmbient () { static float ambient[4 ] = { 0.2125f , 0.1275f , 0.054f , 1.0f }; return ambient; } float *Util::bronzeDiffuse () { static float diffuse[4 ] = { 0.714f , 0.4284f , 0.18144f , 1.0f }; return diffuse; } float *Util::bronzeSpecular () { static float specular[4 ] = { 0.393548f , 0.271906f , 0.166721f , 1.0f }; return specular; } float Util::bronzeShininess () { return 25.6f ; }
这些代码仅仅提供了用于描述并存储场景中元素所需光照和材质特性的一种方式,仍然需要自己计算光照。
7.4 ADS光照计算
当绘制场景时,每个顶点坐标都会进行变换以将 3D 世界模拟到 2D 屏幕上。每个像素的颜色都是栅格化、纹理贴图以及插值的结果。
现在需要加入一个新的步骤来调整这些栅格化之后的像素颜色,以便反应场景中的光照和材质。
首先确定每个像素的反射强度 I:
I o b s e r v e d = I a m b i e n t + I d i f f u s e + I s p e c u l a r I_{observed} = I_{ambient} + I_{diffuse} + I_{specular}
I o b s e r v e d = I a m b i e n t + I d i f f u s e + I s p e c u l a r
其中环境光值为场景环境光与材质环境光分量的乘积(RGB分别乘积):
I a m b i e n t = L i g h t a m b i e n t M a t e r i a l a m b i e n t = { I a m b i e n t r e d = L i g h t a m b i e n t r e d M a t e r i a l a m b i e n t r e d I a m b i e n t g r e e n = L i g h t a m b i e n t g r e e n M a t e r i a l a m b i e n t g r e e n I a m b i e n t b l u e = L i g h t a m b i e n t b l u e M a t e r i a l a m b i e n t b l u e I_{ambient}=Light_{ambient}\ Material_{ambient}=
\begin{cases}
I_{ambient}^{red}=Light_{ambient}^{red}Material_{ambient}^{red}\\
I_{ambient}^{green}=Light_{ambient}^{green}Material_{ambient}^{green}\\
I_{ambient}^{blue}=Light_{ambient}^{blue}Material_{ambient}^{blue}
\end{cases}
I a m b i e n t = L i g h t a m b i e n t M a t e r i a l a m b i e n t = ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ I a m b i e n t r e d = L i g h t a m b i e n t r e d M a t e r i a l a m b i e n t r e d I a m b i e n t g r e e n = L i g h t a m b i e n t g r e e n M a t e r i a l a m b i e n t g r e e n I a m b i e n t b l u e = L i g h t a m b i e n t b l u e M a t e r i a l a m b i e n t b l u e
其中漫反射分量基于光对于平面的入射角 θ \theta θ 。
其中确定入射角 θ \theta θ 需要:
求从绘制向量到光源的向量(或与光照方向相反的向量) L L L
渲染物体表明的法向量 N N N
由 { I d i f f u s e = cos ( θ ) L i g h t d i f f u s e M a t e r i a l d i f f u s e cos ( θ ) = N ⋅ L ∣ N ∥ L ∣ − π 2 ≤ θ ≤ π 2 得 I d i f f u s e = L i g h t d i f f u s e M a t e r i a l d i f f u s e max ( N ⋅ L ∣ N ∥ L ∣ , 0 ) 同样分别进行 R G B 运算 由\begin{cases}
I_{diffuse}=\cos(\theta)Light_{diffuse}Material_{diffuse}\\
\cos(\theta)=\frac{N\cdot L}{\vert N\|L\vert}\\
-\frac{\pi}{2}\leq\theta\leq\frac{\pi}{2}
\end{cases}\\
得I_{diffuse}=Light_{diffuse}\ Material_{diffuse}\ \max(\frac{N\cdot L}{\vert N\|L\vert},0)\\
同样分别进行RGB运算
由 ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ I d i f f u s e = cos ( θ ) L i g h t d i f f u s e M a t e r i a l d i f f u s e cos ( θ ) = ∣ N ∥ L ∣ N ⋅ L − 2 π ≤ θ ≤ 2 π 得 I d i f f u s e = L i g h t d i f f u s e M a t e r i a l d i f f u s e max ( ∣ N ∥ L ∣ N ⋅ L , 0 ) 同 样 分 别 进 行 R G B 运 算
其中镜面反射分量除了与入射角相关,也与光在表面上的反射角以及观察点与反光表面之间的夹角相关。下图 R 表示光反射,V 表示观察向量(像素到眼睛)。
V 是从眼睛到像素的取反。
φ \varphi φ 越小,镜面反射分量越大。
φ \varphi φ 用于计算镜面反射分量的方式取决于物体的光泽度。光泽度越大越闪亮,则入射光更多反射给眼睛;光泽度较小,不那么闪亮,则镜面高光会散开。
反光度可以用衰减函数建模,衰减函数表示角度 φ \varphi φ 增大时镜面反射分量衰减到0的速度。
使用 cos n ( φ ) \cos^n(\varphi) cos n ( φ ) 进行衰减,指数越大,衰减速度越快。
n n n 又叫作材质的反光度因子。
反光度因子以光泽度形式给出。
镜面反射计算如下(实际运算使用RGB运算):
I s p e c u l a r = L i g h t s p e c u l a r M a t e r i a l s p e c u l a r max ( ( R ⋅ V ∣ R ∥ V ∣ ) n , 0 ) I_{specular}=Light_{specular}\ Material_{specular}\ \max((\frac{R\cdot V}{\vert R\|V\vert})^n,0)\\
I s p e c u l a r = L i g h t s p e c u l a r M a t e r i a l s p e c u l a r max ( ( ∣ R ∥ V ∣ R ⋅ V ) n , 0 )
7.5 实现ADS光照
平滑着色方法:
7.5.1 Gouraud 着色
这方法又称为双线性光强插值法
使用 3D 图形管线中的自动插值渲染,适用于现代显卡。过程如下:
确定每个顶点的颜色,并进行光照相关计算。
允许正常的栅格化过程在插入像素时对颜色也进行插值(同时也对光照进行插值)。
OpenGL 中,大多数光照计算都是顶点着色器中完成,片段着色器仅传递并展示自动插值的光照后的颜色。
顶点着色器
根据顶点计算 N、L、V 和 R 向量
计算 A、D、S分量
输出属性、光照后的颜色、gl_position
片段着色器
传入插值:颜色、位置
程序代码如下:
Util类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #pragma once #include <GL/glew.h> #include <GLFW/glfw3.h> #include <SOIL2/soil2.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #include <string> #include <iostream> #include <fstream> #include <cmath> #include <vector> class Util { public : static std::string readShaderSource (const std::string &path) ; static void printShaderLog (GLuint shader) ; static void printProgramLog (GLuint program) ; static bool checkOpenGLerror () ; static GLuint createShadeProgram (const std::string vpath, const std::string fpath) ; static GLuint loadTexture (const std::string &imgPath) ; static float *goldAmbient () ; static float *goldDiffuse () ; static float *goldSpecular () ; static float goldShininess () ; static float *silverAmbient () ; static float *silverDiffuse () ; static float *silverSpecular () ; static float silverShininess () ; static float *bronzeAmbient () ; static float *bronzeDiffuse () ; static float *bronzeSpecular () ; static float bronzeShininess () ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 #include "Util.h" std::string Util::readShaderSource (const std::string &path) { std::string content = "" ; std::ifstream fileStream (path, std::ios::in) ; std::string line = "" ; while (!fileStream.eof ()) { getline (fileStream, line); content.append (line + "\n" ); } fileStream.close (); return content; } void Util::printShaderLog (GLuint shader) { int len = 0 ; int chWrittn = 0 ; char *log; glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetShaderInfoLog (shader, len, &chWrittn, log); std::cout << "Shader Info: " << log << "\n" ; delete [] log; } } void Util::printProgramLog (GLuint program) { int len = 0 ; int chWrittn = 0 ; char *log; glGetProgramiv (program, GL_INFO_LOG_LENGTH, &len); if (len > 0 ) { log = new char [len]; glGetProgramInfoLog (program, len, &chWrittn, log); std::cout << "Program Info: " << log << "\n" ; delete [] log; } } bool Util::checkOpenGLerror () { bool err = false ; int glErr = glGetError (); while (glErr != GL_NO_ERROR) { std::cout << "glError: " << glErr << "\n" ; err = true ; glErr = glGetError (); } return err; } GLuint Util::createShadeProgram (const std::string vpath, const std::string fpath) { GLint vertCompiled, fragCompiled, linked; std::string vs = readShaderSource (vpath); std::string fs = readShaderSource (fpath); const char *vshaderSource = vs.c_str (); const char *fshaderSource = fs.c_str (); GLuint vShader = glCreateShader (GL_VERTEX_SHADER); GLuint fShader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vShader, 1 , &vshaderSource, NULL ); glShaderSource (fShader, 1 , &fshaderSource, NULL ); glCompileShader (vShader); checkOpenGLerror (); glGetShaderiv (vShader, GL_COMPILE_STATUS, &vertCompiled); if (vertCompiled == GL_FALSE) { std::cout << "vertex compilation failed\n" ; printShaderLog (vShader); } glCompileShader (fShader); checkOpenGLerror (); glGetShaderiv (fShader, GL_COMPILE_STATUS, &fragCompiled); if (fragCompiled == GL_FALSE) { std::cout << "fragment compilation failed\n" ; printShaderLog (fShader); } GLuint vProgram = glCreateProgram (); glAttachShader (vProgram, vShader); glAttachShader (vProgram, fShader); glLinkProgram (vProgram); checkOpenGLerror (); glGetProgramiv (vProgram, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { std::cout << "linking failed\n" ; printProgramLog (vProgram); } return vProgram; } GLuint Util::loadTexture (const std::string &imgPath) { GLuint textureID; textureID = SOIL_load_OGL_texture (imgPath.c_str (), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y); if (textureID == 0 ) std::cout << "Failed to load texture: " << imgPath << "\n" ; return textureID; } float *Util::goldAmbient () { static float ambient[4 ] = { 0.2473f , 0.1995f , 0.0745f , 1.0f }; return ambient; } float *Util::goldDiffuse () { static float diffuse[4 ] = { 0.7516f , 0.6065f , 0.2265f , 1.0f }; return diffuse; } float *Util::goldSpecular () { static float specular[4 ] = { 0.6283f , 0.5559f , 0.3661f , 1.0f }; return specular; } float Util::goldShininess () { return 51.2f ; } float *Util::silverAmbient () { static float ambient[4 ] = { 0.1923f , 0.1923f , 0.1923f , 1.0f }; return ambient; } float *Util::silverDiffuse () { static float diffuse[4 ] = { 0.5075f , 0.5075f , 0.5075f , 1.0f }; return diffuse; } float *Util::silverSpecular () { static float specular[4 ] = { 0.5083f , 0.5083f , 0.5083f , 1.0f }; return specular; } float Util::silverShininess () { return 51.2f ; } float *Util::bronzeAmbient () { static float ambient[4 ] = { 0.2125f , 0.1275f , 0.054f , 1.0f }; return ambient; } float *Util::bronzeDiffuse () { static float diffuse[4 ] = { 0.714f , 0.4284f , 0.18144f , 1.0f }; return diffuse; } float *Util::bronzeSpecular () { static float specular[4 ] = { 0.393548f , 0.271906f , 0.166721f , 1.0f }; return specular; } float Util::bronzeShininess () { return 25.6f ; }
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc, nLoc; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; glm::vec3 initialLightLoc = glm::vec3 (5.0f , 2.0f , 2.0f ); float globalAmbient[4 ] = { 0.7f , 0.7f , 0.7f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float *matAmb = Util::goldAmbient ();float *matDif = Util::goldDiffuse ();float *matSpe = Util::goldSpecular ();float matShi = Util::goldShininess ();Torus model (48 , 0.5f , 0.2f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (glm::mat4 vMatrix) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y; lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , matAmb); glProgramUniform4fv (renderProgram, mDifLoc, 1 , matDif); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , matSpe); glProgramUniform1f (renderProgram, mShiLoc, matShi); } void makeVertexArray () { std::vector<int > ind = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * ind.size (), &ind[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 4.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 35.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); currentLightPos = glm::vec3 (initialLightLoc.x, initialLightLoc.y, initialLightLoc.z); installLights (vMat); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (1 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Light" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
注意:
定义函数 installLights()
将光源在视觉空间中的位置和材质的 ADS 特性读入相应的统一变量。
直接对法向量应用 MV 矩阵不能保证法向量依然与物体表面垂直。使用 MV 矩阵的逆转置矩阵,用于变换法向量。
变量 lightPosV 包含光源在相机空间中的位置,每帧只需要计算一次。
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; struct Material { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec3 vertNormal;uniform mat4 mv_matrix; uniform mat4 proj_matrix; uniform mat4 norm_matrix; uniform vec4 globalAmbient; uniform PositionalLight light; uniform Material material; out vec4 color; void main () { vec4 P = mv_matrix * vec4 (vertPos, 1.0 ); vec3 N = normalize ((norm_matrix * vec4 (vertNormal, 0.0 )).xyz); vec3 L = normalize (light.position - P.xyz); vec3 V = normalize (-P.xyz); vec3 R = reflect (-L, N); vec3 ambient = ((globalAmbient * material.ambient) + (light.ambient * material.ambient)).xyz; vec3 diffuse = light.diffuse.xyz * material.diffuse.xyz * max (dot (N, L), 0.0 ); vec3 specular = pow (max (dot (R, V), 0.0f ), material.shininess) * material.specular.xyz * light.specular.xyz; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); color = vec4 (ambient + diffuse + specular, 1.0 ); }
注意:
字段选择器符号“.xyz”,是将 vec4
转换为仅包含其前 3 个元素的等效 vec3
的“快捷方式”。
normalize()
用来将向量转换为单位长度。
reflect()
函数用与计算一个向量基于另一个向量的反射。
dot()
函数为点乘运算。
1 2 3 4 5 6 7 8 9 10 #version 460 core in vec4 color; out vec4 fragColor; void main () { fragColor = color; }
Gouraud 着色容易受到其他伪影影响。
如果镜面高光范围内一个模型顶点也没有,那么它可能不会被渲染出来。
镜面反射分量是依顶点计算的,因此,当模型的所有顶点都没有镜面反射分量时,其栅格化后的像素也不会有镜面反射效果。
7.5.2 Phong 着色
该算法的结构类似 Gouraud 着色算法,不同之处在于光照计算是按像素而非顶点 完成的。
顶点着色器
计算向量 N、L、V
输出属性 N、L、V、gl_position
片段着色器
传入插值 N、L、V
计算 R、θ、φ
计算 ADS 分量
输出颜色
将之前在顶点着色器中实现的过程移到片段着色器中进行。
故 main.cpp和 Util 类无需改变,只需修改着色器的代码。
对于原始的 Phong 进行优化:
R R R 的计算是非必须的。
角 φ \varphi φ 可以通过 L L L 和 V V V 的角平分线向量 H H H 得到。
H = L + V H = L + V H = L + V
θ = 1 2 φ \theta = \frac{1}{2}\varphi θ = 2 1 φ
用 α \alpha α 代替 φ \varphi φ 就已经可以获得足够好的结果。
最后镜面反射分量在光泽度乘 3.0 作为改善镜面高光的微调。
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec3 vertNormal;uniform mat4 mv_matrix; uniform mat4 proj_matrix; uniform mat4 norm_matrix; uniform PositionalLight light; out vec3 normals; out vec3 lightDir; out vec3 halfVector; void main () { vec3 _vertPos; _vertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; lightDir = light.position - _vertPos; normals = (norm_matrix * vec4 (vertNormal, 1.0 )).xyz; halfVector = (lightDir + (-_vertPos)).xyz; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; struct Material { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }; uniform vec4 globalAmbient; uniform PositionalLight light; uniform Material material; in vec3 normals; in vec3 lightDir; in vec3 halfVector; out vec4 fragColor; void main () { vec3 L = normalize (lightDir); vec3 N = normalize (normals); vec3 H = normalize (halfVector); float cosTheta = dot (L, N); float cosPhi = dot (H, N); vec3 ambient = ((globalAmbient * material.ambient) + (light.ambient * material.ambient)).xyz; vec3 diffuse = light.diffuse.xyz * material.diffuse.xyz * max (cosTheta, 0.0 ); vec3 specular = light.specular.xyz * material.specular.xyz * pow (max (cosPhi, 0.0 ), material.shininess * 3.0 ); fragColor = vec4 (ambient + diffuse + specular, 1.0 ); }
7.6 结合光照和纹理
结合光照和纹理的方式取决于物体的特性及其纹理的目的:
纹理图像很写实地反映了物体真实的表面外观;
物体同时具有材质和纹理;
材质包括阴影和反射信息;
有多种光或多个纹理
当物体拥有一个简单的纹理,同时进行光照时,简单的方法是:
在片段着色器中完全将材质特性去除;
使用纹理取样所得纹理颜色代替材质的 ADS 值。
对于闪亮表面:
纹理颜色影响了环境光和漫反射分量,而镜面反射颜色仅由光源决定。
1 fragColor = textureColor * (ambient + diffuse) + specular;
1 2 3 textureColor = texture (samp, texCoord); lightColor = (ambLight * ambMaterial) + (diffLight * diffMaterial) + specLight; fragColor = 0.5 * textureColor + 0.5 * lightColor;
结合了光照、材质、纹理,并能够扩展到多个光源、多种材质的情况。
1 2 3 4 5 6 7 8 texture1Color = texture (sampler1, texCoord); texture2Color = texture (sampler2, texCoord); light1Color = (ambLight1 * ambMaterial) + (diffLight1 * diffMaterial) + specLight1; light2Color = (ambLight2 * ambMaterial) + (diffLight2 * diffMaterial) + specLight2; fragColor = 0.25 * texture1Color + 0.25 * texture2Color + 0.25 * light1Color + 0.25 * light2Color;
稍作尝试的一个例子:
对于这个例子的代码(ImportedModel 类和 Util 类同之前):
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc, nLoc; GLuint textureID; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; glm::vec3 initialLightLoc = glm::vec3 (5.0f , 2.0f , 2.0f ); float globalAmbient[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float *matAmb = Util::bronzeAmbient ();float *matDif = Util::bronzeDiffuse ();float *matSpe = Util::bronzeSpecular ();float matShi = Util::bronzeShininess ();ImportedModel model ("model/velociraptor.obj" ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (glm::mat4 vMatrix) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y * 10.0f * cos (glfwGetTime () * 2.0f ); lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , matAmb); glProgramUniform4fv (renderProgram, mDifLoc, 1 , matDif); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , matSpe); glProgramUniform1f (renderProgram, mShiLoc, matShi); } void makeVertexArray () { std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("Phong/vertex.glsl" , "Phong/fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 4.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("textureImg/ice.jpg" ); glBindTexture (GL_TEXTURE_2D, textureID); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , -1.0f , 0.0f )); mMat = glm::rotate (mMat, 35.0f * 3.14159f / 180.0f , glm::vec3 (0.0f , 1.0f , 0.0f )); mMat *= glm::scale (glm::mat4 (1.0f ), glm::vec3 (5.0f , 5.0f , 5.0f )); currentLightPos = glm::vec3 (initialLightLoc.x, initialLightLoc.y, initialLightLoc.z); installLights (vMat); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (2 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (2 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap (GL_TEXTURE_2D); if (glewIsSupported ("GL_EXT_texture_filter_anisotropic" )) { GLfloat anisoSetting = 0.0f ; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting); } glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , model.getNumVertices ()); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Light" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec2 texCoord;layout (location = 2 ) in vec3 vertNormal;layout (binding = 0 ) uniform sampler2D samp;uniform mat4 mv_matrix; uniform mat4 proj_matrix; uniform mat4 norm_matrix; uniform PositionalLight light; out vec3 normals; out vec3 lightDir; out vec3 halfVector; out vec2 tc; void main () { vec3 _vertPos; _vertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; lightDir = light.position - _vertPos; normals = (norm_matrix * vec4 (vertNormal, 1.0 )).xyz; halfVector = (lightDir + (-_vertPos)).xyz; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); tc = texCoord; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; struct Material { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }; layout (binding = 0 ) uniform sampler2D samp;uniform vec4 globalAmbient; uniform PositionalLight light; uniform Material material; in vec2 tc; in vec3 normals; in vec3 lightDir; in vec3 halfVector; out vec4 fragColor; void main () { vec3 L = normalize (lightDir); vec3 N = normalize (normals); vec3 H = normalize (halfVector); float cosTheta = dot (L, N); float cosPhi = dot (H, N); vec3 ambient = ((globalAmbient * material.ambient) + (light.ambient * material.ambient)).xyz; vec3 diffuse = light.diffuse.xyz * material.diffuse.xyz * max (cosTheta, 0.0 ); vec3 specular = light.specular.xyz * material.specular.xyz * pow (max (cosPhi, 0.0 ), material.shininess * 3.0 ); vec4 textureColor = texture (samp, tc); vec4 lightColor = light.ambient + light.diffuse + light.specular; vec4 stripLightColor = vec4 (ambient + diffuse + specular, 1.0 ); fragColor = 0.3 * textureColor + 0.7 * stripLightColor; }
八、阴影
8.1 投影阴影
投影阴影:给定一个位于 ( x 1 , y 1 , z 1 ) (x_1, y_1, z_1) ( x 1 , y 1 , z 1 ) 的点光源,一个需要渲染的物体(物体上的点 ( x w , y w , z w ) (x_w, y_w, z_w) ( x w , y w , z w ) )和一个投射阴影的平面,可以通过生成一个变换矩阵:
( x w , y w , z w ) → ( x S , 0 , z S ) (x_w, y_w, z_w)\rightarrow(x_S,0,z_S)
( x w , y w , z w ) → ( x S , 0 , z S )
投影阴影易于实现,但仅适用于平坦表面。
8.2 阴影体
找到被物体阴影覆盖的阴影体,之后减少视体与阴影体相交部分中的多边形的颜色强度。
阴影体的优点在于其高度准确,比起其他方法来更不容易产生伪影。
几何着色器可以用于计算阴影体,模板缓冲区可以用于判断像素
是否在阴影体内。有些显卡对于特定的阴影体操作优化提供了硬件支持。
8.3 阴影贴图
阴影贴图的想法:光线无法“看到”的任何东西都在阴影中。也就是说,如果对象 1 阻挡光线到达对象 2,等同于光线不能“看到”对象 2
所以计算策略是:暂时将相机移动到光的位置,应用 Z-buffer 算法,然后使用生成的深度信息来计算。
由此可得,渲染场景需要两轮:
第一轮从光源角度渲染场景(不绘制)。对于每个像素,深度缓冲区包含光源与最近的对象之间的距离。
将深度缓冲区复制到纹理对象。
当纹理对象用于储存阴影深度信息时,称其为阴影纹理。
将深度缓冲区复制到纹理中的过程称为“阴影贴图”。
第二轮从相机角度渲染场景(正常渲染)。对于每个像素,在阴影纹理中查找相应的位置。
如果相机到渲染点的距离大于从阴影纹理检索到的值,则在该像素处绘制的对象离光源的距离比当前离光源最近的对象离光源更远,得出该像素处于阴影中。
阴影的处理:仅渲染环境光,忽略漫反射和镜面反射分量。
8.3.1 第一轮——从光源位置绘制
第一轮中并不是真的在显示其中绘制场景,故仅需用到顶点着色器。
通过变换矩阵(构建观察矩阵)移动相机:
第一轮需要处理的细节:
配置缓冲区和阴影纹理。
禁用颜色输出。
在光源处为视野中的物体构建一个 LookAt 矩阵。
启用 GLSL 第一轮着色器程序,准备接受 MVP 矩阵。
M:对象的模型矩阵
V:LookAt 矩阵作为观察矩阵
P:透视矩阵
该 MVP 矩阵 称为 shadowMVP
。
为每个对象创建一个 shadowMVP
矩阵,并调用 glDrawArrays()
。
顶点着色器变成:
1 2 3 4 5 6 7 8 9 #version 460 core layout (location = 0 ) in vec3 vertPos;uniform mat4 shadowMVP;void main(){ gl_Position = shadowMVP * vec4 (vertPos, 1.0 ); }
片段着色器变成:
1 2 #version 460 core void main() {}
8.3.2 将深度缓冲区复制到纹理
OpenGL 提供了两种将深度缓冲区深度数据放入纹理单元的方法:
生成空阴影纹理,然后使用命令 glCopyTexImage2D()
将活动的深度缓冲区复制到阴影纹理中。
在第一轮中构建“自定义帧缓冲区”,并使用命令 glFrameBufferTexture2D()
将阴影纹理附加到上面。
使用该方法无须将深度缓冲区复制到纹理,因为缓冲区已经附加了纹理,深度信息由 OpenGL 自动放入纹理中。
8.3.3 第二轮——从相机位置绘制
第二轮需要渲染完整的场景、其中的所有物体以及光照、材质和装饰场景中物体的纹理,同时还确定阴影。
使用两个 MVP 矩阵:
一个将对象坐标转换为屏幕坐标的标准 MVP 矩阵(像之前一样);
一个是第一轮生成的 shadowMVP 矩阵,用于从光源的角度进行渲染,即从阴影纹理中查找深度信息。
从纹理贴图查找像素时,应该处理 OpenGL 相机空间使用 [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] 而纹理贴图使用 [ 0 , 1 ] [0,1] [ 0 , 1 ] 空间的问题:
B = [ 0.5 0 0 0.5 0 0.5 0 0.5 0 0 0.5 0.5 0 0 0 1 ] B = \begin{bmatrix}
0.5 & 0 & 0 & 0.5 \\
0 & 0.5 & 0 & 0.5 \\
0 & 0 & 0.5 & 0.5 \\
0 & 0 & 0 & 1\\
\end{bmatrix}
B = ⎣ ⎢ ⎢ ⎢ ⎡ 0 . 5 0 0 0 0 0 . 5 0 0 0 0 0 . 5 0 0 . 5 0 . 5 0 . 5 1 ⎦ ⎥ ⎥ ⎥ ⎤
将 B B B 和 shadowMVP
矩阵相乘,得到 shadowMVP2
矩阵。
第二轮的大概操作:
构建变换矩阵 B B B ,用于从光照空间转换到纹理空间。
启用阴影纹理,进行查找。
启用颜色输出。
启用 GLSL 第二轮渲染程序。
根据相机正常位置为对象构建 MVP 矩阵。
构建 shadowMVP2 矩阵。着色器用其查找阴影纹理中的像素坐标。
将生成的变换矩阵发送到着色器统一变量。
启用包含顶点、法向量和纹理坐标的缓冲区。
调用 glDrawArrays()
。
着色器的工作:
顶点着色器将顶点位置从相机空间转换为光照空间,将结果发送到顶点属性中的片段着色器。
片段着色器调用 textureProj()
函数指示像素是否处于引用中(返回0或1)。如果位于阴影中,则剔除漫反射和镜面反射。
使用特殊采样器变量类型 sampler2DShadow
,可以附加到程序中的阴影纹理中。
关注细节:正在渲染的像素和阴影纹理中的值的深度比较
在模型空间中使用顶点坐标,与 shadowMVP2 相乘生成阴影纹理坐标(对应于投影到光照空间中的顶点坐标,是之前从光源视角生成的)。
经过插值后的光照空间(3D)坐标 (x, y, z) 在片段着色器中使用情况:
z 分量表示从光到像素的距离;
(x, y) 分量用于检索存储在阴影纹理(2D)中的深度信息。
将该检索的值(到最靠近光的物体的距离)与 z 进行比较。
例子效果展示如下图:
代码:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" const int numVAO = 1 , numVBO = 5 ;GLuint vao[numVAO], vbo[numVBO]; GLuint renderProgram1, renderProgram2; int width, height;float aspect;GLuint mvLoc, projLoc, nLoc, sLoc; GLuint textureID; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; float globalAmbient[4 ] = { 0.7f , 0.7f , 0.7f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };struct Material { float *ambient, *diffuse, *specular; float shininess; }; Material gold{ Util::goldAmbient (), Util::goldDiffuse (), Util::goldSpecular (), Util::goldShininess () }; Material silver{ Util::silverAmbient (), Util::silverDiffuse (), Util::silverSpecular (), Util::silverShininess () }; int scSizeX, scSizeY;GLuint shadowTex, shadowBuffer; glm::mat4 lightVmatrix, lightPmatrix; glm::mat4 shadowMVP1, shadowMVP2; glm::mat4 b; Torus model1 (48 , 0.6f , 0.4f ) ;ImportedModel model2 ("model/pyr.obj" ) ;glm::vec3 cameraLoc (0.0f , 0.0f , 6.0f ) ;glm::vec3 lightLoc (-3.8f , 2.2f , 1.1f ) ;glm::vec3 model1Loc (1.6f , 0.0f , -0.3f ) ;glm::vec3 model2Loc (-1.0f , 0.1f , 0.3f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (GLuint renderProgram, glm::mat4 vMatrix, Material &m) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y; lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , m.ambient); glProgramUniform4fv (renderProgram, mDifLoc, 1 , m.diffuse); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , m.specular); glProgramUniform1f (renderProgram, mShiLoc, m.shininess); } void makeVertexArray () { std::vector<int > model1Ind = model1.getIndices (); std::vector<glm::vec3> vert = model1.getVertices (); std::vector<glm::vec3> norm = model1.getNormals (); std::vector<float > model1Pvalues; std::vector<float > model1Nvalues; for (int i = 0 ; i < model1.getNumVertices (); ++ i) { model1Pvalues.push_back (vert[i].x); model1Pvalues.push_back (vert[i].y); model1Pvalues.push_back (vert[i].z); model1Nvalues.push_back (norm[i].x); model1Nvalues.push_back (norm[i].y); model1Nvalues.push_back (norm[i].z); } vert = model2.getVertices (); norm = model2.getNormals (); std::vector<float > model2Pvalues; std::vector<float > model2Nvalues; for (int i = 0 ; i < model2.getNumVertices (); ++ i) { model2Pvalues.push_back (vert[i].x); model2Pvalues.push_back (vert[i].y); model2Pvalues.push_back (vert[i].z); model2Nvalues.push_back (norm[i].x); model2Nvalues.push_back (norm[i].y); model2Nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * model1Pvalues.size (), &model1Pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * model1Nvalues.size (), &model1Nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * model1Ind.size (), &model1Ind[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ARRAY_BUFFER, 4 * model2Pvalues.size (), &model2Pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[4 ]); glBufferData (GL_ARRAY_BUFFER, 4 * model2Nvalues.size (), &model2Nvalues[0 ], GL_STATIC_DRAW); } void setupShadowBuffers (GLFWwindow *window) { glfwGetFramebufferSize (window, &width, &height); scSizeX = width; scSizeY = height; glGenFramebuffers (1 , &shadowBuffer); glGenTextures (1 , &shadowTex); glBindTexture (GL_TEXTURE_2D, shadowTex); glTexImage2D (GL_TEXTURE_2D, 0 , GL_DEPTH_COMPONENT32, scSizeX, scSizeY, 0 , GL_DEPTH_COMPONENT, GL_FLOAT, 0 ); 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_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } void init (GLFWwindow *window) { renderProgram1 = Util::createShadeProgram ("shadows/vertex1.glsl" , "shadows/fragment1.glsl" ); renderProgram2 = Util::createShadeProgram ("shadows/vertex2.glsl" , "shadows/fragment2.glsl" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); setupShadowBuffers (window); b = glm::mat4 ( 0.5f , 0.0f , 0.0f , 0.0f , 0.0f , 0.5f , 0.0f , 0.0f , 0.0f , 0.0f , 0.5f , 0.0f , 0.5f , 0.5f , 0.5f , 1.0f ); } void passOne () { glUseProgram (renderProgram1); mMat = glm::translate (glm::mat4 (1.0f ), model1Loc); mMat = glm::rotate (mMat, 25.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); shadowMVP1 = lightPmatrix * lightVmatrix * mMat; sLoc = glGetUniformLocation (renderProgram1, "shadowMVP" ); glUniformMatrix4fv (sLoc, 1 , GL_FALSE, glm::value_ptr (shadowMVP1)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glClear (GL_DEPTH_BUFFER_BIT); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[2 ]); glDrawElements (GL_TRIANGLES, model1.getNumIndices (), GL_UNSIGNED_INT, 0 ); mMat = glm::translate (glm::mat4 (1.0f ), model2Loc); mMat = glm::rotate (mMat, 30.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 40.0f * 3.14159f / 180.0f , glm::vec3 (0.0f , 1.0f , 0.0f )); shadowMVP1 = lightPmatrix * lightVmatrix * mMat; glUniformMatrix4fv (sLoc, 1 , GL_FALSE, glm::value_ptr (shadowMVP1)); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , model2.getNumVertices ()); } void passTwo () { glUseProgram (renderProgram2); mvLoc = glGetUniformLocation (renderProgram2, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram2, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram2, "norm_matrix" ); sLoc = glGetUniformLocation (renderProgram2, "shadowMVP" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraLoc.x, -cameraLoc.y, -cameraLoc.z)); mMat = glm::translate (glm::mat4 (1.0f ), model1Loc); mMat = glm::rotate (mMat, 25.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); currentLightPos = glm::vec3 (lightLoc); installLights (renderProgram2, vMat, gold); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); shadowMVP2 = b * lightPmatrix * lightVmatrix * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glUniformMatrix4fv (sLoc, 1 , GL_FALSE, glm::value_ptr (shadowMVP2)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glClear (GL_DEPTH_BUFFER_BIT); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[2 ]); glDrawElements (GL_TRIANGLES, model1.getNumIndices (), GL_UNSIGNED_INT, 0 ); mMat = glm::translate (glm::mat4 (1.0f ), model2Loc); mMat = glm::rotate (mMat, 30.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 40.0f * 3.14159f / 180.0f , glm::vec3 (0.0f , 1.0f , 0.0f )); currentLightPos = glm::vec3 (lightLoc); installLights (renderProgram2, vMat, silver); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); shadowMVP2 = b * lightPmatrix * lightVmatrix * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glUniformMatrix4fv (sLoc, 1 , GL_FALSE, glm::value_ptr (shadowMVP2)); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[4 ]); glVertexAttribPointer (1 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , model2.getNumVertices ()); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); currentLightPos = glm::vec3 (lightLoc); lightVmatrix = glm::lookAt (currentLightPos, glm::vec3 (0.0f , 0.0f , 0.0f ), glm::vec3 (0.0f , 1.0f , 0.0f )); lightPmatrix = glm::perspective (60.0f * 3.14159f / 180.0f , aspect, 0.1f , 1000.0f ); glBindFramebuffer (GL_FRAMEBUFFER, shadowBuffer); glFramebufferTexture (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowTex, 0 ); glDrawBuffer (GL_NONE); glEnable (GL_DEPTH_TEST); glEnable (GL_POLYGON_OFFSET_FILL); glPolygonOffset (2.0f , 4.0f ); passOne (); glDisable (GL_POLYGON_OFFSET_FILL); glBindFramebuffer (GL_FRAMEBUFFER, 0 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, shadowTex); glDrawBuffer (GL_FRONT); passTwo (); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Light" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
第一轮的着色器
1 2 3 4 5 6 7 8 9 10 11 #version 460 core layout (location = 0 ) in vec3 vertPos;uniform mat4 shadowMVP;void main(){ gl_Position = shadowMVP * vec4 (vertPos, 1.0 ); }
1 2 3 4 #version 460 core void main(){}
第二轮的着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #version 460 struct PositionalLight { vec4 ambient, diffuse, specular; vec3 position; }; struct Material { vec4 ambient, diffuse, specular; float shininess; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec3 vertNormal;out vec3 vNormal, vLightDir, vVertPos, vHalfVec;out vec4 shadowCoord;uniform vec4 globalAmbient;uniform PositionalLight light;uniform Material material;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix;uniform mat4 shadowMVP;void main(){ vVertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; vLightDir = light.position - vVertPos; vNormal = (norm_matrix * vec4 (vertNormal, 0.0 )).xyz; vHalfVec = (vLightDir - vVertPos).xyz; shadowCoord = shadowMVP * vec4 (vertPos, 1.0 ); gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #version 460 core struct PositionalLight { vec4 ambient, diffuse, specular; vec3 position; }; struct Material { vec4 ambient, diffuse, specular; float shininess; }; in vec3 vNormal, vLightDir, vVertPos, vHalfVec;in vec4 shadowCoord;out vec4 fragColor;uniform vec4 globalAmbient;uniform PositionalLight light;uniform Material material;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix;uniform mat4 shadowMVP;layout (binding = 0 ) uniform sampler2DShadow shadowTex;void main(){ vec3 L = normalize (vLightDir); vec3 N = normalize (vNormal); vec3 V = normalize (-vVertPos); vec3 H = normalize (vHalfVec); float inShadow = textureProj (shadowTex, shadowCoord); fragColor = globalAmbient * material.ambient + light.ambient * material.ambient; if (inShadow != 0.0 ) { fragColor += light.diffuse * material.diffuse * max (dot (L, N), 0.0 ) + light.specular * material.specular * pow (max (dot (H, N), 0.0 ), material.shininess * 3 ); } }
8.4 柔和阴影
使用百分比临近滤波 PCF 生成柔和阴影。
一种用于实现 PCF 的常见算法是:
对每个像素附近的 4 个纹元(纹理图像像素)进行采样,其中样本通过像素对应纹元的特定偏移量选择。
对于每个像素,都需要改变偏移量,并用新的偏移量确定采样的 4 个纹元。
使用交错的方式改变偏移量的方法被称为抖动,它旨在使柔和阴影的边界不会由于采样点不足而看起来“结块”。
假设有4种偏移模式,每次取一种计算像素 glFragCorrd mod 2
值选择当前偏移,结果可能为:(0,0)、(1,0)、(0,1)、(1,1)。
偏移模式通常在 x 和 y 方向上指定,具有-1.5、-0.5、+0.5和+1.5不同组合:
4种计算结果对应的偏移模式采样点如下表
计算结果
(0,0)
(0,1)
(1,0)
(1,1)
( s x − 1.5 , s y + 1.5 ) (s_x-1.5,s_y+1.5) ( s x − 1 . 5 , s y + 1 . 5 )
( s x − 1.5 , s y + 0.5 ) (s_x-1.5,s_y+0.5) ( s x − 1 . 5 , s y + 0 . 5 )
( s x − 0.5 , s y + 1.5 ) (s_x-0.5,s_y+1.5) ( s x − 0 . 5 , s y + 1 . 5 )
( s x − 0.5 , s y + 0.5 ) (s_x-0.5,s_y+0.5) ( s x − 0 . 5 , s y + 0 . 5 )
( s x − 1.5 , s y − 0.5 ) (s_x-1.5,s_y-0.5) ( s x − 1 . 5 , s y − 0 . 5 )
( s x − 1.5 , s y − 1.5 ) (s_x-1.5,s_y-1.5) ( s x − 1 . 5 , s y − 1 . 5 )
( s x − 0.5 , s y − 0.5 ) (s_x-0.5,s_y-0.5) ( s x − 0 . 5 , s y − 0 . 5 )
( s x − 0.5 , s y − 1.5 ) (s_x-0.5,s_y-1.5) ( s x − 0 . 5 , s y − 1 . 5 )
( s x + 0.5 , s y + 1.5 ) (s_x+0.5,s_y+1.5) ( s x + 0 . 5 , s y + 1 . 5 )
( s x + 0.5 , s y + 0.5 ) (s_x+0.5,s_y+0.5) ( s x + 0 . 5 , s y + 0 . 5 )
( s x + 1.5 , s y + 1.5 ) (s_x+1.5,s_y+1.5) ( s x + 1 . 5 , s y + 1 . 5 )
( s x + 1.5 , s y + 0.5 ) (s_x+1.5,s_y+0.5) ( s x + 1 . 5 , s y + 0 . 5 )
( s x + 0.5 , s y − 0.5 ) (s_x+0.5,s_y-0.5) ( s x + 0 . 5 , s y − 0 . 5 )
( s x + 0.5 , s y − 1.5 ) (s_x+0.5,s_y-1.5) ( s x + 0 . 5 , s y − 1 . 5 )
( s x + 1.5 , s y − 0.5 ) (s_x+1.5,s_y-0.5) ( s x + 1 . 5 , s y − 0 . 5 )
( s x + 1.5 , s y − 1.5 ) (s_x+1.5,s_y-1.5) ( s x + 1 . 5 , s y − 1 . 5 )
假设随便一组,对其4个采样点分别调用 textureProj()
,将得到的4个结果相加求平均,确定阴影中采样点的百分比,以此百分比作系数,确定渲染当前像素时要应用的漫反射和镜面反射分量。
采样点可以有多个,但渲染速度也会随之下降。
64个采样点柔和阴影效果如下:
仅需修改第二轮的片段着色器:
fragment2.glsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #version 460 core struct PositionalLight { vec4 ambient, diffuse, specular; vec3 position; }; struct Material { vec4 ambient, diffuse, specular; float shininess; }; in vec3 vNormal, vLightDir, vVertPos, vHalfVec;in vec4 shadowCoord;out vec4 fragColor;uniform vec4 globalAmbient;uniform PositionalLight light;uniform Material material;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix;uniform mat4 shadowMVP;layout (binding = 0 ) uniform sampler2DShadow shadowTex;float lookup(float ox, float oy){ float t = textureProj (shadowTex, shadowCoord + vec4 (ox * 0.001 * shadowCoord.w, oy * 0.001 * shadowCoord.w, -0.01 , 0.0 )); return t; } void main(){ float shadowFactor = 0.0 ; vec3 L = normalize (vLightDir); vec3 N = normalize (vNormal); vec3 V = normalize (-vVertPos); vec3 H = normalize (vHalfVec); float swidth = 2.5 ; float endp = swidth * 3.0 + swidth / 2.0 ; for (float m = -endp; m <= endp; m += swidth) for (float n = -endp; n <= endp; n += swidth) shadowFactor += lookup(m, n); shadowFactor /= 64.0 ; vec4 shadowColor = globalAmbient * material.ambient + light.ambient * material.ambient; vec4 lightedColor = light.diffuse * material.diffuse * max (dot (L, N), 0.0 ) + light.specular * material.specular * pow (max (dot (H, N), 0.0 ), material.shininess * 3.0 ); fragColor = vec4 ((shadowColor.xyz + shadowFactor * (lightedColor.xyz)), 1.0 ); }
有些时候,在场景中的某些对象拥有纹理时,添加阴影时必须确保片段着色器正确区分阴影纹理和其他纹理。一种简单的方法是将它们绑定到不同的纹理单元:
1 2 layout (binding = 0 ) uniform sampler2DShadow shadowTex;layout (binding = 1 ) uniform sampler2D tex;
然后通过绑定值引用两个采样器。
当场景使用多个灯光时,则需要多个阴影纹理,即每个光源需要一个阴影纹理。
每个光源都需要单独执行第 1 轮渲染,并在第 2 轮渲染中合并结果。
九、天空和背景
9.1 天空盒
天空盒:
实例化一个立方体对象
将立方体的纹理设置为所需的环境
将立方体围绕相机设置
如何为地平线制作纹理:
使用一个包含6个面的纹理图像。
可使用 Terragen、Blender、PS等软件工具辅助构建贴图图像。
如何让天空盒看起来距离很远:
确保纹理表现看起来像远处的地平线。
禁用深度测试并先渲染天空盒,然后启用深度测试再渲染其他对象。
使天空盒随着相机移动。
9.2 穹顶
与天空盒类似,不过使用带纹理的球体或半球体代替带纹理的立方体。
一样需要先禁用深度测试渲染穹顶,再将相机置于穹顶的中心位置。
穹顶相比天空盒:
优点:不易受到畸变和接缝的影响(尽管在纹理图像
中必须考虑极点处的球形畸变)。
缺点:球体或穹顶模型比立方体模型更复杂,穹顶有更多的顶点,其数量取决于期望的精度。
9.3 实现天空盒
9.3.1 从头开始构建天空盒
效果如下图:
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" const int numVAO = 1 , numVBO = 5 ;GLuint vao[numVAO], vbo[numVBO]; GLuint renderProgram; int width, height;float aspect;GLuint mvLoc, projLoc; GLuint textureID, skyboxTex; glm::mat4 pMat, vMat, mMat, mvMat; Torus model (48 , 0.5f , 0.2f ) ;glm::vec3 cameraLoc (0.0f , 0.0f , 5.0f ) ;glm::vec3 modelLoc (0.0f , -0.75 , 0.0f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float cubeVertexPositions[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 }; float cubeTextureCoord[72 ] = { 1.00f , 0.666666f , 1.00f , 0.333333f , 0.75f , 0.333333f , 0.75f , 0.333333f , 0.75f , 0.666666f , 1.00f , 0.666666f , 0.75f , 0.333333f , 0.50f , 0.333333f , 0.75f , 0.666666f , 0.50f , 0.333333f , 0.50f , 0.666666f , 0.75f , 0.666666f , 0.50f , 0.333333f , 0.25f , 0.333333f , 0.50f , 0.666666f , 0.25f , 0.333333f , 0.25f , 0.666666f , 0.50f , 0.666666f , 0.25f , 0.333333f , 0.00f , 0.333333f , 0.25f , 0.666666f , 0.00f , 0.333333f , 0.00f , 0.666666f , 0.25f , 0.666666f , 0.25f , 0.333333f , 0.50f , 0.333333f , 0.50f , 0.000000f , 0.50f , 0.000000f , 0.25f , 0.000000f , 0.25f , 0.333333f , 0.25f , 1.000000f , 0.50f , 1.000000f , 0.50f , 0.666666f , 0.50f , 0.666666f , 0.25f , 0.666666f , 0.25f , 1.000000f , }; std::vector<int > modelInd = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > modelPvalues; std::vector<float > modelTvalues; std::vector<float > modelNvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { modelPvalues.push_back (vert[i].x); modelPvalues.push_back (vert[i].y); modelPvalues.push_back (vert[i].z); modelTvalues.push_back (tex[i].s); modelTvalues.push_back (tex[i].t); modelNvalues.push_back (norm[i].x); modelNvalues.push_back (norm[i].y); modelNvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (cubeVertexPositions), cubeVertexPositions, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (cubeTextureCoord), cubeTextureCoord, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelPvalues.size (), &modelPvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelTvalues.size (), &modelTvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[4 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * modelInd.size (), &modelInd[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("textureImg/brick1.jpg" ); skyboxTex = Util::loadTexture ("textureImg/alien.jpg" ); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraLoc.x, -cameraLoc.y, -cameraLoc.z)); glUseProgram (renderProgram); mMat = glm::translate (glm::mat4 (1.0f ), cameraLoc); mvMat = vMat * mMat; mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, skyboxTex); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDisable (GL_DEPTH_TEST); glDrawArrays (GL_TRIANGLES, 0 , 36 ); glEnable (GL_DEPTH_TEST); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); mMat = glm::translate (glm::mat4 (1.0f ), modelLoc); mMat = glm::rotate (mMat, glm::radians (15.0f ), glm::vec3 (1.0f , 0.0f , 0.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glClear (GL_DEPTH_BUFFER_BIT); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[4 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Skybox" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #version 460 core layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec2 texCoord;out vec2 tc;uniform mat4 mv_matrix;uniform mat4 proj_matrix;void main(){ tc = texCoord; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core in vec2 tc;out vec4 fragColor;layout (binding = 0 ) uniform sampler2D s;void main(){ fragColor = texture (s, tc); }
9.3.2 使用OpenGL立方体贴图
OpenGL 纹理立方体贴图使用带有 3 个变量的纹理坐标 ( s , t , r ) (s, t, r) ( s , t , r ) 访问。
立方体贴图的另一个特性是,其中的图像以纹理图像的左上角作为纹理坐标 ( 0 , 0 , 0 ) (0, 0, 0) ( 0 , 0 , 0 ) 。
另外定义函数 loadCubeMap()
函数读入6个单独的立方体面图像文件,以这种形式再调用 SOIL_load_OGL_cubemap()
进行构建纹理。该函数置于 Util
类中。
效果截图:
代码如下:
新增函数
1 2 3 4 5 6 7 8 9 10 11 12 13 GLuint Util::loadCubeMap (const std::string &mapDirPath) { GLuint textureRef; std::string xp = mapDirPath + "/xp.jpg" ; std::string xn = mapDirPath + "/xn.jpg" ; std::string yp = mapDirPath + "/yp.jpg" ; std::string yn = mapDirPath + "/yn.jpg" ; std::string zp = mapDirPath + "/zp.jpg" ; std::string zn = mapDirPath + "/zn.jpg" ; textureRef = SOIL_load_OGL_cubemap (xp.c_str (), xn.c_str (), yp.c_str (), yn.c_str (), zp.c_str (), zn.c_str (), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS); if (textureRef == 0 ) std::cout << "Failed to load texture: " << mapDirPath << "\n" ; return textureRef; }
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; GLuint renderProgram, renderProgramCubeMap; int width, height;float aspect;GLuint mvLoc, projLoc, vLoc; GLuint textureID, skyboxTex; glm::mat4 pMat, vMat, mMat, mvMat; Torus model (48 , 0.8f , 0.4f ) ;glm::vec3 cameraLoc (0.0f , 0.0f , 5.0f ) ;glm::vec3 modelLoc (0.0f , 0.0f , 0.0f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float cubeVertexPositions[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 }; std::vector<int > modelInd = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > modelPvalues; std::vector<float > modelTvalues; std::vector<float > modelNvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { modelPvalues.push_back (vert[i].x); modelPvalues.push_back (vert[i].y); modelPvalues.push_back (vert[i].z); modelTvalues.push_back (tex[i].s); modelTvalues.push_back (tex[i].t); modelNvalues.push_back (norm[i].x); modelNvalues.push_back (norm[i].y); modelNvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (cubeVertexPositions), cubeVertexPositions, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelPvalues.size (), &modelPvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelTvalues.size (), &modelTvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * modelInd.size (), &modelInd[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); renderProgramCubeMap = Util::createShadeProgram ("vertexCubeMap.glsl" , "fragmentCubeMap.glsl" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); textureID = Util::loadTexture ("textureImg/brick1.jpg" ); skyboxTex = Util::loadCubeMap ("textureImg/cubeMap" ); glEnable (GL_TEXTURE_CUBE_MAP_SEAMLESS); glBindTexture (GL_TEXTURE_2D, textureID); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraLoc.x, -cameraLoc.y, -cameraLoc.z)); glUseProgram (renderProgramCubeMap); vLoc = glGetUniformLocation (renderProgramCubeMap, "v_matrix" ); glUniformMatrix4fv (vLoc, 1 , GL_FALSE, glm::value_ptr (vMat)); projLoc = glGetUniformLocation (renderProgramCubeMap, "p_matrix" ); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_CUBE_MAP, skyboxTex); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDisable (GL_DEPTH_TEST); glDrawArrays (GL_TRIANGLES, 0 , 36 ); glEnable (GL_DEPTH_TEST); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); mMat = glm::translate (glm::mat4 (1.0f ), modelLoc); mMat = glm::rotate (mMat, glm::radians (35.0f ), glm::vec3 (1.0f , 0.0f , 0.0f )); mvMat = vMat * mMat; glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, textureID); glClear (GL_DEPTH_BUFFER_BIT); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , 36 ); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Skybox" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
立方体贴图着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #version 460 core layout (location = 0 ) in vec3 vertPos;out vec3 tc;uniform mat4 v_matrix;uniform mat4 p_matrix;void main(){ tc = vertPos; mat4 v3_matrix = mat4 (mat3 (v_matrix)); gl_Position = p_matrix * v3_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core in vec3 tc;out vec4 fragColor;layout (binding = 0 ) uniform samplerCube samp;void main(){ fragColor = texture (samp, tc); }
描绘场景的着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #version 460 core layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec2 texCoord;out vec2 tc;uniform mat4 mv_matrix;uniform mat4 proj_matrix;void main(){ tc = texCoord; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core in vec2 tc;out vec4 fragColor;layout (binding = 0 ) uniform sampler2D s;void main(){ fragColor = texture (s, tc); }
9.4 环境贴图
环境贴图:使用立方体贴图来构造反射对象本身。
在光照时计算过反射向量,现在使用反射向量从纹理贴图中查找值。
仍然需要两组着色器,一组用于立方体贴图,一组用于场景物体(环面)。
程序上:
makeVertexArray()
函数中:
display()
函数中:
创建变换法向量的矩阵并关联到统一变量
激活环面法向量缓冲区
激活立方体贴图为环面的纹理
顶点着色器中:
片段着色器中:
计算反射向量
从立方体贴图使用反射向量检索输出颜色
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; GLuint renderProgram, renderProgramCubeMap; int width, height;float aspect;GLuint mvLoc, projLoc, vLoc, nLoc; GLuint skyboxTex; float rotAmt;glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; Torus model (48 , 0.8f , 0.4f ) ;glm::vec3 cameraLoc (0.0f , 0.0f , 5.0f ) ;glm::vec3 modelLoc (0.0f , 0.0f , 0.0f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void makeVertexArray () { float cubeVertexPositions[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 }; std::vector<int > modelInd = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > modelPvalues; std::vector<float > modelTvalues; std::vector<float > modelNvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { modelPvalues.push_back (vert[i].x); modelPvalues.push_back (vert[i].y); modelPvalues.push_back (vert[i].z); modelTvalues.push_back (tex[i].s); modelTvalues.push_back (tex[i].t); modelNvalues.push_back (norm[i].x); modelNvalues.push_back (norm[i].y); modelNvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, sizeof (cubeVertexPositions), cubeVertexPositions, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelPvalues.size (), &modelPvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * modelTvalues.size (), &modelTvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * modelInd.size (), &modelInd[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("vertex.glsl" , "fragment.glsl" ); renderProgramCubeMap = Util::createShadeProgram ("vertexCubeMap.glsl" , "fragmentCubeMap.glsl" ); glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); skyboxTex = Util::loadCubeMap ("textureImg/lakeCubeMap" ); glEnable (GL_TEXTURE_CUBE_MAP_SEAMLESS); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraLoc.x, -cameraLoc.y, -cameraLoc.z)); glUseProgram (renderProgramCubeMap); vLoc = glGetUniformLocation (renderProgramCubeMap, "v_matrix" ); glUniformMatrix4fv (vLoc, 1 , GL_FALSE, glm::value_ptr (vMat)); projLoc = glGetUniformLocation (renderProgramCubeMap, "p_matrix" ); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_CUBE_MAP, skyboxTex); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDisable (GL_DEPTH_TEST); glDrawArrays (GL_TRIANGLES, 0 , 36 ); glEnable (GL_DEPTH_TEST); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); rotAmt = currentTime * 0.5f ; mMat = glm::translate (glm::mat4 (1.0f ), modelLoc); mMat = glm::rotate (mMat, rotAmt, glm::vec3 (1.0f , 0.0f , 0.0f )); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_CUBE_MAP, skyboxTex); glClear (GL_DEPTH_BUFFER_BIT); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Skybox" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
描绘场景的着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #version 460 core layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec3 normal;out vec3 vNormal;out vec3 vVertPos;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix;void main(){ vVertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; vNormal = (norm_matrix * vec4 (normal, 0.0 )).xyz; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #version 460 core in vec3 vVertPos;in vec3 vNormal;out vec4 fragColor;layout (binding = 0 ) uniform samplerCube t;void main(){ vec3 r = -reflect (normalize (-vVertPos), normalize (vNormal)); fragColor = texture (t, r); }
环境贴图的主要限制之一是它只能构建反射立方体贴图内容的对象。
在场景中渲染的其他对象并不会出现在使用贴图模拟反射的对象中。
可以使用模板缓冲区或其他教程实现反射出多个物体的效果。
使用 SOIL2 可以很方便加载纹理贴图,但也有其他方法:
使用 stb_image.h
:
包含头文件
使用 glGenTextures()
为立方体贴图创建纹理及其 ID
使用 glBindTexture()
指定纹理 ID 和 GL_TEXTURE_CUBE_MAP
使用 stbi_load()
加载纹理文件
使用 glTexImage2D()
将图像分配给立方体的各个面
SOIL2 以及包含文件 stb_image.h
也许Terragen对于制作贴图不错。
十、增强表面细节
10.1 凹凸贴图
如果想让一个物体看起来好像有凹凸,一种方法是计算表面确实凹凸不平时其上的法向量,然后模拟法向量。
使用正弦函数生成凹凸不平的法向量。
效果如图:
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 4 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc, nLoc; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; glm::vec3 initialLightLoc = glm::vec3 (5.0f , 2.0f , 2.0f ); float globalAmbient[4 ] = { 0.7f , 0.7f , 0.7f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float *matAmb = Util::silverAmbient ();float *matDif = Util::silverDiffuse ();float *matSpe = Util::silverSpecular ();float matShi = Util::silverShininess ();Torus model (48 , 0.5f , 0.2f ) ;static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (glm::mat4 vMatrix) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y; lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , matAmb); glProgramUniform4fv (renderProgram, mDifLoc, 1 , matDif); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , matSpe); glProgramUniform1f (renderProgram, mShiLoc, matShi); } void makeVertexArray () { std::vector<int > ind = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * ind.size (), &ind[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("bumpMap/vertex.glsl" , "bumpMap/fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 4.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 35.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); currentLightPos = glm::vec3 (initialLightLoc.x, initialLightLoc.y, initialLightLoc.z); installLights (vMat); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (1 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[3 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Bumps" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec3 vertNormal;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix;uniform PositionalLight light;out vec3 varyingNormal;out vec3 varyingLightDir;out vec3 varyingVertPos;out vec3 originalVertex;void main(){ varyingVertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; varyingLightDir = light.position - varyingVertPos; varyingNormal = (norm_matrix * vec4 (vertNormal, 0.0 )).xyz; originalVertex = vertPos; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; struct Material { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }; uniform vec4 globalAmbient;uniform PositionalLight light;uniform Material material;in vec3 varyingNormal;in vec3 varyingLightDir;in vec3 varyingVertPos;in vec3 originalVertex;out vec4 fragColor;void main(){ vec3 L = normalize (varyingLightDir); vec3 N = normalize (varyingNormal); vec3 V = normalize (-varyingVertPos); float a = 0.25 ; float b = 100.0 ; float x = originalVertex.x; float y = originalVertex.y; float z = originalVertex.z; N.x = varyingNormal.x + a * sin (b * x); N.y = varyingNormal.y + a * sin (b * y); N.z = varyingNormal.z + a * sin (b * z); N = normalize (N); vec3 R = normalize (reflect (-L, N)); float cosTheta = dot (L, N); float cosPhi = dot (V, R); fragColor = globalAmbient * material.ambient + light.ambient * material.diffuse + light.diffuse * material.diffuse * max (cosTheta, 0.0 ) + light.specular * material.specular * pow (max (cosPhi, 0.0 ), material.shininess); }
10.2 法线贴图
凹凸贴图可以使用查找表来替换法向量的形式替代。
以查找表的方法实现凹凸细节叫作法线贴图。
可以将法向量存储在彩色图像文件中,恰好 RGB 三通道对应向量的 xyz。
RGB 值以字节形式存储,通常为 [ 0 , 1 ] [0,1] [ 0 , 1 ] 。
向量限制在 [ − 1 , + 1 ] [-1,+1] [ − 1 , + 1 ] 。
{ R = ( N x + 1 ) / 2 G = ( N y + 1 ) / 2 B = ( N z + 1 ) / 2 \begin{cases}
R=(N_x+1)/2 \\
G=(N_y+1)/2 \\
B=(N_z+1)/2
\end{cases}
⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ R = ( N x + 1 ) / 2 G = ( N y + 1 ) / 2 B = ( N z + 1 ) / 2
法线贴图使用一个图像(称为法线图)文件,该图像文件包含在光照下所期望表面外观的法向量。
法向量的 x 和 y 分量表示其被扰动后与“垂直”方向的偏差,z 分量设置为 1。
严格垂直的向量(即没有偏差)将表示为 ( 0 , 0 , 1 ) (0, 0, 1) ( 0 , 0 , 1 ) 。
但发现图查找到的法向量不能直接使用,因为并没有考虑法向量在物体上的位置以及在相机空间种的方向。
在对象顶点出,考虑对象的切平面,取切平面两个相互垂直的向量(切向量和副切向量),可以通过计算切向量和法向量的叉积构建副切向量。
如果模型没有定义切向量,还需计算切向量。
对于那些表面无法求导、无法精确求解切向量的模型,其切向量可以通过近似得到。
切向量一样通过 VBO 传递到顶点着色器中。
一旦在相机空间中得到法向量、切向量和副切向量,就可以构造矩阵(称为 TBN 矩阵)。
该矩阵用于将从法线贴图中检索到的法向量转换为在相机空间中相对于物体表面的法向量。
在片段着色器中,新法向量的计算在 calcNewNormal()
函数中完成。
该函数包含 dot(tangent, normal)
的计算确保切向量垂直于法向量。新的切向量和法向量的叉积就是副切向量。
制作法线图可以通过 GIMP 和 PS 等工具完成。
法线贴图例子
法线图
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 5 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc, nLoc; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; glm::vec3 initialLightLoc = glm::vec3 (-5.0f , 2.0f , 5.0f ); float globalAmbient[4 ] = { 0.7f , 0.7f , 0.7f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float *matAmb = Util::goldAmbient ();float *matDif = Util::goldDiffuse ();float *matSpe = Util::goldSpecular ();float matShi = Util::goldShininess ();Sphere model (48 ) ;GLuint texture; static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (glm::mat4 vMatrix) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y; lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , matAmb); glProgramUniform4fv (renderProgram, mDifLoc, 1 , matDif); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , matSpe); glProgramUniform1f (renderProgram, mShiLoc, matShi); } void makeVertexArray () { std::vector<int > ind = model.getIndices (); std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<glm::vec3> tang = model.getTangents (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; std::vector<float > tanvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); tanvalues.push_back (tang[i].x); tanvalues.push_back (tang[i].y); tanvalues.push_back (tang[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tanvalues.size (), &tanvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[4 ]); glBufferData (GL_ELEMENT_ARRAY_BUFFER, 4 * ind.size (), &ind[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("normalMap/vertex.glsl" , "normalMap/fragment.glsl" ); cameraX = 0.0f , cameraY = 0.0f , cameraZ = 4.0f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); texture = Util::loadTexture ("normalMap/castleroofNORMAL.jpg" ); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , -1.0f )); mMat = glm::rotate (mMat, 35.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); currentLightPos = glm::vec3 (initialLightLoc.x, initialLightLoc.y, initialLightLoc.z); installLights (vMat); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (2 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (2 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[3 ]); glVertexAttribPointer (3 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (3 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, texture); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[4 ]); glDrawElements (GL_TRIANGLES, model.getNumIndices (), GL_UNSIGNED_INT, 0 ); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Bumps" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
球体类新增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void Sphere::init (int _prec) { numVertices = (_prec + 1 ) * (_prec + 1 ); numIndices = _prec * _prec * 6 ; for (int i = 0 ; i < numVertices; ++ i) { vertices.push_back (glm::vec3 ()); texCoords.push_back (glm::vec2 ()); normals.push_back (glm::vec3 ()); tangents.push_back (glm::vec3 ()); } for (int i = 0 ; i < numIndices; ++ i) indices.push_back (0 ); for (int i = 0 ; i <= _prec; ++ i) for (int j = 0 ; j <= _prec; ++ j) { float y = static_cast <float >(cos (toRadians (180.0f - i * 180.0f / _prec))); float x = - static_cast <float >(cos (toRadians (j * 360.0f / _prec)) * fabs (cos (asin (y)))); float z = static_cast <float >(sin (toRadians (j * 360.0f / _prec)) * fabs (cos (asin (y)))); vertices[i * (_prec + 1 ) + j] = glm::vec3 (x, y, z); if (((x == 0 ) and (y == 1 ) and (z == 0 )) or ((x == 0 ) and (y == -1 ) and (z == 0 ))) tangents[i * (_prec + 1 ) + j] = glm::vec3 (0.0f , 0.0f , -1.0f ); else tangents[i * (_prec + 1 ) + j] = glm::cross (glm::vec3 (0.0f , 1.0f , 0.0f ), glm::vec3 (x, y, z)); texCoords[i * (_prec + 1 ) + j] = glm::vec2 (j * 1.0f / _prec, i * 1.0f / _prec); normals[i * (_prec + 1 ) + j] = glm::vec3 (x, y, z); } } std::vector<glm::vec3> Sphere::getTangents () { return tangents; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec2 texCoord;layout (location = 2 ) in vec3 vertNormal;layout (location = 3 ) in vec3 vertTangent;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform mat4 norm_matrix; uniform PositionalLight light;out vec3 varyingLightDir;out vec3 varyingVertPos;out vec3 varyingNormal;out vec3 varyingTangent;out vec3 originalVertex;out vec2 tc;out vec3 varyingHalfVector;void main(){ varyingVertPos = (mv_matrix * vec4 (vertPos, 1.0 )).xyz; varyingLightDir = light.position - varyingVertPos; tc = texCoord; originalVertex = vertPos; varyingNormal = (norm_matrix * vec4 (vertNormal, 0.0 )).xyz; varyingTangent = (norm_matrix * vec4 (vertTangent, 0.0 )).xyz; varyingHalfVector = normalize (normalize (varyingLightDir) + normalize (-varyingVertPos)).xyz; gl_Position = proj_matrix * mv_matrix * vec4 (vertPos, 1.0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #version 460 core struct PositionalLight { vec4 ambient; vec4 diffuse; vec4 specular; vec3 position; }; struct Material { vec4 ambient; vec4 diffuse; vec4 specular; float shininess; }; layout (binding = 0 ) uniform sampler2D nromsamp;uniform vec4 globalAmbient;uniform PositionalLight light;uniform Material material;in vec3 varyingLightDir;in vec3 varyingNormal;in vec3 varyingVertPos;in vec3 varyingTangent;in vec3 originalVertPos;in vec2 tc;in vec3 varyingHalfVector;out vec4 fragColor;vec3 calcNewNormal(){ vec3 normal = normalize (varyingNormal); vec3 tangent = normalize (varyingTangent); tangent = normalize (tangent - dot (tangent, normal) * normal); vec3 bitangent = cross (normal, tangent); mat3 tbn = mat3 (tangent, bitangent, normal); vec3 retrievedNormal = texture (nromsamp, tc).xyz; vec3 newNormal = tbn * retrievedNormal; newNormal = normalize (newNormal); return newNormal; } void main(){ vec3 L = normalize (varyingLightDir); vec3 N = calcNewNormal(); vec3 R = normalize (reflect (-L, N)); vec3 H = normalize (varyingHalfVector); float cosTheta = dot (L, N); float cosPhi = dot (H, N); fragColor = globalAmbient * material.ambient + light.ambient * material.ambient + light.diffuse * material.diffuse * max (0.0 , cosTheta) + light.specular * material.specular * pow (max (0.0 , cosPhi), material.shininess); }
可以使用纹理加法线贴图,使得模型更逼真。
法线贴图也可以使用多级渐远纹理贴图改善效果。
法线贴图的各向异性过滤更有效,它不但减少了闪烁的伪影,同时还保留了细节。
10.3 高度贴图
使用纹理图像来存储高度值,然后使用该高度值来提升(或降低)顶点位置。
使用高度图更改对象的顶点的方法称为高度贴图。
效果展示:
用于展示的高度图和纹理图如下:
高度图
纹理图
代码如下:
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 #include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include "Util.h" #include <stack> #include "Sphere.h" #include "Torus.h" #include "ImportedModel.h" GLuint renderProgram; const int numVAO = 1 , numVBO = 3 ;GLuint vao[numVAO], vbo[numVBO]; int width, height;float aspect;double cameraX, cameraY, cameraZ;GLuint mvLoc, projLoc, nLoc; GLuint globalAmbLoc, ambLoc, diffLoc, specLoc, posLoc; GLuint mAmbLoc, mDifLoc, mSpecLoc, mShiLoc; glm::mat4 pMat, vMat, mMat, mvMat, invTrMat; glm::vec3 currentLightPos, lightPosV; float lightPos[3 ]; glm::vec3 initialLightLoc = glm::vec3 (-5.0f , 2.0f , 5.0f ); float globalAmbient[4 ] = { 0.7f , 0.7f , 0.7f , 1.0f };float lightAmbient[4 ] = { 0.0f , 0.0f , 0.0f , 1.0f };float lightDiffuse[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float lightSpecular[4 ] = { 1.0f , 1.0f , 1.0f , 1.0f };float *matAmb = Util::goldAmbient ();float *matDif = Util::goldDiffuse ();float *matSpe = Util::goldSpecular ();float matShi = Util::goldShininess ();ImportedModel model ("heightMap/grid.obj" ) ;GLuint heightMap; GLuint texture; static void windowSizeCallback (GLFWwindow *window, int width, int height) { aspect = static_cast <float >(width) / static_cast <float >(height); glViewport (0 , 0 , width, height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); } void installLights (glm::mat4 vMatrix) { lightPosV = glm::vec3 (vMatrix * glm::vec4 (currentLightPos, 1.0 )); lightPos[0 ] = lightPosV.x; lightPos[1 ] = lightPosV.y; lightPos[2 ] = lightPosV.z; globalAmbLoc = glGetUniformLocation (renderProgram, "globalAmbient" ); ambLoc = glGetUniformLocation (renderProgram, "light.ambient" ); diffLoc = glGetUniformLocation (renderProgram, "light.diffuse" ); specLoc = glGetUniformLocation (renderProgram, "light.specular" ); posLoc = glGetUniformLocation (renderProgram, "light.position" ); mAmbLoc = glGetUniformLocation (renderProgram, "material.ambient" ); mDifLoc = glGetUniformLocation (renderProgram, "material.diffuse" ); mSpecLoc = glGetUniformLocation (renderProgram, "material.specular" ); mShiLoc = glGetUniformLocation (renderProgram, "material.shininess" ); glProgramUniform4fv (renderProgram, globalAmbLoc, 1 , globalAmbient); glProgramUniform4fv (renderProgram, ambLoc, 1 , lightAmbient); glProgramUniform4fv (renderProgram, diffLoc, 1 , lightDiffuse); glProgramUniform4fv (renderProgram, specLoc, 1 , lightSpecular); glProgramUniform3fv (renderProgram, posLoc, 1 , lightPos); glProgramUniform4fv (renderProgram, mAmbLoc, 1 , matAmb); glProgramUniform4fv (renderProgram, mDifLoc, 1 , matDif); glProgramUniform4fv (renderProgram, mSpecLoc, 1 , matSpe); glProgramUniform1f (renderProgram, mShiLoc, matShi); } void makeVertexArray () { std::vector<glm::vec3> vert = model.getVertices (); std::vector<glm::vec2> tex = model.getTexCoords (); std::vector<glm::vec3> norm = model.getNormals (); std::vector<float > pvalues; std::vector<float > tvalues; std::vector<float > nvalues; for (int i = 0 ; i < model.getNumVertices (); ++ i) { pvalues.push_back (vert[i].x); pvalues.push_back (vert[i].y); pvalues.push_back (vert[i].z); tvalues.push_back (tex[i].s); tvalues.push_back (tex[i].t); nvalues.push_back (norm[i].x); nvalues.push_back (norm[i].y); nvalues.push_back (norm[i].z); } glGenVertexArrays (numVAO, vao); glBindVertexArray (vao[0 ]); glGenBuffers (numVBO, vbo); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glBufferData (GL_ARRAY_BUFFER, 4 * pvalues.size (), &pvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glBufferData (GL_ARRAY_BUFFER, 4 * tvalues.size (), &tvalues[0 ], GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glBufferData (GL_ARRAY_BUFFER, 4 * nvalues.size (), &nvalues[0 ], GL_STATIC_DRAW); } void init (GLFWwindow *window) { renderProgram = Util::createShadeProgram ("heightMap/vertex.glsl" , "heightMap/fragment.glsl" ); cameraX = 0.03f ; cameraY = 0.03f ; cameraZ = 0.8f ; glfwGetFramebufferSize (window, &width, &height); aspect = static_cast <float >(width) / static_cast <float >(height); pMat = glm::perspective (1.0472f , aspect, 0.1f , 1000.0f ); makeVertexArray (); texture = Util::loadTexture ("textureImg/ice.jpg" ); heightMap = Util::loadTexture ("heightMap/height.jpg" ); } void display (GLFWwindow *window, double currentTime) { glClear (GL_DEPTH_BUFFER_BIT); glClear (GL_COLOR_BUFFER_BIT); glUseProgram (renderProgram); mvLoc = glGetUniformLocation (renderProgram, "mv_matrix" ); projLoc = glGetUniformLocation (renderProgram, "proj_matrix" ); nLoc = glGetUniformLocation (renderProgram, "norm_matrix" ); vMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (-cameraX, -cameraY, -cameraZ)); mMat = glm::translate (glm::mat4 (1.0f ), glm::vec3 (0.0f , 0.0f , 0.0f )); mMat = glm::rotate (mMat, 15.0f * 3.14159f / 180.0f , glm::vec3 (1.0f , 0.0f , 0.0f )); currentLightPos = glm::vec3 (initialLightLoc.x, initialLightLoc.y, initialLightLoc.z); installLights (vMat); mvMat = vMat * mMat; invTrMat = glm::transpose (glm::inverse (mvMat)); glUniformMatrix4fv (projLoc, 1 , GL_FALSE, glm::value_ptr (pMat)); glUniformMatrix4fv (mvLoc, 1 , GL_FALSE, glm::value_ptr (mvMat)); glUniformMatrix4fv (nLoc, 1 , GL_FALSE, glm::value_ptr (invTrMat)); glBindBuffer (GL_ARRAY_BUFFER, vbo[0 ]); glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (0 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[1 ]); glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (1 ); glBindBuffer (GL_ARRAY_BUFFER, vbo[2 ]); glVertexAttribPointer (2 , 3 , GL_FLOAT, GL_FALSE, 0 , 0 ); glEnableVertexAttribArray (2 ); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, texture); glActiveTexture (GL_TEXTURE1); glBindTexture (GL_TEXTURE_2D, heightMap); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glDrawArrays (GL_TRIANGLES, 0 , model.getNumVertices ()); } int main () { if (!glfwInit ()) exit (EXIT_FAILURE); glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 6 ); GLFWwindow *window = glfwCreateWindow (800 , 600 , "Bumps" , NULL , NULL ); glfwMakeContextCurrent (window); if (glewInit () != GLEW_OK) exit (EXIT_FAILURE); glfwSwapInterval (1 ); glfwSetWindowSizeCallback (window, windowSizeCallback); init (window); while (!glfwWindowShouldClose (window)) { display (window, glfwGetTime ()); glfwSwapBuffers (window); glfwPollEvents (); } glfwDestroyWindow (window); glfwTerminate (); return 0 ; }
顶点着色器和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #version 430 layout (location = 0 ) in vec3 vertPos;layout (location = 1 ) in vec2 texCoord;layout (location = 2 ) in vec3 vertNormal;layout (binding = 1 ) uniform sampler2D h; out vec2 tc;uniform mat4 mv_matrix;uniform mat4 proj_matrix;void main(){ vec4 p = vec4 (vertPos, 1.0 ) + vec4 ((vertNormal * ((texture (h, texCoord).r) / 5.0 f)), 1.0 f); tc = texCoord; gl_Position = proj_matrix * mv_matrix * p; }
1 2 3 4 5 6 7 8 9 10 11 12 #version 460 core in vec2 tc;out vec4 fragColor;layout (binding = 0 ) uniform sampler2D t; void main(){ fragColor = texture (t, tc); }
凹凸贴图或法线贴图的一个基本限制是:
虽然能够在所渲染对象的内部提供表面细节,
但是物体轮廓(外边界)无法显示这些细节(仍保持平滑)。
高度贴图在用于实际修改顶点位置时修复了这个缺陷,但它也有其自身的局限性。