OpenGL學習大致的理解
OpenGL為什么會涉及這么多操作順序跑揉。這是因為,和我們現在使用的C++、JAVA這種面向對象的語言不同。OpenGL中的大多數函數使用了一種基于狀態(tài)的方法团甲,大多數OpenGL對象都需要在使用前把該對象綁定到context上。這里有兩個新名詞——OpenGL對象和Context黍聂。
ContextContext
ContextContext是一個非常抽象的概念躺苦,我們姑且把它理解成一個包含了所有OpenGL狀態(tài)的對象。如果我們把一個Context銷毀了产还,那么OpenGL也不復存在圾另。OpenGL對象我們可以把OpenGL對象理解成一個狀態(tài)的集合,它負責管理它下屬的所有狀態(tài)雕沉。當然,除了狀態(tài)去件,OpenGL對象還會存儲其他數據坡椒。注意。這些狀態(tài)和上述context中的狀態(tài)并不重合尤溜,只有在把一個OpenGL對象綁定到context上時倔叼,OpenGL對象的各種狀態(tài)才會映射到context的狀態(tài)。因此宫莱,這時如果我們改變了context的狀態(tài)丈攒,那么也會影響這個對象,而相反地授霸,依賴這些context狀態(tài)的函數也會使用存儲在這個對象上的數據巡验。因此,OpenGL對象的綁定既可能是為了修改該對象的狀態(tài)(大多數對象需要綁定到context上才可以改變它的狀態(tài))碘耳,也可能是為了讓context渲染時使用它的狀態(tài)显设。
OpenGL就是一個“狀態(tài)機”
那些各種各樣的API調用會改變這些狀態(tài),或者根據這些狀態(tài)進行操作辛辨。但我們要注意的是捕捂,這只是說明了OpenGL是怎樣被定義的,但硬件是否是按狀態(tài)機實現的就是另一回事了斗搞。這不是我們需要擔心的地方指攒。
OpenGL對象包含了下面一些類型:
Buffer Objects,Vertex Array Objects僻焚,Textures允悦,Framebuffer Objects等等。
這些對象都有三個相關的重要函數:
void glGen*(GLsizei n?, GLuint *objects?);? 負責生成一個對象的name溅呢。而name就是這個對象的引用澡屡。
void glDelete*(GLsizei n?, const GLuint *objects?);? 負責銷毀一個對象猿挚。
void glBind*(GLenum target?, GLuint object?);? 將對象綁定到context上。
關于OpenGL對象還有很多內容驶鹉,這里就不講了绩蜻。
我們還要了解一些圖形名詞。
渲染(Rendering):
計算機從模型到創(chuàng)建一張圖像的過程室埋。OpenGL僅僅是其中一個渲染系統(tǒng)办绝。它是一個基于光柵化的系統(tǒng),其他的系統(tǒng)還有光線追蹤(但有時也會用到OpenGL)等姚淆。
模型(Models)或者對象(Objects):
這里兩者的含義是一樣的孕蝉。指從幾何圖元——點、線腌逢、三角形中創(chuàng)建的東西降淮,由頂點指定。Shaders:
這是一類特殊的函數搏讶,是在圖形硬件上執(zhí)行的佳鳖。我們可以理解成,Shader是一些為圖形處理單元(GPU)編譯的小程序媒惕。OpenGL包含了編譯工具來把我們編寫的Shader源代碼編譯成可以在GPU上運行的代碼系吩。
在OpenGL中,我們可以使用四種shader階段妒蔚。
最常見的就是vertex shaders——它們可以處理頂點數據穿挨;以及fragment shaders,它們處理光柵化后生成的fragments肴盏。vertex shaders和fragment shaders是每個OpenGL程序必不可少的部分科盛。
像素(pixel):像素是我們顯示器上的最小可見元素。我們系統(tǒng)中的像素被存儲在一個幀緩存(framebuffer)中叁鉴。幀緩存是一塊由圖形硬件管理的內存空間土涝,用于供給給我們的顯示設備。代碼如下(提示:這里可以粗略地看下中文注釋幌墓,后面會更詳細講述的)有用的程序結構? //? ? #includeusing namespace std;
#include "vgl.h"
#include "LoadShaders.h"
enum VAO_IDs { Triangles, NumVAOs };
enum Buffer_IDs { ArrayBuffer, NumBuffers };
enum Attrib_IDs { vPosition = 0 };
GLuint? VAOs[NumVAOs];
GLuint? Buffers[NumBuffers];
const GLuint NumVertices = 6;
// init
//
// init()函數用于設置我們后面會用到的一些數據.例如頂點信息,紋理等
//
void init(void) {
glGenVertexArrays(NumVAOs, VAOs);
glBindVertexArray(VAOs[Triangles]);
// 我們首先指定了要渲染的兩個三角形的位置信息.
GLfloat? vertices[NumVertices][2] = {
{ -0.90, -0.90 },? // Triangle 1
{? 0.85, -0.90 },
{ -0.90,? 0.85 },
{? 0.90, -0.85 },? // Triangle 2
{? 0.90,? 0.90 },
{ -0.85,? 0.90 }
};
glGenBuffers(NumBuffers, Buffers);
glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices),
vertices, GL_STATIC_DRAW);
// 然后使用了必需的vertex和fragment shaders
ShaderInfo? shaders[] = {
{ GL_VERTEX_SHADER, "triangles.vert" },
{ GL_FRAGMENT_SHADER, "triangles.frag" },
{ GL_NONE, NULL }
};
// LoadShaders()是我們自定義(這里沒有給出)的一個函數,
// 用于簡化為GPU準備shaders的過程,后面會詳細講述
GLuint program = LoadShaders(shaders);
glUseProgram(program);
// 最后這部分我們成為shader plumbing,
// 我們把需要的數據和shader程序中的變量關聯在一起,后面會詳細講述
glVertexAttribPointer(vPosition, 2, GL_FLOAT,
GL_FALSE, 0, BUFFER_OFFSET(0));
glEnableVertexAttribArray(vPosition);
}
//---------------------------------------------------------------------
//
// display
//
// 這個函數是真正進行渲染的地方.它調用OpenGL的函數來請求數據進行渲染.
// 幾乎所有的display函數都會進行下面的三個步驟.
//
void display(void) {
// 1. 調用glClear()清空窗口
glClear(GL_COLOR_BUFFER_BIT);
// 2. 發(fā)起OpenGL調用來請求渲染你的對象
glBindVertexArray(VAOs[Triangles]);
glDrawArrays(GL_TRIANGLES, 0, NumVertices);
// 3. 請求將圖像繪制到窗口
glFlush();
}
// main
//
// main()函數用于創(chuàng)建窗口,調用init()函數,最后進入到事件循環(huán)(event loop).
// 這里仍會看到一些以gl開頭的函數,但和上面的有所不同.
// 這些函數來自第三方庫,以便我們可以在不同的系統(tǒng)中更方便地使用OpenGL.
// 這里我們使用的是GLUT和GLEW.
//
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(512, 512);
glutInitContextVersion(4, 3);
glutInitContextProfile(GLUT_CORE_PROFILE);
glutCreateWindow(argv[0]);
if (glewInit()) {
cerr << "Unable to initialize GLEW ... exiting" << endl; exit(EXIT_FAILURE);
}
init();
glutDisplayFunc(display);
glutMainLoop();
}
Vertex Shader如下:
[plain] view plain copy print?
#version 430 core
layout(location = 0) in vec4 vPosition;
void
main()
{
gl_Position = vPosition;
}
Fragment Shader如下:
[plain] view plain copy print?
#version 430 core
out vec4 fColor;
void
main()
{
fColor = vec4(0.0, 0.0, 1.0, 1.0);
}
OpenGL的語法解釋
從上面可以看出但壮,OpenGL里面的函數長得都有一個特點,都是由“gl”開頭的常侣,然后緊跟一個或多個大寫字母(例如蜡饵,glBindVertexArray())。而且可以告訴胳施,所有的OpenGL函數都長這樣溯祸。在上面的程序里面還有一些函數是“glut”開頭的,這是來自OpenGL實用工具(OpenGL Utility Toolkit)——GLUT。這是一個非常流行的跨平臺工具焦辅,可以用于打開窗口博杖、管理輸入等操作。龍書用的GLUT版本是Freeglut筷登,是原始GLUT的一個變種剃根。GLUT已經不再更新了。前方。狈醉。Sad。惠险。苗傅。同樣,還有一個函數班巩,glewInit()渣慕,它來自GLEW庫捌木。GLUT和GLEW就是龍書所用的兩個庫了黔牵。和OpenGL函數的命名規(guī)范類似鹰椒,在display()函數里見到的GL_COLOR_BUFFER_BIT這樣的常量堪唐,也是OpenGL定義的。它們由GL_開頭核无,實用下劃線來分割字符。它們的定義就是通過OpenGL頭文件(glcorearb.h和glewt.h)里面的#define指令定義的。
OpenGL為了跨平臺還自己定義了一系列數據類型单匣,如GLfloat。而且宝穗,因為OpenGL是一個“C”語言庫户秤,它不使用函數重載來解決不同類型的數據問題,而是使用函數命名規(guī)范來組織不同的函數逮矛。例如鸡号,后面我們會碰到一個函數叫glUniform*(),這個函數有很多形式须鼎,例如鲸伴,glUniform2f()和glUniform3fv。這些函數名字后面的后綴——2f和3fv晋控,提供了函數的參數信息汞窗。例如,2f中的2表示有兩個數據將會傳遞給函數赡译,f表示這兩個參數的類型是GLfloat仲吏。而3fv中最后的v,則是vector的簡寫,表明這三個GLfloat將以vector的形式傳遞給函數裹唆,而不是三個獨立的參數誓斥。一些例子中沒有使用OpenGL定義的數據類型,直接使用了float這樣的變量许帐。這可能會造成在不同平臺上不兼容的問題劳坑。
在三維的世界里,所有的故事都是從頂點開始的舞吭。雖然題目是“詳解第一個程序”泡垃,但目的是為了讓大家理解最基礎的頂點是怎么一步步傳遞到GLSL中的。
傳遞頂點數據:
例如紋理坐標羡鸥、法線等蔑穴,傳遞給GLSL呢?一般人都會想到多維數組惧浴。我們下面把它稱為頂點流(Vertex Stream)存和。(什么?衷旅!你不是這么想的捐腿?!沒關系柿顶,OpenGL是這么想的就好茄袖。。嘁锯。)
我們創(chuàng)建這個頂點流宪祥,然后只需要告訴OpenGL怎樣解讀它就可以了。
為了渲染一個對象家乘,我們必須使用一個shader program蝗羊。而這個program會定義一系列頂點屬性,例如上述Vertex Shader中的vPosition一行仁锯。這些屬性決定了我們需要傳遞哪些頂點數據耀找。每一個屬性對應了一個數組,并且這些數據的維度都必須相等业崖,即是一一對應的關系野芒。
比如我們想要渲染3個頂點,我們會定義下面的數據:
{ {1, 1, 1}, {0, 0, 0}, {0, 0, 1} }
這些頂點的順序是非常重要的双炕,OpenGL將會根據這些順序渲染網格复罐。我們可以直接使用上述這種數據來直接渲染,也可以使用索引(indices)來指定順序雄家,這樣可以重復使用同一個頂點效诅。
例如胀滚,我們使用下面的索引列表:
{2, 1, 0, 2, 1, 2}
那么OpenGL將會渲染6個頂點:
{ {0, 0, 1}, {0, 0, 0}, {1, 1, 1}, {0, 0, 1}, {0, 0, 0}, {0, 0, 1} }
現在,我們還想傳遞一個新的頂點屬性乱投,即每個頂點的紋理坐標咽笼,那么新的紋理數組可能長這樣:
{ {0, 0}, {0.5, 0}, {0, 1} }
注意,紋理數據的維度大小一定要和上面的坐標數組大小一致戚炫,而其他頂點屬性數組的維度也要滿足這個條件剑刑。這是非常容易理解的。
那么双肤,合并后的頂點屬性列表就是:
[{0, 0, 1}, {0, 1}], [{0, 0, 0}, {0.5, 0}], [{1, 1, 1}, {0, 0}], [{0, 0, 1}, {0, 1}], [{0, 0, 0}, {0.5, 0}], [{0, 0, 1}, {0, 1}] }
OpenGL的做法:VAO和VBO
OpenGL使用了VAO來實現上述管理頂點數據的數據作用施掏,以及VBO來存放真正的頂點屬性數據。
VAO(Vertex Array Object)
我們這里遇到了第一種OpenGL對象——VAO(Vertex Array Object)茅糜。前面說到OpenGL對象是狀態(tài)的集合七芭,那么VAO就是所有頂點數據的狀態(tài)集合。它存儲了頂點數據的格式以及頂點數據數據所需的緩存對象的引用蔑赘。前面提過狸驳,OpenGL對象都有三個非常重要的函數,而VAO對應的就是glGenVertexArrays?缩赛、glDeleteVertexArrays和glBindVertexArray?耙箍。
VAO負責管理頂點屬性,而這些頂點屬性從0到GL_MAX_VERTEX_ATTRIBS? - 1被編號酥馍。這些屬性在Vertex Shader里的表現就是類似下面的語句:
layout(location = 0) in vec4 vPosition;
上述頂點屬性vPosition被編號為0辩昆。
每個屬性可以被enable或者disable,被disable的屬性是不會傳遞給shader的旨袒,即便在shader里定義了這些屬性卤材,它們讀出的值也會是一個常量,而非真正的數據峦失。一個新建的VAO的所有屬性訪問都是disable的。而開啟一個屬性是通過下面的函數:
void glEnableVertexAttribArray?(GLuint index?);
與其對應的是glDisableVertexAttribArray? 函數术吗。
而為了使用上述函數來改變VAO的狀態(tài)尉辑,我們首先需要把VAO綁定到當前的context上。
VBO(Vertex Buffer Object)
VBO是一種Buffer Object较屿,即它也是一個OpenGl對象隧魄。
VBO是頂點數組數據真正所在的地方。
為了指定一個屬性數據的格式和來源隘蝎,我們需要告訴OpenGL购啄,編號為0的屬性使用哪個VBO,編號為1的屬性使用哪個VBO等等嘱么。為了實現它狮含,我們可以這么做。
首先,我們要知道几迄,任何VBO都需要先綁定到GL_ARRAY_BUFFER?才可以對它進行操作蔚龙。綁定后,我們可以調用下面的函數之一:
void glVertexAttribPointer?( GLuint index?, GLint size?, GLenum type?,
GLboolean normalized?, GLsizei stride?, const void *offset?);
void glVertexAttribIPointer?( GLuint index?, GLint size?, GLenum type?,
GLsizei stride?, const void *offset? );
void glVertexAttribLPointer?( GLuint index?, GLint size?, GLenum type?,
GLsizei stride?, const void *offset? );
它們的作用大同小異映胁,就是告訴OpenGl木羹,編號為index的屬性使用當前綁定在GL_ARRAY_BUFFER?的VBO。為了更好理解解孙,我們舉例:
glBindBuffer(GL_ARRAY_BUFFER, buf1);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
上面第一行代碼將buf1綁定到了GL_ARRAY_BUFFER?上坑填。第二行意味著,編號為0的屬性將使用buf1的數據弛姜,因為當前綁定到GL_ARRAY_BUFFER?上的是buf1脐瑰。第三行將緩存對象0綁定到了GL_ARRAY_BUFFER?上,這不會對頂點屬性有任何影響娱据,只有glVertexAttribPointer?函數可以影響它們蚪黑!
這個過程就像一個中介人的作用,而中介人就是GL_ARRAY_BUFFER?中剩。我們可以這么想忌穿,glBindBuffer? 設置了一個全局變量,然后glVertexAttribPointer讀取了這個全局變量并把它存儲在VAO中结啼,這個全局變量就是GL_ARRAY_BUFFER掠剑。當調用完glVertexAttribPointer后,頂點屬性已經知道了數據來源就是buf1郊愧,它們之間就會直接聯系朴译,而不需要在通過GL_ARRAY_BUFFER。
1.uniform變量
uniform變量是外部application程序傳遞給(vertex和fragment)shader的變量属铁。因此它是application通過
函數glUniform**()函數賦值的眠寿。在(vertex和fragment)shader程序內部,uniform變量就像是C語言里面
的常量(const )焦蘑,它不能被shader程序修改盯拱。(shader只能用,不能改)
如果uniform變量在vertex和fragment兩者之間聲明方式完全一樣例嘱,則它可以在vertex和fragment共享使用狡逢。
(相當于一個被vertex和fragment shader共享的全局變量)
uniform變量一般用來表示:變換矩陣,材質拼卵,光照參數和顏色等信息奢浑。
以下是例子:
uniform mat4 viewProjMatrix; //投影+視圖矩陣
uniform mat4 viewMatrix;? ? ? ? //視圖矩陣
uniform vec3 lightPosition;? ? //光源位置
2.attribute變量
attribute變量是只能在vertex shader中使用的變量。(它不能在fragment shader中聲明attribute變量腋腮,
也不能被fragment shader中使用)
一般用attribute變量來表示一些頂點的數據雀彼,如:頂點坐標壤蚜,法線,紋理坐標详羡,頂點顏色等仍律。
在application中,一般用函數glBindAttribLocation()來綁定每個attribute變量的位置实柠,然后用函數
glVertexAttribPointer()為每個attribute變量賦值水泉。
以下是例子:
uniform mat4 u_matViewProjection;
attribute vec4 a_position;
attribute vec2 a_texCoord0;
varying vec2 v_texCoord;
void main(void)
{
gl_Position = u_matViewProjection * a_position;
v_texCoord = a_texCoord0;
}
3.varying變量
varying變量是vertex和fragment shader之間做數據傳遞用的。一般vertex shader修改varying變量的值窒盐,
然后fragment shader使用該varying變量的值草则。因此varying變量在vertex和fragment shader二者之間的聲
明必須是一致的。application不能使用此變量蟹漓。
以下是例子:
// Vertex shader
uniform mat4 u_matViewProjection;
attribute vec4 a_position;
attribute vec2 a_texCoord0;
varying vec2 v_texCoord; // Varying in vertex shader
void main(void)
{
gl_Position = u_matViewProjection * a_position;
v_texCoord = a_texCoord0;
}
// Fragment shader
precision mediump float;
varying vec2 v_texCoord; // Varying in fragment shader
uniform sampler2D s_baseMap;
uniform sampler2D s_lightMap;
void main()
{
vec4 baseColor;
vec4 lightColor;
baseColor = texture2D(s_baseMap, v_texCoord);
lightColor = texture2D(s_lightMap, v_texCoord);
gl_FragColor = baseColor * (lightColor + 0.25);
}
buffer的使用總結
1. buffer分為frame buffer和render buffer兩大類炕横,其中frame buffer相當于render buffer的管理者,frame buffer object即稱為FBO葡粒,常用于做離屏渲染緩沖等份殿。render buffer則又可分為三類,color buffer / depth buffer / stencil buffer嗽交。
2. 生成frame buffer object的API函數: glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);生成render buffer的API函數卿嘲,render buffer的生成函數是一樣的,buffer句柄類型只有在進行分配buffer空間的時候才會確定:glGenRenderbuffers(1,&renderbuffer);glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
2. frame buffer僅僅是管理者夫壁,不需要分配空間拾枣;render buffer的存儲空間的分配,對于不同的render buffer盒让,使用不同的API進行分配梅肤,而只有分配空間的時候,
render buffer句柄才確定其類型
(1). 最基本的是color buffer邑茄,
調用EGALContext的OC方法為其分配空間/* Attaches an EAGLDrawable as storage for the OpenGL ES renderbuffer object bound to*/
- (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id)drawable;
(2). 而depth buffer則可以直接調用openGL本身的API進行分配
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT16, width, height);
2. 上面(1)(2)函數是用于生成render buffer的存儲空間姨蝴,生成空間之后,則需要將renderbuffer跟framebuffer進行綁定肺缕,調用glFramebufferRenderbuffer函數進行綁定左医,后面的繪制才能起作用
3. 接下來可以調用OpenGL的函數進行繪制處理,最后則需要調用EGALContext的OC方法進行最終的渲染繪制搓谆,這里渲染的是color buffer,這個方法會講buffer渲染到CALayer上面
- (BOOL)presentRenderbuffer:(NSUInteger)target;
4. 還有一個需要注意的地方是在退出的時候豪墅,需要調用glDelegateFramebuffers或者glDeleteRenderbuffers函數刪除frame
buffer或者render buffer
總結:以上是對學習OpenGL的一些學習過程中整體認識和了解泉手,只是將知識系統(tǒng)化,自己在看代碼學習和練習的時候不會莫名其妙偶器,把知識規(guī)整一下斩萌,能夠更清楚的理解程序