版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.07.25 |
前言
OpenGL 圖形庫項目中一直也沒用過,最近也想學著使用這個圖形庫,感覺還是很有意思,也就自然想著好好的總結一下,希望對大家能有所幫助。
1. OpenGL 圖形庫使用(一) —— 概念基礎
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象、擴展和狀態機
3. OpenGL 圖形庫使用(三) —— 著色器、數據類型與輸入輸出
Uniform
Uniform
是一種從CPU
中的應用向GPU
中的著色器發送數據的方式,但uniform和頂點屬性有些不同。首先,uniform是全局的(Global)
。全局意味著uniform變量必須在每個著色器程序對象中都是獨一無二的,而且它可以被著色器程序的任意著色器在任意階段訪問。第二,無論你把uniform值設置成什么,uniform會一直保存它們的數據,直到它們被重置或更新。我們可以在一個著色器中添加uniform關鍵字至類型和變量名前來聲明一個GLSL
的uniform。從此處開始我們就可以在著色器中使用新聲明的uniform了。
下面我們看一下代碼。
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代碼中設定這個變量
void main()
{
FragColor = ourColor;
}
我們在片段著色器中聲明了一個uniform vec4
的ourColor
,并把片段著色器的輸出顏色設置為uniform值的內容。因為uniform是全局變量,我們可以在任何著色器中定義它們,而無需通過頂點著色器作為中介。頂點著色器中不需要這個uniform。
如果你聲明了一個uniform
卻在GLSL
代碼中沒用過,編譯器會靜默移除這個變量,導致最后編譯出的版本中并不會包含它,這可能導致幾個非常麻煩的錯誤,記住這點!
這個uniform
現在還是空的,我們還沒有給它添加任何數據,所以下面我們就做這件事。我們首先需要找到著色器中uniform屬性的索引/位置值。當我們得到uniform的索引/位置值后,我們就可以更新它的值了。這次我們不去給像素傳遞單獨一個顏色,而是讓它隨著時間改變顏色。
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
首先我們通過glfwGetTime()
獲取運行的秒數。然后我們使用sin
函數讓顏色在0.0到1.0之間改變,最后將結果儲存到greenValue
里。
接著,我們用glGetUniformLocation
查詢uniform ourColor
的位置值。我們為查詢函數提供著色器程序和uniform的名字(這是我們希望獲得的位置值的來源)。如果glGetUniformLocation
返回-1就代表沒有找到這個位置值。最后,我們可以通過glUniform4f
函數設置uniform
值。注意,查詢uniform地址不要求你之前使用過著色器程序,但是更新一個uniform之前你必須先使用程序(調用glUseProgram
),因為它是在當前激活的著色器程序中設置uniform的。
因為OpenGL
在其核心是一個C
庫,所以它不支持類型重載,在函數參數不同的時候就要為其定義新的函數;glUniform
是一個典型例子。這個函數有一個特定的后綴,標識設定的uniform
的類型。可能的后綴有:
每當你打算配置一個OpenGL
的選項時就可以簡單地根據這些規則選擇適合你的數據類型的重載函數。在我們的例子里,我們希望分別設定uniform的4個float值
,所以我們通過glUniform4f
傳遞我們的數據(注意,我們也可以使用fv
版本)。
現在你知道如何設置uniform
變量的值了,我們可以使用它們來渲染了。如果我們打算讓顏色慢慢變化,我們就要在游戲循環的每一次迭代中(所以他會逐幀改變)更新這個uniform。下面我們就計算greenValue
然后每個渲染迭代都更新這個uniform。
while(!glfwWindowShouldClose(window))
{
// 輸入
processInput(window);
// 渲染
// 清除顏色緩沖
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 記得激活著色器
glUseProgram(shaderProgram);
// 更新uniform顏色
float timeValue = glfwGetTime();
float greenValue = sin(timeValue) / 2.0f + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
// 繪制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交換緩沖并查詢IO事件
glfwSwapBuffers(window);
glfwPollEvents();
}
這里的代碼對之前代碼是一次非常直接的修改。這次,我們在每次迭代繪制三角形前先更新uniform值。如果你正確更新了uniform,你會看到你的三角形逐漸由綠變黑再變回綠色。
可以到這里查看源碼。
可以看到,uniform
對于設置一個在渲染迭代中會改變的屬性是一個非常有用的工具,它也是一個在程序和著色器間數據交互的很好工具,但假如我們打算為每個頂點設置一個顏色的時候該怎么辦?這種情況下,我們就不得不聲明和頂點數目一樣多的uniform了。在這一問題上更好的解決方案是在頂點屬性中包含更多的數據,這是我們接下來要做的事情。
更多屬性
下面把顏色數據添加為3個float值至vertices
數組。我們將把三角形的三個角分別指定為紅色、綠色和藍色。
看下面的代碼。
float vertices[ ] =
{
// 位置 // 顏色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 頂部
};
由于現在有更多的數據要發送到頂點著色器,我們有必要去調整一下頂點著色器,使它能夠接收顏色值作為一個頂點屬性輸入。需要注意的是我們用layout
標識符來把aColor
屬性的位置值設置為1
。
#version 330 core
layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為 0
layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值為 1
out vec3 ourColor; // 向片段著色器輸出一個顏色
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 將ourColor設置為我們從頂點數據那里得到的輸入顏色
}
由于我們不再使用uniform
來傳遞片段的顏色了,現在使用ourColor
輸出變量,我們必須再修改一下片段著色器。
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
使用glVertexAttribPointer
函數更新頂點格式。
// 位置屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 顏色屬性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer
函數的前幾個參數比較明了。這次我們配置屬性位置值為1的頂點屬性。顏色值有3個float那么大,我們不去標準化這些值。我們必須指定一個偏移量。對于每個頂點來說,位置頂點屬性在前,所以它的偏移量是0。顏色屬性緊隨位置數據之后,所以偏移量就是3 * sizeof(float)
,用字節來計算就是12
字節。
這里可以看到源碼
這個圖片可能不是你所期望的那種,因為我們只提供了3個顏色,而不是我們現在看到的大調色板。這是在片段著色器中進行的所謂片段插值(Fragment Interpolation)
的結果。
后記
未完,待續~~