本文章內(nèi)容代碼可在這里找到障簿,如果此代碼對(duì)您有幫助,煩請(qǐng)動(dòng)動(dòng)您的手指栅迄,點(diǎn)個(gè)
Star
站故,謝謝!歡迎訪問我的個(gè)人主頁(yè)Orient毅舆。
創(chuàng)建窗口
1西篓、首先我們引入必要的頭文件:
#include "glad.h"
#include <GLFW/glfw3.h>
請(qǐng)確保GLAD頭文件的引入在GLFW之前,GLAD的頭文件包含了正確的OpenGL頭文件(例如GL/gl.h
)憋活,所以需要在其他依賴于OpenGL的頭文件之前引入GLAD
2岂津、實(shí)例化GLFW窗口
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Mac必須添加此行,Windows忽略
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
return 0;
}
前兩行代碼指定了OpenGL的主版本和次版本號(hào)(4.1)悦即,第三行代表著使用核心模式(Core-profile)吮成,意味著我們只能使用OpenGL功能的一個(gè)子集(沒有我們不再需要的向后兼容特性)橱乱。
3、接下來(lái)創(chuàng)建一個(gè)窗口對(duì)象粱甫,它存放了所有和窗口相關(guān)的數(shù)據(jù)泳叠,而且會(huì)被GLFW的其他函數(shù)頻繁調(diào)用
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if(window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContexCurrent(window);
glfwCreateWindow函數(shù),前兩個(gè)參數(shù)是窗口的寬高茶宵,第三個(gè)參數(shù)是這個(gè)窗口的命名析二,后兩個(gè)暫時(shí)忽略,返回了一個(gè)GLFWwindow對(duì)象节预。glfwMakeContexCurrent函數(shù)告訴GLFW將窗口的上下文設(shè)置為當(dāng)前線程的主上下文叶摄。
4、GLAD是用來(lái)管理OpenGL的函數(shù)指針的安拟,所以調(diào)用任何OpenGL函數(shù)之前需要初始化GLAD
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
我們給GLAD傳入了用來(lái)加載系統(tǒng)相關(guān)的OpenGL函數(shù)指針地址的函數(shù)蛤吓。GLFW給我們的是glfwGetProcAdress
,它根據(jù)我們編譯的系統(tǒng)定義了正確的函數(shù)糠赦。
5会傲、視口
在開始渲染之前必須告訴OpenGL渲染窗口(Viewport)的尺寸大小,這樣OpenGL才能知道怎樣根據(jù)窗口大小顯示數(shù)據(jù)和坐標(biāo)拙泽。
// 此函數(shù)設(shè)置窗口的維度(Dimension)
glViewport(0, 0, 800, 600);
前兩個(gè)參數(shù)控制窗口左下角位置淌山,后兩個(gè)控制渲染窗口的寬高(像素)。也可將視口維度設(shè)置比GLFW窗口維度小顾瞻,這樣子之后所有的OpenGL渲染將會(huì)在一個(gè)更小的窗口中顯示泼疑,這樣子的話我們也可以將一些其它元素顯示在OpenGL視口之外。
6荷荤、對(duì)窗口注冊(cè)回調(diào)函數(shù)(CallbackFunction)
函數(shù)注冊(cè)后會(huì)在每次窗口大小改變的時(shí)候調(diào)用退渗,視口也會(huì)隨之調(diào)整
函數(shù)原型如下:
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
進(jìn)行注冊(cè),告訴GLFW每當(dāng)窗口調(diào)整時(shí)調(diào)用此函數(shù):
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
7蕴纳、為了使圖像能夠持續(xù)顯示而不是一閃即逝会油,我們需要寫一個(gè)渲染循環(huán),使得GLFW在退出之前一直保持運(yùn)行
while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwWindowShouldClose
函數(shù)在每次循環(huán)開始前檢查一次GLFW是否被要求退出古毛,是的話返回true
翻翩,循環(huán)結(jié)束
glfwPollEvents
函數(shù)檢查是否有觸發(fā)事件,比如鍵盤稻薇、鼠標(biāo)等信號(hào)輸入嫂冻,然后更新窗口狀態(tài),調(diào)用相應(yīng)的回調(diào)函數(shù)(可通過回調(diào)方法手動(dòng)設(shè)置)颖低。
glfwSwapBuffers
函數(shù)會(huì)交換顏色緩沖
8絮吵、渲染結(jié)束后釋放所有資源
glfwTerminate();
return 0;
至此弧烤,窗口創(chuàng)建完成
接下來(lái)我們進(jìn)行一些完善工作
9忱屑、接下來(lái)我們添加一個(gè)觸發(fā)時(shí)間蹬敲,當(dāng)用戶按下Esc
鍵時(shí)關(guān)閉窗口。
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
glfwGetKey
函數(shù)需要一個(gè)窗口以及一個(gè)按鍵作為輸入莺戒。這個(gè)函數(shù)將會(huì)返回這個(gè)案件是否正在被按下伴嗡,我們將其定義在processInput
函數(shù)當(dāng)中
接下來(lái)在渲染循環(huán)的每一個(gè)迭代中調(diào)用processInput
:
while (!glfwWindowShouldClose(window))
{
processInput(window);
// 這里是渲染指令
...
glfwSwapBuffers(window);
glfwPollEvents();
}
10、我們使用一個(gè)自定義的顏色清空屏幕从铲,使得在每個(gè)新的渲染迭代開始后清除上一次渲染結(jié)果瘪校,并顯示我們自定義的顏色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
渲染一個(gè)三角形
開始繪制之前,我們需要給OpenGL輸入一些頂點(diǎn)數(shù)據(jù)(范圍在[-1, 1]名段,需要自行進(jìn)行坐標(biāo)變換)阱扬。我們需要渲染一個(gè)三角形,因此我們需要三個(gè)頂點(diǎn)位置伸辟,將它定義為一個(gè)float
數(shù)組:
// 由于我們繪制的是一個(gè)2D三角形麻惶,因此,將其頂點(diǎn)的z坐標(biāo)都設(shè)置為0
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
接下來(lái)使用glGenBuffers
函數(shù)和一個(gè)緩沖ID生成一個(gè)VBO對(duì)象信夫,并使用glBindBuffer
函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER
目標(biāo)上:
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
從這一刻起窃蹋,我們使用的任何(在GL_ARRAY_BUFFER
目標(biāo)上的)緩沖調(diào)用都會(huì)用來(lái)配置當(dāng)前綁定的緩沖(VBO)。然后我們可以調(diào)用glBufferData函數(shù)静稻,它會(huì)把之前定義的頂點(diǎn)數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
第一個(gè)參數(shù)是目標(biāo)緩沖的類型警没,頂點(diǎn)緩沖對(duì)象當(dāng)前綁定到GL_ARRAY_BUFFER
目標(biāo)上。
第二個(gè)參數(shù)指定傳輸數(shù)據(jù)大小振湾。
第三個(gè)參數(shù)是我們實(shí)際發(fā)送的數(shù)據(jù)杀迹。
第四個(gè)參數(shù)指定了顯卡管理數(shù)據(jù)的方式,有一下三種形式:
GL_STATIC_DRAW
:數(shù)據(jù)不會(huì)或幾乎不改變押搪。
GL_DYNAMIC_DRAW
:數(shù)據(jù)會(huì)改變很多佛南。
GL_STREAM_DRAW
:數(shù)據(jù)每次繪制都會(huì)改變。
頂點(diǎn)著色器
首先用GLSL
(OoenGL Shading Language)編寫頂點(diǎn)著色器嵌言,然后編譯這個(gè)著色器嗅回。下面給出一個(gè)非常基礎(chǔ)的頂點(diǎn)著色器源代碼:
#version 410 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
首先申明OpenGL版本4.1(對(duì)應(yīng)410)摧茴。
接下來(lái)使用in
關(guān)鍵字绵载,在頂點(diǎn)著色器中聲明所有的輸入頂點(diǎn)屬性(Input Vertex Attribute)。現(xiàn)在我們只關(guān)心位置(Position)數(shù)據(jù)苛白,所以我們只需要一個(gè)頂點(diǎn)屬性娃豹。GLSL有一個(gè)向量數(shù)據(jù)類型,它包含1到4個(gè)float
分量购裙,包含的數(shù)量可以從它的后綴數(shù)字看出來(lái)懂版。由于每個(gè)頂點(diǎn)都有一個(gè)3D坐標(biāo),我們就創(chuàng)建一個(gè)vec3
輸入變量aPos
躏率。我們同樣也通過layout (location = 0)
設(shè)定了輸入變量的位置值(Location)你后面會(huì)看到為什么我們會(huì)需要這個(gè)位置值躯畴。
編譯著色器
先創(chuàng)建一個(gè)著色器對(duì)象民鼓,注意還是用ID來(lái)引用。所以我們儲(chǔ)存這個(gè)頂點(diǎn)著色器為unsigned int
蓬抄,然后用glCreateShader
創(chuàng)建這個(gè)著色器:
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
我們把需要?jiǎng)?chuàng)建的著色器類型以參數(shù)形式提供給glCreateShader
丰嘉。由于我們正在創(chuàng)建一個(gè)頂點(diǎn)著色器,傳遞的參數(shù)是GL_VERTEX_SHADER
嚷缭。
接下來(lái)把著色器源碼附加到著色器對(duì)象上饮亏,并編譯它:
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource
函數(shù)把要編譯的著色器對(duì)象作為第一個(gè)參數(shù)。第二參數(shù)指定了傳遞的源碼字符串?dāng)?shù)量阅爽,這里只有一個(gè)路幸。第三個(gè)參數(shù)是頂點(diǎn)著色器真正的源碼,第四個(gè)參數(shù)我們先設(shè)置為NULL
付翁。
片段著色器
先給出片段著色器源碼:
#version 410 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
片段著色器只需要一個(gè)輸出變量劝赔,這個(gè)變量是一個(gè)4分量向量,它表示的是最終的輸出顏色胆敞,我們應(yīng)該自己將其計(jì)算出來(lái)着帽。我們可以用out
關(guān)鍵字聲明輸出變量,這里我們命名為FragColor
移层。下面仍翰,我們將一個(gè)alpha值為1.0(1.0代表完全不透明)的橘黃色的vec4
賦值給顏色輸出。
編譯片段著色器的過程與頂點(diǎn)著色器類似观话,只不過我們使用GL_FRAGMENT_SHADER
常量作為著色器類型:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
著色器程序
著色器程序?qū)ο?Shader Program Object)是多個(gè)著色器合并之后并最終鏈接完成的版本予借。如果要使用剛才編譯的著色器我們必須把它們鏈接(Link)為一個(gè)著色器程序?qū)ο螅缓笤阡秩緦?duì)象的時(shí)候激活這個(gè)著色器程序频蛔。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時(shí)候被使用灵迫。
創(chuàng)建程序?qū)ο螅?/p>
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glCreateProgram
函數(shù)創(chuàng)建一個(gè)程序,并返回新創(chuàng)建程序?qū)ο蟮腎D引用』尴現(xiàn)在我們需要把之前編譯的著色器附加到程序?qū)ο笊掀僦啵缓笥?code>glLinkProgram鏈接它們:
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
最后調(diào)用glUseProgram
函數(shù),激活程序:
glUseProgram(shaderProgram);
著色器對(duì)象鏈接到程序?qū)ο笠院笕玻枰獎(jiǎng)h除著色器對(duì)象:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
鏈接頂點(diǎn)屬性
頂點(diǎn)著色器允許我們指定任何以頂點(diǎn)屬性為形式的輸入狞换。這使其具有很強(qiáng)的靈活性的同時(shí),它還的確意味著我們必須手動(dòng)指定輸入數(shù)據(jù)的哪一個(gè)部分對(duì)應(yīng)頂點(diǎn)著色器的哪一個(gè)頂點(diǎn)屬性舟肉。所以修噪,我們必須在渲染前指定OpenGL該如何解釋頂點(diǎn)數(shù)據(jù)。
我們的頂點(diǎn)緩沖數(shù)據(jù)會(huì)被解析為下面這樣子:
因此使用glVertexAttribPointer
函數(shù)告訴OpenGL該如何解析頂點(diǎn)數(shù)據(jù):
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
第一個(gè)參數(shù)指定我們要配置的頂點(diǎn)屬性
第二個(gè)參數(shù)指定頂點(diǎn)屬性的大小路媚。
第三個(gè)參數(shù)指定數(shù)據(jù)的類型
第四個(gè)參數(shù)定義我們是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)黄琼。如果我們?cè)O(shè)置為GL_TRUE
,所有數(shù)據(jù)都會(huì)被映射到0(對(duì)于有符號(hào)型signed數(shù)據(jù)是-1)到1之間整慎。
第五個(gè)參數(shù)是步長(zhǎng)脏款,它告訴我們?cè)谶B續(xù)的頂點(diǎn)屬性組之間的間隔围苫。
最后一個(gè)參數(shù)表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset)。
接下來(lái)使用glEnableVertexAttribArray
函數(shù)弛矛,以頂點(diǎn)屬性位置值作為參數(shù)够吩,啟用頂點(diǎn)屬性比然。
代碼最終大概長(zhǎng)這樣:
// 0. 復(fù)制頂點(diǎn)數(shù)組到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 當(dāng)我們渲染一個(gè)物體時(shí)要使用著色器程序
glUseProgram(shaderProgram);
// 3. 繪制物體
someOpenGLFunctionThatDrawsOurTriangle();
頂點(diǎn)數(shù)組對(duì)象
Vertex Array Object(VAO)可以像頂點(diǎn)緩沖對(duì)象那樣被綁定丈氓,任何隨后的頂點(diǎn)屬性調(diào)用都會(huì)儲(chǔ)存在這個(gè)VAO中。
OpenGL的核心模式要求我們使用VAO强法,所以它知道該如何處理我們的頂點(diǎn)輸入万俗。如果我們綁定VAO失敗,OpenGL會(huì)拒絕繪制任何東西饮怯。
一個(gè)頂點(diǎn)數(shù)組對(duì)象會(huì)儲(chǔ)存以下這些內(nèi)容:
glEnableVertexAttribArray
和glDisableVertexAttribArray
的調(diào)用闰歪。
通過glVertexAttribPointer
設(shè)置的頂點(diǎn)屬性配置。
通過glVertexAttribPointer
調(diào)用與頂點(diǎn)屬性關(guān)聯(lián)的頂點(diǎn)緩沖對(duì)象蓖墅。
VAO的創(chuàng)建類似VBO:
unsigned int VAO;
glGenVertexArrays(1, &VAO);
要使用VAO库倘,只需使用glBindVertexArray
綁定VAO。從綁定之后起论矾,我們應(yīng)該綁定和配置對(duì)應(yīng)的VBO和屬性指針教翩,之后解綁VAO供之后使用。當(dāng)我們打算繪制一個(gè)物體的時(shí)候贪壳,我們只要在繪制物體前簡(jiǎn)單地把VAO綁定到希望使用的設(shè)定上就行了饱亿。
代碼大概是這樣的:
// ..:: 初始化代碼(只運(yùn)行一次 (除非你的物體頻繁改變)) :: ..
// 1. 綁定VAO
glBindVertexArray(VAO);
// 2. 把頂點(diǎn)數(shù)組復(fù)制到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 繪制代(渲染循環(huán)中) :: ..
// 4. 繪制物體
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
繪制三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawArrays
函數(shù)第一個(gè)參數(shù)是打算繪制的圖元的類型。第二個(gè)參數(shù)制訂了頂點(diǎn)數(shù)組的起始索引闰靴,第三個(gè)參數(shù)指定我們打算繪制的頂點(diǎn)個(gè)數(shù)彪笼。
最終三角形是長(zhǎng)這樣的: