說明
OpenGL學習中文網
學習OpenGL3.3核心模式(因爲早期的立即渲染模式,也稱固定渲染管線,這個模式雖然繪圖方便,但靈活性不強,OpenGL3.2開始,規範文檔開始廢棄立即渲染模式,並鼓勵開發者在OpenGL的核心模式(Core-profile)下進行開發,爲什麼要使用3.3版本,因爲所有OpenGL的更高的版本都是在3.3的基礎上,引入了額外的功能,並沒有改動核心架構)
開發環境Win10,VS2017
OpenGL開發環境搭建
GLFW配置
GLFW是一個專門針對OpenGL的C語言庫,它提供了一些渲染物體所需的最低限度的接口。它允許用戶創建OpenGL上下文,定義窗口參數以及處理用戶輸入
GLFW源碼包下載 ,請下載32位的版本,64位版本大部分人反映有BUG。
CMake生成項目,並使用VistualStudio編譯之後在src/Debug文件夾內可找到glfw3.lib
創建文件夾ThirdParty/glfw3存放include和lib
#include <GLFW\glfw3.h>
GLAD配置
GLAD是一個開源的庫,GLAD使用了一個在線服務 。
選擇對應選項後,點擊生成(Generate)按鈕來生成庫文件。
GLAD現在應該提供給你了一個zip壓縮文件,包含兩個頭文件目錄,和一個glad.c文件。將兩個頭文件目錄(glad和KHR)複製到你的include文件夾中,並添加glad.c文件到你的工程中。
在項目引用頭文件的方法
#include <glad/glad.h>
VS項目引入第三方庫
創建VC++控制檯項目
拷貝glad.c文件到項目中
項目設置中添加包含目錄:ThirdParty/opengl/include
添加庫目錄:ThirdParty/opengl/lib
添加庫:opengl32.lib;glfw3.lib;
知識理論
圖形渲染管線(Graphics Pipeline)
關鍵詞
頂點數組對象:Vertex Array Object,VAO
頂點緩衝對象:Vertex Buffer Object,VBO
索引緩衝對象:Element Buffer Object,EBO或Index Buffer Object,IBO
GLSL(OpenGL着色器語言)
in 和 out
uniform
紋理(Texture)
紋理是一個2D圖片(甚至也有1D和3D的紋理)
紋理座標(Texture Coordinate),爲了能夠把紋理映射到三角形上,三角形每個頂點關聯着一個紋理座標,用來標明該從紋理圖像的哪個部分採樣(採集片段顏色),紋理座標在x和y軸上,範圍爲0到1之間(2D紋理圖像),使用紋理座標獲取紋理顏色叫做採樣(Sampling)。下面的圖片展示了我們是如何把紋理座標映射到三角形上的。float texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};
紋理環繞方式
| 環繞方式 | 描述 |
| -------- | ------------- |
| GL_REPEAT | 對紋理的默認行爲。重複紋理圖像。 |
| GL_MIRRORED_REPEAT | 和GL_REPEAT一樣,但每次重複圖片是鏡像放置的。 |
| GL_CLAMP_TO_EDGE | 紋理座標會被約束在0到1之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果。 |
| GL_CLAMP_TO_BORDER | 超出的座標爲用戶指定的邊緣顏色。 |
// 可以使用glTexParameter*函數對單獨的一個座標軸設置(紋理座標軸s、t、r等價於x、y、z)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
//選擇GL_CLAMP_TO_BORDER選項,我們還需要指定一個邊緣的顏色。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
紋理過濾(Texture Filtering)
紋理過濾有很多個選項,但是現在我們只討論最重要的兩種:GL_NEAREST和GL_LINEAR。
GL_NEAREST(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL默認的紋理過濾方式。當設置爲GL_NEAREST的時候,OpenGL會選擇中心點最接近紋理座標的那個像素。下圖中你可以看到四個像素,加號代表紋理座標。左上角那個紋理像素的中心距離紋理座標最近,所以它會被選擇爲樣本顏色:
GL_LINEAR(也叫線性過濾,(Bi)linear Filtering)它會基於紋理座標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理座標越近,那麼這個紋理像素的顏色對最終的樣本顏色的貢獻越大。下圖中你可以看到返回的顏色是鄰近像素的混合色:
那麼這兩種紋理過濾方式有怎樣的視覺效果呢?
//我們需要使用glTexParameter*函數爲放大和縮小指定過濾方式。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
變換
向量與標量運算
向量取反
向量加減
長度
向量相乘
點乘(Dot Product),記作v¯⋅k¯
兩個向量的點乘等於它們的數乘結果乘以兩個向量之間夾角的餘弦值。
公式:v¯⋅k¯=||v¯||⋅||k¯||⋅cosθ
乘是通過將對應分量逐個相乘,然後再把所得積相加來計算的。兩個單位向量點乘會像是這樣:
要計算兩個單位向量間的夾角,我們可以使用反餘弦函數cos−1 ,可得結果是143.1度。
另一個是叉乘(Cross Product),記作v¯×k¯。
叉乘只在3D空間中有定義,它需要兩個不平行向量作爲輸入,生成一個正交於兩個輸入向量的第三個向量。下面的圖片展示了3D空間中叉乘的樣子:
兩個正交向量A和B叉積:
矩陣加減
矩陣數乘
矩陣相乘
只有當左側矩陣的列數與右側矩陣的行數相等,兩個矩陣才能相乘。
矩陣相乘不遵守交換律(Commutative),也就是說A⋅B≠B⋅A。
單位矩陣
單位矩陣是一個除了對角線以外都是0的N×N矩陣。
縮放
如果我們把縮放變量表示爲(S1,S2,S3)我們可以爲任意向量(x,y,z)定義一個縮放矩陣:
位移
如果我們把位移向量表示爲(Tx,Ty,Tz),我們就能把位移矩陣定義爲:
旋轉
2D或3D空間中的旋轉用角(Angle)來表示。角可以是角度制或弧度制的,周角是360角度或2 PI弧度。//大多數旋轉函數需要用弧度制的角,但幸運的是角度制的角也可以很容易地轉化爲弧度制的:
弧度轉角度:角度 = 弧度 * (180.0f / PI)
角度轉弧度:弧度 = 角度 * (PI / 180.0f)
旋轉矩陣在3D空間中每個單位軸都有不同定義,旋轉角度用θ表示:
沿x軸旋轉:
沿y軸旋轉:
沿z軸旋轉:
矩陣的組合
假設我們有一個頂點(x, y, z),我們希望將其縮放2倍,然後位移(1, 2, 3)個單位。我們需要一個位移和縮放矩陣來完成這些變換。
建議您在組合矩陣時,先進行縮放操作,然後是旋轉,最後纔是位移。比如,如果你先位移再縮放,位移的向量也會同樣被縮放。
最終的變換矩陣左乘我們的向量會得到以下結果:
不錯!向量先縮放2倍,然後位移了(1, 2, 3)個單位。
項目案例
創建窗口
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
// glfw: initialize and configure
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// glfw window creation
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window ==NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// render loop
while (!glfwWindowShouldClose(window))
{
//input
processInput(window);
//render
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}