前言
我們對頂點數(shù)組對象(VAO)和頂點緩存對象(VBO)有了初步的印象之后。我們可以繼續(xù)接觸另一個OpenGL有趣的模塊盾沫,GLSL語言。正是有了這門語言,才能在復(fù)雜的圖形編程中摩桶,做了不少簡化。
如果遇到問題請在這個地址找本人:http://www.reibang.com/p/9267e7b8640f
正文
接著上一篇文章帽揪,在聊GLSL之前硝清,先介紹一個常用的工具。我們不可能每一次都在一個字符串中編寫一個GLSL的代碼转晰。我們需要更加好用的工具芦拿,讓開發(fā)更加接近我們正常的編寫手段。
Shader的設(shè)計
實際上查邢,在我們的編寫OpenGL過程中蔗崎,發(fā)現(xiàn)有很多共性。為什么我們不把他抽象成一個類去處理呢扰藕?
我們這一次缓苛,需要一個類去控制整個著色器編譯流程。要控制其著色器程序讀取文件中的字符串,還有一個標志位邓深,用來判斷是否已經(jīng)編譯成功他嫡,能夠使用。
因為在OpenGL中庐完,頂點著色器和片元著色器必須存在钢属。因此作為構(gòu)造函數(shù)穿進去。后續(xù)就會繼續(xù)豐富這個類门躯,加入更多的著色器淆党。
Shader.hpp
class Shader{
public:
unsigned int ID;
bool isSuccess = false;
const char* mVertexPath;
const char* mFragmentPath;
public:
Shader(const char* vertexPath,const char* fragmentPath);
void compile();
void use();
inline bool isCompileSuccess() const{
return isSuccess;
}
~Shader();
private:
bool checkComplieErrors(unsigned int shader,std::string type);
};
我們需要一個編譯所有傳到Shader中的GLSL文件compile方法。其次還需要一個使用著色器程序的方法,以及一個判斷當(dāng)前編譯是否正常的方法染乌。以及一個私有的打印異常的方法山孔。
Shader的實現(xiàn)
首先實現(xiàn)構(gòu)造函數(shù)。
Shader::Shader(const char* vertexPath,const char* fragmentPath){
mVertexPath = vertexPath;
mFragmentPath = fragmentPath;
}
接著實現(xiàn)編譯流程,我們需要讀取從文件中讀取字符串上來并且編譯
void Shader:: compile(){
if(!mVertexPath || !mFragmentPath){
cout<< "Error::Shader::please set file Path";
return;
}
string vertexCode;
string fragmentCode;
ifstream vShaderFile;
ifstream fShaderFile;
vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
try{
vShaderFile.open(mVertexPath);
fShaderFile.open(mFragmentPath);
stringstream vShaderStream,fShaderStream;
vShaderStream <<vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vertexCode =vShaderStream.str();
fragmentCode = fShaderStream.str();
}catch(ifstream::failure e){
cout<< "Error::Shader::file loaded fail";
}
if(vertexCode.empty()|| fragmentCode.empty()){
return;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode =fragmentCode.c_str();
GLuint vertexShader;
//創(chuàng)建一個著色器類型
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//把代碼復(fù)制進著色器中
glShaderSource(vertexShader, 1, &vShaderCode, NULL);
//編譯頂點著色器
glCompileShader(vertexShader);
//判斷是否編譯成功
if(!checkComplieErrors(vertexShader, "VERTEX")){
return;
}
///下一個階段是片段著色器
GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fShaderCode, NULL);
glCompileShader(fragmentShader);
if(!checkComplieErrors(fragmentShader, "Fragment")){
return;
}
//鏈接荷憋,創(chuàng)建一個程序
ID = glCreateProgram();
//鏈接
glAttachShader(ID, vertexShader);
glAttachShader(ID, fragmentShader);
glLinkProgram(ID);
if(!checkComplieErrors(ID, "PROGRAM")){
return;
}
//編譯好了之后台颠,刪除著色器
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
isSuccess = true;
}
我們這邊繼續(xù)按照原來的編譯著色器程序的流程。每個著色器的都需要經(jīng)歷三個步驟勒庄,創(chuàng)建串前,代碼拷貝,編譯的順序实蔽。最后把所有的著色器都綁定到著色器程序上荡碾,并且鏈接起來。完成之后局装,需要刪除著色器坛吁。
異常警報提示如下:
bool Shader::checkComplieErrors(unsigned int shader, std::string type){
int success;
char infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
isSuccess = false;
return false;
}
}else{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
isSuccess = false;
return false;
}
}
return true;
}
這樣就能完成了Shader的基礎(chǔ)編譯功能。
最后再提供一個use的方法铐尚,使用里面的著色器程序
void Shader:: use(){
glUseProgram(ID);
}
通過這種方式拨脉,我們就能把冗余的代碼簡化到如下這種情況:
Shader *shader = new Shader(vPath,fPath);
shader->compile();
if(shader&&shader->isCompileSuccess()){
loop(window,VAO,VBO,shader);
}
GLSL的介紹
有了上面的工具之后,我們就能把注意力集中到編寫GLSL的語言中宣增。讓我們介紹一下GLSL女坑。
GLSL基礎(chǔ)類型
先介紹GLSL的支持的基礎(chǔ)類型
能看到除了支持我們常用的幾種基本類型,OpenGL的GLSL語言本身就支持向量和矩陣统舀。GPU這種設(shè)計比起CPU來說優(yōu)勢太大了匆骗。因為在圖形學(xué)中矩陣可以說是極其基本的操作單位。
在這里面誉简,我們能夠看到一些OpenGL的設(shè)計碉就。vec(n)是指向量,向量中的是什么類型的數(shù)據(jù)通過前綴i/u/b/d來確定闷串,默認是float類型瓮钥;向量中的n是指這個向量是的分量是多少。
同理在矩陣也是一樣mat(m)(n)烹吵,前綴確定了矩陣中的數(shù)據(jù)是什么類型碉熄,而M*N是指這是一個怎么樣的行列矩陣。
基本類型和C中用法差不多肋拔,這里說一下在GLSL中矩陣的操作:
vec3 v = vec3(0.0,2.0,3.0);
ivec3 s = ivec3(v);
vec4 color;
vec3 rgb = vec3(color);//這樣就能截斷前三個數(shù)據(jù)
這里有這么一個規(guī)律锈津,當(dāng)分量更多的向量賦值給更加少的向量賦值的時候,其行為就和我們寫c語言時候,int往char轉(zhuǎn)化一樣,丟失位數(shù)娜庇。通過這種做法能夠獲取截斷的數(shù)據(jù)蜻拨。
向量的賦值也很自由:
vec3 white = vec3(1.0);//white = (1.0,1.0,1.0)
vec4 t = vec3(white,0.5);//(1.0,1.0,1.0,0.5)
能夠任意的傳入向量到另一個向量,從而做到賦值或者添加新的變量希俩。當(dāng)傳入單一值的時候档押,就會默認讓向量中所有的值都是一致逞力。
矩陣的設(shè)置:
//相當(dāng)于為對角賦值4.0
m = mat3(4.0) = (4.0,0,0
0,4.0,0
0,0,4.0)
mat3 M = mat3(1.0,2.0,3.0,
4.0,5.0,6.0,
7.0,8.0,9.0);
我們設(shè)置矩陣的時候可以設(shè)置單一的值煌往,這樣就能設(shè)置矩陣的一條對角線上所有的值倾哺。
vec3 c1 = vec3(1.0,2.0,3.0);
vec3 c2 = vec3(4.0,5.0,6.0);
vec3 c3 = vec3(7.0,8.0,9.0);
mat3 M = mat3(c1,c2,c3);
vec2 c1 = vec2(1.0,2.0);
vec2 c2 = vec2(4.0,5.0);
vec2 c3 = vec2(7.0,8.0);
mat3 M = mat3(c1,3.0,
c2,6.0,
c3,9.0);
先設(shè)置列,在設(shè)置行
這些結(jié)果都是矩陣:(1.0 4.0 7.0
2.0 5.0 8.0
3.0 4.0 9.0)
OpenGL中還能通過幾個向量組成一個mat矩陣刽脖。不過羞海,其設(shè)置的順序是按照列的順序豎直的向后擺放。也就是按照順序設(shè)置行列式中的列曾棕。
當(dāng)然扣猫,向量的分量或者矩陣菜循,都可以看成一段特殊數(shù)組翘地,可以通過[]和.訪問到每個分量。
實際上為了便于分辨每個向量的作用癌幕,因此將每個每個位置設(shè)置為:
vec3 l = color.rrr
這樣相當(dāng)于取了3次r位置
color = color.abgr//反轉(zhuǎn)每個color位置
但是 vec2 pos;
float zPos = pos.z;//2d不存在z
還能如此:
mat4 m = mat4(2.0);
vec4 = z = m[2]; //這樣就能獲得4*4矩陣的第二列
float y = m[1][1].這樣能獲得第一列第一行的數(shù)據(jù)衙耕。
能夠看到的是,這種方式其實和Octave和Matlab十分相似勺远。
當(dāng)然還能構(gòu)造結(jié)構(gòu)體,并且用“.”訪問里面的屬性
struct pos{
vec3 pos;
vec3 v;
float life;
};
pos p = pos(pos,v,10.0);
數(shù)組
float c[3];//3個float數(shù)組
float[3] c;
int i[];//為定義數(shù)組維度橙喘,可以稍后定義。
for(int i = 0;i < c.length;i++){
c[i] *= 2.0;
}
同理胶逢,我們可以把矩陣看成n*n維度的數(shù)組
因此:
mat3x4 m;
int c = m.length();
int r = m[0].length();//獲取第一列的長度
mat4 m;
float d[m.length()];//設(shè)置長度為矩陣大小厅瞎。
float x[gl_in.length()].設(shè)置數(shù)組大小為頂點數(shù)組大小
實際上數(shù)組還是和Java語言中的差不多。
存儲限制符
存儲限制符是十分重要的一個概念初坠,其控制了變量的作用和簸。其中uniform尤為的重要。
const 讓一個變量變成只讀碟刺。如果初始化是編譯時常量锁保,那么本身就是編譯時常量。
in 設(shè)置這個變量為著色器階段的輸入變量
out 設(shè)置這個變量為著色器階段輸出變量
uniform 設(shè)置這個變量為用戶應(yīng)用傳遞給著色器數(shù)據(jù)半沽,他對于給定的圖元而言爽柒,是一個常量。uniform變量在所有可用的著色階段之間共享者填,必須定義為全局變量浩村。任何變量的變量都可以是uniform變量。著色器無法寫入占哟,也無法改變穴亏。
uniform vrc4 color;
在著色器中蜂挪,可以根據(jù)名字color來引用這個變量,但如果需要在用戶應(yīng)用中設(shè)置他的值嗓化,需要一些工作棠涮。
GLSL編譯器會在鏈接著色器程序時創(chuàng)建一個uniform變量表。如果需要設(shè)置應(yīng)用程序中的color值刺覆,首先獲得color在列表中的索引严肪,通過下面這個這個函數(shù)完成。
GLuint glGetuniformLocation(GLuint program,const char* name)
返回著色器程序中uniform變量name對應(yīng)的索引值谦屑。name是一個以NULL結(jié)尾的字符串驳糯,不存在空格。如果name與啟用的著色器程序所有的uniform都不相符或者name是一個內(nèi)部保留的變色齊變量名稱氢橙,則返回-1.
name 可以是單一變量名稱酝枢,數(shù)組一個元素,或者結(jié)構(gòu)體中一個域變量悍手。
對于uniform變量數(shù)組帘睦,也可以只通過制定數(shù)組的名稱來獲取數(shù)組的第一個元素。
除非重新鏈接程序坦康,不然這里的返回值不會變竣付。
得到uniform變量對應(yīng)索引值之后,我們可以通過glUnform()或者glUniformMatrix()系列函數(shù)來設(shè)置uniform變量值滞欠。
例子:
//GLSL中:
float time;
//opengl編程中:
GLint timeLoc;
GLfloat timevalue;
int timeLoc = glGetUniformLocation(program,"time");
glUniform(timeLoc,timevalue);
獲取以及寫入 uniform
void glUniform{1234}{fdi ui}(GLint location,TYPE value);
void glUniform{1234}{fdi ui}v (GLint location,GLsizei count,TYPE value);
void glUniformMatrix{234}{fdi ui}v (GLint location,GLsizei count,GLboolean transpose,GLfloat *value);
void glUniformMatrix{2x2,2x4,3x2,3x4,4x2,4x3}{fdi ui}v (GLint location,GLsizei count,GLboolean transpose,GLfloat *value);
設(shè)置與location索引位置對應(yīng)的uniform變量的值古胆。其中向量形式的函數(shù)會載入count個數(shù)據(jù)的集合,并寫入location位置的uniform變量筛璧。如果location是數(shù)組的起始索引逸绎,那么數(shù)組之后的連續(xù)count會被載入。
GLfloat形式的函數(shù)夭谤,可以載入但精度或者雙精度的棺牧。
tranpose設(shè)置為GL_TRUE,那么values中的數(shù)據(jù)是以行順序讀入沮翔,否則按照行陨帆。
buffer 和uniform很相似,設(shè)置應(yīng)用共享一塊可讀寫的內(nèi)存采蚀,成為著色器的存儲緩存
shared 設(shè)置變量時本地工作組中共享疲牵,只能用于計算著色器。
可以看到uniform和buffer都是OpenGL中GLSL語言和我們在CPU編程的程序傳遞數(shù)據(jù)的接口榆鼠,十分重要纲爸。
控制流
和普遍的語言一模一樣
但是多了discard終止著色器程序執(zhí)行。
參數(shù)限制符
盡管GLSL中函數(shù)可以在運行之后修改和返回數(shù)據(jù)妆够。但是它與C中不一樣识啦,沒有引用和指針负蚊。不過與之對應(yīng),此時函數(shù)參數(shù)可以制定一個參數(shù)限定符號颓哮,來表明它是否需要在函數(shù)運行時將數(shù)據(jù)拷貝到函數(shù)家妆,或者從函數(shù)中返回修改的數(shù)據(jù)。
如果我們寫出到一個沒有設(shè)置上述修飾符的變量冕茅,會產(chǎn)生編譯時錯誤伤极。
如果我們需要在編譯時驗證函數(shù)是否修改了某個輸入變量,可以使用const in類型變量來組織函數(shù)對變量進行寫操作姨伤。不這么做哨坪,那么在函數(shù)中寫入一個in類型的變量,相當(dāng)于變量的局部拷貝進行了修改乍楚,因此只在函數(shù)自身范圍內(nèi)產(chǎn)生作用当编。
計算不變性
很有趣的是,glsl無法保證在不同著色器中徒溪,兩個完全相同的計算世會得到完全一樣的結(jié)果忿偷。這個情形與cpu端應(yīng)用進行計算問題相同,即不同優(yōu)化的方式會導(dǎo)致結(jié)果非常細微的差異词渤。
為了確定不變性有兩個關(guān)鍵字:
- invariant
- precise
這兩個方法都需要在圖形設(shè)備上完成計算過程牵舱,來確保同一表達式的結(jié)果可以保證重復(fù)性串绩。但是缺虐,對于宿主計算機和圖形硬件格子計算,這兩個方法無法保證結(jié)果完全一致礁凡。
著色器編譯時的常量表達式是由編譯器的宿主計算機計算的高氮,因此我們無法保證宿主計算機計算的結(jié)果與圖形硬件的結(jié)果完全相同。
uniform float ten;//假設(shè)應(yīng)用程序設(shè)置這個值為10.0
const float f = sin(10.0);//宿主編譯器負責(zé)計算
float g = sin(ten); //圖形硬件負責(zé)計算
void main(){
if(g == f){
//這兩個不一定相等
}
}
invariant
invariant限制符可以設(shè)置任何著色器的輸出變量顷牌。他可以確保兩個著色器的輸出變量使用了同樣的表達式剪芍,并且表達式變量也是相同,計算結(jié)果也是相同窟蓝。
invariant gl_Position;
invariant centroid out vec3 Color;
輸出變量作用是將一個著色器的數(shù)據(jù)從一個階段傳遞到下一個罪裹。可以在著色器用到某個變量或者內(nèi)置變量之前的任何位置运挫,對該變量設(shè)置關(guān)鍵字invariant状共。
在調(diào)試過程中可能需要全部變量編程invariant
#pragma STDGL invariant(all)
但是這樣對于著色器也會有所影響。
precise
precise 限制符可以設(shè)置任何計算中的變量或者函數(shù)的返回值谁帕。不是增加精確度峡继,而是增加計算的可復(fù)用。
我們通常在細分著色器用它避免幾何形狀的裂縫匈挖。
總體來說碾牌,如果必須保證某個表達式產(chǎn)生結(jié)果是一致的康愤,即使表達式中的數(shù)據(jù)發(fā)生了變化也是如此,那么此時我們此時應(yīng)該用precise而非invariant舶吗。
下面a和b的值發(fā)生交換征冷,得到的結(jié)果也是不變。
此外即使c和d的值發(fā)生變換誓琼,或者a和c同時與b和d發(fā)生交換资盅,也要計算相同結(jié)果。
Location = a * b + c *d;
precise可以設(shè)置內(nèi)置變量踊赠,用戶變量或者函數(shù)的返回值呵扛。
precise gl_Position;
precise out vec3 Location;
precise vec3 subdivide(vec3 p1,vec3 p2){...}
著色器,關(guān)鍵字precise可以使用某個變量的任何位置設(shè)置個變量筐带,并且可以修改已經(jīng)聲明過的變量今穿。
編譯器使用precise 一個實際影響,類似上面的表達是不能在使用兩種不同的乘法命令同時參與計算伦籍。
例如第一次是普通乘法蓝晒,第二次相乘使用混合乘加預(yù)算,因為這兩個命令對于同一組值的計算可能可能出現(xiàn)微笑差異帖鸦,而這種差異precise是不允許芝薇,因此編譯會組織你在代碼這么做。
但是混乘對性能提升很重要作儿,因此GLSL提供了一個內(nèi)置函數(shù)fma()代替原來的操作洛二。
小結(jié)
可能這樣還是很抽象,為什么GPU需要計算不變性而CPU不需要呢攻锰?這里有個神圖晾嘶,一看就懂。
其中Control是控制器娶吞、ALU算術(shù)邏輯單元垒迂、Cache是cpu內(nèi)部緩存、DRAM就是內(nèi)存妒蛇。
能看到GPU有許許多多的小的計算單元机断。而CPU則是有一個很大的強大計算模塊,而GPU則是有很多不是那么強大的計算單元绣夺。
舉個例子吏奸,CPU的計算單元相當(dāng)于是一個大學(xué)教授在計算,而GPU則是成千上百和小學(xué)生在計算乐导。由于計算機大部分都是由簡單的計算邏輯組成苦丁,一個強大的大腦當(dāng)然架不住很多簡單大腦共同計算的速度快,因此GPU作為大量的并行計算工具是首選物臂。這也是為什么在人工智能旺拉,大數(shù)據(jù)選擇GPU作為計算工具也是這個原因产上。
話題又轉(zhuǎn)回來。由于GPU本身含有大量的計算單元蛾狗,這樣就造成了晋涣,計算可能會因為每個計算單元的情況而出現(xiàn)微小的差異。因此GLSL需要計算不變性沉桌。
invariant 保證了每個階段著色器的輸出在使用相同的表達式谢鹊,結(jié)果一致。
precise 保證了在著色器編程內(nèi)部使用相同的表達式留凭,結(jié)果一致佃扼。
實戰(zhàn)演練 Uniform數(shù)據(jù)接口
介紹到這里,一個粗略的GLSL的語言總覽已經(jīng)有了蔼夜。接下來就讓我們實戰(zhàn)演練一番兼耀。
我們在上一篇文章基礎(chǔ)上,讓整個Uniform增加隨著時間的顏色變化而變化求冷。
我們先編寫一個頂點著色器的GLSL:
文件:veritex.glsl
#version 330 core
layout(location = 0) in vec3 aPos;
void main(){
gl_Position = vec4(aPos,1.0);
}
這樣我就把頂點著色的輸出設(shè)置為分量為4的向量瘤运,在原來的方位的向量上擴展為一個w分量(暫時不用理)。
文件:fragment.glsl
#version 330 core
out vec4 mFragColor;
uniform vec4 mChangingColor;
void main(){
mFragColor = mChangingColor;
}
在片元著色器上匠题,渲染的顏色由片元著色器決定拯坟。
完成這兩個之后,我們只需要編寫如下代碼韭山,編譯著色器程序,利用C++ 11特性回調(diào)Loop中的事件調(diào)用郁季。
const char* vPath = "/Users/yjy/Desktop/iOS workspcae/first_opengl/glsl/veritex.glsl";
const char* fPath = "/Users/yjy/Desktop/iOS workspcae/first_opengl/glsl/fragment.glsl";
Shader *shader = new Shader(vPath,fPath);
shader->compile();
if(shader&&shader->isCompileSuccess()){
mixColorTri(VAO,VBO);
loop(window,VAO,VBO,shader,[](Shader *shader){
//更新uniform顏色
float timeValue = glfwGetTime();
float colorValue = sin(timeValue) / 2.0 + 0.5f;
//拿到uniform的位置索引
int vertexColorLocation = glGetUniformLocation(shader->ID,"mChangingColor");
glUniform4f(vertexColorLocation, colorValue, 0.0f, 0.0f, 1.0f);
});
}
flushTriangleToOpengl這個方法就是和上一篇文章創(chuàng)造并綁定頂點緩存對象和頂點數(shù)組對象,最后設(shè)定步長掠哥。
void flushTriangleToOpengl(GLuint& VAO,GLuint& VBO){
//我們要繪制三角形
float vertices[] = {
//位置
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
//生成分配VAO
glGenVertexArrays(1,&VAO);
//生成一個VBO緩存對象
glGenBuffers(1, &VBO);
//綁定VAO巩踏,注意在core模式秃诵,沒有綁定VAO续搀,opengl拒絕繪制任何東西
glBindVertexArray(VAO);
//綁定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//復(fù)制頂點給opengl緩存
//類型為GL_ARRAY_BUFFER 第二第三參數(shù)說明要放入緩存的多少,GL_STATIC_DRAW當(dāng)畫面不懂的時候推薦使用
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//設(shè)定頂點屬性指針
//第一個參數(shù)指定我們要配置頂點屬性菠净,對應(yīng)vertex glsl中l(wèi)ocation 確定位置
//第二參數(shù)頂點大小禁舷,頂點屬性是一個vec3,由3個值組成毅往,大小是3
//第三參數(shù)指定數(shù)據(jù)類型牵咙,都是float(glsl中vec*都是float)
//第四個參數(shù):是否被歸一化
//第五參數(shù):步長,告訴我們連續(xù)的頂點屬性組之間的間隔攀唯,這里是每一段都是3個float洁桌,所以是3*float
//最后一個是偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
}
最后,我們利用C++ 11特性創(chuàng)建一個回調(diào)把,在創(chuàng)建一個事件循環(huán):
void loop(GLFWwindow *window,const GLuint VAO,const GLuint VBO,Shader *shader,function<void(Shader*)> handle){
while(!glfwWindowShouldClose(window)){
processInput(window);
//交換顏色緩沖侯嘀,他是一個存儲著GLFW窗口每一個像素顏色值的大緩沖
//會在這個迭代中用來繪制另凌,并且顯示在屏幕上
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//我們已經(jīng)告訴谱轨,程序要數(shù)據(jù)什么數(shù)據(jù),以及怎么解釋整個數(shù)據(jù)
//數(shù)據(jù)傳輸之后運行程序
//glUseProgram(shaderProgram);
shader->use();
if(handle){
handle(shader);
}
//綁定數(shù)據(jù)
glBindVertexArray(VAO);
//繪制一個三角形
//從0開始吠谢,3個
glDrawArrays(GL_TRIANGLES, 0, 3);
//繪制矩形
//glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
glBindVertexArray(0);
glfwSwapBuffers(window);
//檢查有沒有觸發(fā)事件土童,鍵盤輸入,鼠標移動工坊,更新噶 u 那個口耦
glfwPollEvents();
}
glDeleteVertexArrays(1,&VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
}
其核心就是這里献汗,通過glGetUniformLocation獲取uniform的索引,接著通過glUniform4f方法設(shè)置一個4個分量的顏色向量到片元著色器中著色王污。
這樣就能做到罢吃,讓整個三角形顏色隨著時間動起來,從淺紅一直變成深紅昭齐。
實戰(zhàn)演練 GLSL頂點著色器layout的妙用
實際上在頂點著色器中有這么一行:
layout(location = 0) in vec3 aPos;
layout中有l(wèi)ocation的字段刃麸。接下來,來看看這個字段是怎么運作司浪。
還記得泊业,glVertexAttribPointer這個方法指定了OpenGL如何解析讀取頂點數(shù)據(jù)的方法。
這個方法的第一個參數(shù)實際上就是指定的是啊易,將會寫入到哪一個location的對應(yīng)的in數(shù)據(jù)吁伺。
假設(shè)我們有這么一堆頂點數(shù)據(jù):
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
};
我們想要設(shè)置前3個是位置信息,而后三個是顏色信息的坐標參數(shù)租谈,又是該如何解析呢篮奄?
void mixColorTri(GLuint& VAO,GLuint& VBO){
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
};
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
//綁定
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
//綁定數(shù)據(jù)
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
//告訴opengl 每6個頂點往后讀取下一個數(shù)據(jù)
//這個0代表了 layout(location = 0)
//最后一個參數(shù)是偏移量
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),0);
glEnableVertexAttribArray(0);
//走3個float的偏移量,開始讀取數(shù)據(jù)
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
}
那么對應(yīng)的頂點著色器呢割去?
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
out vec3 ourColor;
void main(){
gl_Position = vec4(aPos,1.0);
ourColor = aColor;
}
片元著色器:
#version 330 core
out vec4 FragmentColor;
in vec3 ourColor;
void main(){
FragmentColor = vec4(ourColor,1.0);
}
記住每個著色器傳遞數(shù)據(jù)out和in之間的變量命名要一致窟却,不然會出現(xiàn)著色器程序鏈接異常。
實戰(zhàn)演練 移動三角形
當(dāng)我們變化顏色呻逆,處理更多的頂點信息了夸赫,就會想到怎么把這個三角形動起來。
編寫一個頂點著色器
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
uniform float offset;
out vec3 ourColor;
void main(){
gl_Position = vec4(aPos.x + offset,aPos.y,aPos.z,1.0);
ourColor = aColor;
}
接著為了方便我們處理uniform的數(shù)據(jù)設(shè)置咖城,我們在Shader類新增下面方法:
void Shader::setBool(const std::string &name,bool value) const{
glUniform1i(glGetUniformLocation(ID,name.c_str()),(int)value);
}
void Shader:: setInt(const std::string &name,int value) const{
glUniform1i(glGetUniformLocation(ID,name.c_str()),value);
}
void Shader:: setFloat(const std::string &name,float value) const{
glUniform1f(glGetUniformLocation(ID,name.c_str()),value);
}
接著把這行改造成如下:
if(shader&&shader->isCompileSuccess()){
mixColorTri(VAO,VBO);
static float init = 0.0f;
loop(window,VAO,VBO,shader,[](Shader *shader){
//更新uniform顏色
init += 0.005;
shader->setFloat("offset", init);
});
}
就能看到這個三角形快速的移動茬腿。
總結(jié)
經(jīng)過這幾個實戰(zhàn)演練,是不是對OpenGL的GLSL的理解宜雀,uniform以及對頂點數(shù)組對象又有了更加深刻的理解呢切平?
實際上GLSL語言還有不少特性,如buffer辐董,子程序悴品,獨立的著色器程序等高級應(yīng)用還沒涉及到。看來革命尚未成功苔严,繼續(xù)努力才是