在2019年12月份到新的公司,公司做飞机飞行态势感知系统,需要用的OpenGL的知识,发现自己对OpenGL知识太欠缺了,虽然之前断断续续自学了《3D数学基础:图形与游戏开发第一二中英文版》、《OpenGL SuperBible》、《LearnOpenGL》和《OpenGL Programming Guide》,感觉自己无法实现飞机的渲染特效,比方说在已有的3D模型中加入纹理、光照、材质、半透明、玻璃等等,完全不能熟练运用。发现自己之前完全没有学懂,更不能融会贯通。哎。。。。。。一言难尽,无奈看来只有重学了,因为里面的着色器代码看不懂,所以有了这本
《OpenGL 4.0 Shading Language Cookbook》。。。。。下面先说说阅读这几本书的体会。
目前为止,再没有比这本书更系统讲解3D数学基础的书籍了,估计除了《Computer.Graphics.with.Open.GL.4th.Edition》(819页)这本书,但是这本书800多页,确实没信心看完,暂时放一放。
《3D数学基础》这本书讲解了各种矩阵变换原理、推导公式、物理意义,比方说叉乘矩阵乘法规则,
模型对象矩阵(ModelMatrix),视图矩阵(viewMatrix)、投影矩阵(projectionMatrix),套用网上的资源
a和b两个向量的叉乘结果是垂直于这两个的法线,即
a * b = c
点乘的几何意义是可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影,公式
根据这个公式就可以计算向量a和向量b之间的夹角。从而就可以进一步判断这两个向量是否是同一方向,是否正交(也就是垂直)等方向关系,具体对应关系为:
a·b>0 方向基本相同,夹角在0°到90°之间
a·b=0 正交,相互垂直
a·b<0 方向基本相反,夹角在90°到180°之间
叉乘和点乘在GLSL(着色器编程)中非常非常非常重要,因为在顶点着色器和片元着色器中,经常看到看到叉乘和点乘函数,还有矩阵变换,例如下面这段顶点着色器代码:
c++主程序向着色器程序传入modelMatrix、viewMatrix、projectionMatrix这三种矩阵值
没有这些3D图形数学基础,这些代码真的很难看懂。
2.《OpenGL SuperBible》(2002页)
这本书是我的大爱,因为他太适合入门了,书中详细讲解了使用本书源码的几乎所有第三方库的配置,比方说
glew / glfw glut glm, assimp,SDL2 freetype作者还提供了丰富的工具封装类,大致概括一下这几个三方库的作用
(1)glew GLEW是OpenGL Extension Wrangler Library的缩写,因为GLEW也是一个库,我们同样需要构建并将其链接进工程 解析顶点着色器,片元着色器,加载着色器源码、链接着色器程序,编译着色器程序。glew下载地址
http://glew.sourceforge.net/index.html
(2)glfw 创建创建渲染窗口,窗口鼠标、键盘事件
GLFW是一个用于OpenGL、OpenGL ES和Vulkan桌面开发的开源多平台库。它提供了一个简单的API,用于创建窗口、上下文和表面、接收输入和事件。GLFW是用C语言编写的,支持Windows、macOS、X11和Wayland。GLFW使用zlib/libpng许可。
glfw 下载地址
https://www.glfw.org/
(3)glut GLUT,是指OpenGL Utility Toolkit,用于开发独立于窗口系统的OpenGL程序,即该库打包了很多的窗口操作,提供了独立于具体操作系统的API,让你快速的在OpenGL开发中完成窗口相关操作,如窗口显示、输入设备读取、多级级联菜单、多窗口管理等等便捷功能,他的升级版freeglut
glut下载地址,
https://www.opengl.org/resources/libraries/glut/
(4)soil
(5)glm
OpenGL Mathematics (GLM)是一个基于OpenGL底纹语言(GLSL)规范的图形软件头C数学库。GLM提供了用与GLSL相同的命名约定和功能设计和实现的类和函数,因此任何了解GLSL的人都可以在C语言中使用GLM。一个基于GLSL扩展约定的扩展系统提供了扩展功能:矩阵变换、四元数、数据打包、随机数、噪声等等。这个库在OpenGL上工作得很好,但它也确保了与其他第三方库和SDK的互操作性。它是软件渲染(光线追踪/光栅化)、图像处理、物理模拟和任何需要简单方便的数学库的开发环境的一个很好的候选。GLM是用c98编写的,但是在编译器支持的情况下可以利用c11。它是一个平台独立的库,没有任何依赖性
下载地址:https://glm.g-truc.net/0.9.9/index.html
(6) assimp
在3D渲染的时候,工作量比较大且比较麻烦的一件事就是建模,如果想降低这种麻烦就需要借用网络上已经存在的一些模型素材,至少这是非商用渲染程序常用手段(咱们自己写例子的时候经常这样做)。但是,由于网络中的模型格式众多,如果自己一一去解析代价也挺高的。ASSIMP作为一个开源项目,设计了一套可扩展的架构,为模型的导入导出提供了良好的支持。这里说的导入是把模型文件,解析成ASSIMP自身定义的一套模型,而导出即是把自身建立的模型结构导出为模型文件。
ASSIMP默认提供了网络上比较流行的多种模型文件格式的导入和导出,如果我们仍需要对一下特殊的文件格式做这些操作,可以自己扩展。
下载地址
https://github.com/assimp/assimp
(7)freetype
FreeType是一个免费提供的用于呈现字体的软件库。它是用C编写的,设计为小巧、高效、高度可定制和可移植,同时能够产生高质量的矢量和位图字体格式输出(字形图像)。一些产品使用FreeType在屏幕上或纸上渲染字体,或完全或部分
下载地址:
https://www.freetype.org/
这几个库我都用过,文章最后给出的下载工程包含对这些库的使用,欢迎各位看客下载,交流
并且详细讲解了程序中用到的3D数学基础,比如矩阵变换推导过程等等。
最重要的是里面有大量的实例代码,就算是把他的代码敲到你的电脑上都会让你有满满的成就感。
这本书目前是我的最爱,应为他最适合入门了,没有之一,作者太有耐心了,几乎每一行的关键代码都不厌其烦讲解的很透彻,生怕读者看不懂,作者大大的赞啊。同样里面例子丰富,比方说下图
再文章的最后,我会附上机器人站在天空盒的完整代码,供读者下载
附上一段代码先
// cubemaps-exercise1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "glew/glew.h"
#include "glfw/glfw3.h"
#include "SOIL.h"
#include "camera.h"
#include "filesystem.h"
#include "mesh.h"
#include "model.h"
#include "root_directory.h"
#include "shader_m.h"
#include "stb_image.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include
#include
using namespace std;
const GLuint screenWidth = 1920;
const GLuint screenHeight = 1080;
void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow *window, double xPos, double yPos);
void mouse_callback(GLFWwindow *window, double xOffset, double yOffset);
void do_movement();
GLuint loadTexture(GLchar *path, GLboolean alpha = GL_FALSE);
GLuint loadCubemap(vector faces);
Camera camera(glm::vec3(0.0f, 8.0f, 15.0f));
GLboolean keys[1024];
GLfloat lastX = (GLfloat)screenWidth / 2.0f;
GLfloat lastY = (GLfloat)screenHeight / 2.0f;
GLboolean firstMouse = GL_FALSE;
GLfloat lastFrame = 0.0f;
GLfloat deltaTime = 0.0f;
int main()
{
GLenum glfwErr = glfwInit();
if (GLFW_FALSE == glfwErr)
{
cout << "GLFW initialization failed!" << endl;
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow *window = glfwCreateWindow(screenWidth, screenHeight, "cubemaps", nullptr, nullptr);
if (nullptr == window)
{
cout << "GLFW create window failed!" << endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
glewExperimental = GL_TRUE;
GLenum glewErr = glewInit();
if (GLEW_OK != glewErr)
{
cout << "GLEW initialization failed!" << endl;
return -1;
}
const GLubyte *renderer = glGetString(GL_RENDERER);
const GLubyte *vendor = glGetString(GL_VENDOR);
const GLubyte *version = glGetString(GL_VERSION);
const GLubyte *glslVersion =
glGetString(GL_SHADING_LANGUAGE_VERSION);
GLint major, minor;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
printf("GL Vendor : %s\n", vendor);
printf("GL Renderer : %s\n", renderer);
printf("GL Version (string) : %s\n", version);
printf("GL Version (integer) : %d.%d\n", major, minor);
printf("GLSL Version : %s\n", glslVersion);
glGetError(); // Debug GLEW bug fix
glViewport(0, 0, screenWidth, screenHeight);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
Shader shader("6.3.cubemaps.vs", "6.3.cubemaps.fs");
Shader skyboxShader("6.1.skybox.vs", "6.1.skybox.fs");
GLfloat skyboxVertices[] = {
// Positions
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f
};
GLuint skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glBindVertexArray(0);
vector faces;
faces.push_back("resources/textures/skybox/right.jpg");
faces.push_back("resources/textures/skybox/left.jpg");
faces.push_back("resources/textures/skybox/top.jpg");
faces.push_back("resources/textures/skybox/bottom.jpg");
faces.push_back("resources/textures/skybox/front.jpg");
faces.push_back("resources/textures/skybox/back.jpg");
GLuint skyboxTextures = loadCubemap(faces);
Model nanosuit("resources/objects/nanosuit/nanosuit.obj");
//绘制线框
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
while (!glfwWindowShouldClose(window))
{
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
glfwPollEvents();
do_movement();
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
view = camera.GetViewMatrix();
projection = glm::perspective(camera.Zoom, (GLfloat)screenWidth / (GLfloat)screenHeight, 0.0001f, 10000.0f);
shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", projection);
shader.setVec3("cameraPos", glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z));
//我们已经有3个纹理单元处于活动状态(在该着色器中),
//因此将skybox设置为第4个纹理单元(纹理单位基于0,因此索引编号为3)
glActiveTexture(GL_TEXTURE3);
shader.setInt("skybox", 3);
// Now draw the nanosuit
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTextures);
nanosuit.Draw(shader);
//draw skybox
glDepthFunc(GL_LEQUAL);
skyboxShader.use();
view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
skyboxShader.setMat4("view", view);
skyboxShader.setMat4("projection", projection);
//skybox cube
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
skyboxShader.setInt("skybox", 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTextures);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
// Set depth function back to default
glDepthFunc(GL_LESS);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &skyboxVAO);
glDeleteBuffers(1, &skyboxVBO);
glfwTerminate();
return 0;
}
// Loads a cubemap texture from 6 individual texture faces
// Order should be:
// +X (right)
// -X (left)
// +Y (top)
// -Y (bottom)
// +Z (front)
// -Z (back)
GLuint loadCubemap(vector faces)
{
GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
int width, height;
unsigned char *image = nullptr;
for (GLuint i = 0; i < faces.size(); i++)
{
image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
SOIL_free_image_data(image);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureId;
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar *path, GLboolean alpha /* = GL_FALSE */)
{
GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
int width, height;
unsigned char * image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureId;
}
void do_movement()
{
if (keys[GLFW_KEY_W])
{
camera.ProcessKeyboard(FORWARD, deltaTime);
}
if (keys[GLFW_KEY_S])
{
camera.ProcessKeyboard(BACKWARD, deltaTime);
}
if (keys[GLFW_KEY_A])
{
camera.ProcessKeyboard(LEFT, deltaTime);
}
if (keys[GLFW_KEY_D])
{
camera.ProcessKeyboard(RIGHT, deltaTime);
}
}
void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
if (action == GLFW_PRESS)
{
keys[key] = GL_TRUE;
}
else if (action == GLFW_RELEASE)
{
keys[key] = GL_FALSE;
}
}
void mouse_callback(GLFWwindow *window, double xPos, double yPos)
{
if (!firstMouse)
{
lastX = xPos;
lastY = yPos;
firstMouse = GL_TRUE;
}
GLfloat xOffset = xPos - lastX;
GLfloat yOffset = lastY - yPos;
lastX = xPos;
lastY = yPos;
camera.ProcessMouseMovement(xOffset, yOffset);
}
void scroll_callback(GLFWwindow *window, double xPos, double yPos)
{
camera.ProcessMouseScroll(yPos);
}
大名鼎鼎得红宝书,当第一次听说OpenGL,就看了大概4章,实在看不下去,里面讲解openGL api函数如何使用,没有任何openGL和3D数学基础就看了这本书,说实话不知所云,想shi的心都有了,简直怀疑人生啊,强烈不推荐新手看这本书,感觉这本书是字典,openGL绝大多数函数都有讲到。
最后上面背景是天空盒,机器人站在前面的完成工程代码,包含这些库的使用:glew glfw glut glm, assimp,SDL2 freetype
下载地址:https://download.csdn.net/download/aoxuestudy/12822684
这篇文章写的比较凌乱,信息量巨大(哈哈,嘚瑟一下哈),后续会对OpenGL_4.0_Shading_Language_Cookbook的理解记录下来,希望能和各位交流学习,共同进步。
good enjoy!