说明:OpenGL是基础的底层渲染API,其在3D渲染中的地位类似于编程语言中的C语言,是入门3D渲染首先要学的,打好基础,才是学习好上层封装库的关键。对于OpenGL的上层封装库很多,其中著名的有VTK,OSG等,这里以OpenGL4.5学习为主,VTK9.1学习为辅。VTK多用于医学图像渲染,其封装了很多优秀的算法,在算法上参考价值很大(而且VTK是开源的)。
学习OpenGL可以参照网站http://learnopengl.com、书籍《OpenGL编程指南/红宝书》、书籍《OpenGL超级宝典/蓝宝书》等。本博客以红宝书第九版为例进行学习。
一、Open Graphics Library
1.1、OpenGL前言
OpenGL相关的开发库有很多,大致可以如下三类:
第一类:GLUT、FreeGLUT、GLFW:窗口的管理,事件的处理等。
第二类:GLEW、GLAD、GL3W:获取OpenGL扩展函数的地址(扩展可理解为3.0之后新增)。
第三类:GLM、VMATH:适用于OpenGL的矩阵运算库。
其 他:Qt也是支持OpenGL的,关于上面这三类库,Qt都有自己专属的库。
创建工程时从每类中任选一个库进行组合即可,例如:GLFW+GLAD+GLM、GLFW+GL3W+VMATH、FreeGLUT+GLAD+GLM等,其中网站http://learnopengl.com使用的第一种组合,OpenGL编程指南(第九版)使用的第二种组合需要注意的是第一种组合是最流行的。
红宝书配套源码可去官方网址http://www.opengl-redbook.com下载,需要注意的是源码工程生成的VS2019工程可能有很多error,如果你不能解决,可加QQ群649692007获取作者重构的VS工程。
OpenGL的好多函数是没有返回值的,所以我们无法确定函数是否执行成功,好在有一个函数可以返回当前的错误代码,可以根据函数glGetError()的返回值,大致判断失败原因:
GL_NO_ERROR :(0)当前无错误值
GL_INVALID_ENUM :(1280)仅当使用非法枚举参数时,如果使用该参数有指定环境,则返回 GL_INVALID_OPERATION
GL_INVALID_VALUE :(1281)仅当使用非法值参数时,如果使用该参数有指定环境,则返回 GL_INVALID_OPERATION
GL_INVALID_OPERATION :(1282)命令的状态集合对于指定的参数非法。
GL_STACK_OVERFLOW :(1283)压栈操作超出堆栈大小。
GL_STACK_UNDERFLOW :(1284)出栈操作达到堆栈底部。
GL_OUT_OF_MEMORY :(1285)不能分配足够内存时。
GL_INVALID_FRAMEBUFFER_OPERATION :(1286)当操作未准备好的真缓存时。
GL_CONTEXT_LOST :(1287)由于显卡重置导致 OpenGL context 丢失。
使用如下代码配置OPENGL程序优先选择NVIDIA显卡执行:
extern "C" { //设置优先使用NVIDIA显卡执行
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
}
1.2、工程创建
项目=>属性=>C/C++=>常规=>附加包含目录:
$(ProjectDir)includes
$(ProjectDir)libraries\gl3w\include
$(ProjectDir)libraries\glm-0.9.9.8\glm
$(ProjectDir)libraries\glfw-3.3.6.bin.win64\include
项目=>属性=>链接器=>常规=>附加库目录:
$(ProjectDir)libraries\glfw-3.3.6.bin.win64\lib
项目=>属性=>链接器=>输入=>附加依赖项:
glfw3.lib
opengl32.lib
项目=>属性=>链接器=>输入=>忽略特定默认库:
msvcrt.lib
//01_triangles.h
#ifndef __01_TRIANGLES_H__
#define __01_TRIANGLES_H__
#include "vapp.h"
#include "loadshaders.h"
class triangles:public VermilionApplication
{
public:
typedef class VermilionApplication base; //宏
static VermilionApplication* Create(void)
{
return (s_app = new triangles);
}
virtual void Initialize(const char* title);
virtual void Display(bool auto_redraw);
virtual void Finalize(void);
virtual void Resize(int width, int height);
void OnKey(int key, int scancode, int action, int mods);
};
#endif
//01_triangles.cpp
#include "01_triangles.h"
enum VAO_IDs { Triangles, NumVAOs };
enum Buffer_IDs { ArrayBuffer, NumBuffers };
enum Attrib_IDs { vPosition = 0 };
GLuint VAOs[NumVAOs];
GLuint Buffers[NumBuffers];
const GLuint NumVertices = 6;
void triangles::Initialize(const char* title)
{
base::Initialize(title); //初始化
glGenVertexArrays(NumVAOs, VAOs); //创建VAO
glBindVertexArray(VAOs[Triangles]); //绑定VAO
GLfloat vertices[NumVertices][2] = {
{ -0.90f, -0.90f }, { 0.85f, -0.90f }, { -0.90f, 0.85f }, // Triangle 1
{ 0.90f, -0.85f }, { 0.90f, 0.90f }, { -0.85f, 0.90f } // Triangle 2
};
glGenBuffers(NumBuffers, Buffers); //创建VBO
glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); //绑定VAO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //往VAO加载数据
ShaderInfo shaders[] = {
{ GL_VERTEX_SHADER, "shaders/01_triangles.vert" },
{ GL_FRAGMENT_SHADER, "shaders/01_triangles.frag" },
{ GL_NONE, NULL }
};
GLuint program = LoadShaders(shaders);
glUseProgram(program);
//设置VAO的属性0
glVertexAttribPointer(0, //顶点属性值
2, //顶点属性大小
GL_FLOAT, //参数二的类型
GL_FALSE, //是否归一化
0, //步长
BUFFER_OFFSET(0));//起始偏移
//使能VAO的属性0
glEnableVertexAttribArray(0);
}
void triangles::Display(bool auto_redraw) {
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAOs[Triangles]);
glDrawArrays(GL_TRIANGLES, 0, NumVertices);
base::Display(auto_redraw);
}
void triangles::Finalize(void) {
}
void triangles::Resize(int width, int height) {
glViewport(0, 0, width, height);
}
void triangles::OnKey(int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS)
{
switch (key)
{
case GLFW_KEY_M:
{
static GLenum mode = GL_FILL;
mode = (mode == GL_FILL ? GL_LINE : GL_FILL);
glPolygonMode(GL_FRONT_AND_BACK, mode);
}
return;
}
}
base::OnKey(key, scancode, action, mods);
}
//main.cpp
#include "01_triangles.h"
int main(int argc,char* argv[])
{
VermilionApplication* app = triangles::Create();
app->Initialize("triangles");
app->MainLoop();
app->Finalize();
}
程序运行结果:
【基于vs2019的工程模板下载地址】:https://download.csdn.net/download/BaoTTing/85186641
1.3、渲染管线
1.4、坐标系统
(1)世界坐标系内的坐标乘以观察矩阵变换到眼坐标空间 eye.xyzw = viewMatrix * world.xyzw;
(2)眼坐标系内的坐标通过乘上投影矩阵变换到裁剪空间 clip.xyzw = projectMatrix * eye.xyzw;
(3)裁剪坐标系内的坐标通过透视除法(也就是 w 为 1 化) 到 规范化设备坐标系 ndc.xyz = clip.xyz / clip.w; (片段着色器的输入是:规范化设备坐标)
(4)设备规范化坐标系到窗口坐标系 win.z = (dfar - dnear)/2 * ndc.z + (dfar+dnear)/2;
在透视投影中,为啥倾斜角越大,物体越小的原因。
1.5、着色器程序
1.6、模型文件的加载
1.7、体渲染之切片式
1.8、体渲染之等值面
1.9、体渲染之光线投影
二、The Visualization Tookit
2.1、VTK的下载
2.2、VTK的编译
接下来使用CMake和VS2019两个软件,没有软件的请先安装这俩软件,其中CMake用于给源码配置编译环境,即构建一个VS工程来用来编译源码<1>。CMake构建的VS工程经过编译后,生成头文件和库文件<2>。头文件和库文件讲用于后续工程的建立<3>。
上述<1><2><3>三个步骤的最终目的:把源码编译成库文件使用。
Bin ===>用来存放头文件以及共享库
Build ==>用来存放VTK编译后的文件
Data ==>用于存放下载的 VTKData-9.1.0.tar.gz解压后的所有文件
Source =>用于存放下载的 VTK-9.1.0.tar.gz 解压后的所有源文件
10.1、生成DEBUG版本的库和头文件
10.2、生成RELEASE版本的库和头文件
10.3、生成DEBUG、RELEASE版本的库和头文件