OpenGL_4.0_Shading_Language_Cookbook 讀書筆記(1)
前言:
在2019年12月份到新的公司,公司做飛機飛行态勢感覺系統,需要用的OpenGL的知識,發現自己對OpenGL知識太欠缺了,雖然之前斷斷續續自學了《3D數學基礎:圖形與遊戲開發第一二中英文版》、《OpenGL SuperBible》、《LearnOpenGL》和《OpenGL Programming Guide》,感覺自己無法實作飛機的渲染特效,比方說在已有的3D模型中加入紋理、光照、材質、半透明、玻璃等等,完全不能熟練運用。發現自己之前完全沒有學懂,更不能融會貫通。哎。。。。。。一言難盡,無奈看來隻有重學了,因為裡面的着色器代碼看不懂,是以有了這本
《OpenGL 4.0 Shading Language Cookbook》。。。。。下面先說說閱讀這幾本書的體會。
- 《3D數學基礎:圖形與遊戲開發》第一二中英文版 (449頁)
目前為止,再沒有比這本書更系統講解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
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下載下傳位址,
https://www.opengl.org/resources/libraries/glut/
(4)soil
- SOIL是簡易OpenGL圖像庫(Simple OpenGL Image Library)的縮寫,它支援大多數流行的圖像格式。
- SOIL的下載下傳網址:http://www.lonesock.net/soil.html
(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數學基礎,比如矩陣變換推導過程等等。
最重要的是裡面有大量的執行個體代碼,就算是把他的代碼敲到你的電腦上都會讓你有滿滿的成就感。
3. 《Learn OpenGL》
這本書目前是我的最愛,應為他最适合入門了,沒有之一,作者太有耐心了,幾乎每一行的關鍵代碼都不厭其煩講解的很透徹,生怕讀者看不懂,作者大大的贊啊。同樣裡面例子豐富,比方說下圖
再文章的最後,我會附上機器人站在天空盒的完整代碼,供讀者下載下傳
附上一段代碼先
// 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 <iostream>
#include <vector>
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<const GLchar*> 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<const GLchar*> 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<const GLchar *> 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);
}
4.《OpenGL Programming Guide》8th
大名鼎鼎得紅寶書,當第一次聽說OpenGL,就看了大概4章,實在看不下去,裡面講解openGL api函數如何使用,沒有任何openGL和3D數學基礎就看了這本書,說實話不知所雲,想shi的心都有了,簡直懷疑人生啊,強烈不推薦新手看這本書,感覺這本書是字典,openGL絕大多數函數都有講到。
最後上面背景是天空盒,機器人站在前面的完成工程代碼,包含這些庫的使用:glew glfw glut glm, assimp,SDL2 freetype
這篇文章寫的比較淩亂,資訊量巨大(哈哈,嘚瑟一下哈),後續會對OpenGL_4.0_Shading_Language_Cookbook的了解記錄下來,希望能和各位交流學習,共同進步。