簡介
本文記錄了我初學Opengl 繪製彩色矩形的過程,可能我對內容的描述不夠準確,還請多多指正
實現
配置圖層
+(Class)layerClass{
return [CAEAGLLayer class];
}
複製程式碼
將當前View的Layer替換成 CAEAGLLayer
類,opengl的繪製內容也是在該View上顯示的.
//設定不透明度為YES,因為透明圖層效能不好
self.layer.opaque = YES;
self.layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
複製程式碼
可以對CAEAGLLayer
進行額外屬性的配置:
kEAGLDrawablePropertyRetainedBacking 傳入布林值,表示是否保持繪製狀態,若設定為NO,則下次將重新繪製. kEAGLDrawablePropertyColorFormat 設定layer的顏色緩衝區格式,EAGLContext物件 使用此格式來建立渲染緩衝區的儲存. kEAGLColorFormatRGB565 ---> 16bit RGB格式 kEAGLColorFormatRGBA8 ---> 32-bit RGBA格式
配置上下文
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
return;
}
// 將當前上下文設定為我們建立的上下文
if (![EAGLContext setCurrentContext:_context]) {
return;
}
}
複製程式碼
設定緩衝區(渲染緩衝和幀緩衝)
//在緩衝區中返回n個渲染緩衝物件控制程式碼,不保證這些控制程式碼是連續的整數,但是肯定沒有被使用.
GLuint renderbuffer[1];
glGenRenderbuffers(ARRAY_SIZE(renderbuffer), renderbuffer);
//將緩衝區物件和控制程式碼 繫結到指定的緩衝區目標.
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[0]);
//檢驗是否建立繫結成功
if (glIsRenderbuffer(renderbuffer[0]) == GL_TRUE) {
NSLog(@"成功生成渲染快取");
}
//為緩衝區物件分配儲存空間.
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
//設定幀緩衝區(Frame Buffer),和渲染緩衝區大致相同
GLuint framebuffer[1];
glGenFramebuffers(ARRAY_SIZE(framebuffer), framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer[0]);
if (glIsFramebuffer(framebuffer[0]) == GL_TRUE) {
NSLog(@"成功繫結幀快取");
}
//將相關的buffer依附到 幀快取上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[0]);
//釋放渲染快取
//glDeleteRenderbuffers(ARRAY_SIZE(renderbuffer), renderbuffer);
//釋放幀快取
//glDeleteFramebuffers(ARRAY_SIZE(framebuffer), framebuffer);
複製程式碼
渲染快取: 是OpenGL ES管理的一塊高效記憶體區域,渲染快取的資料只有關聯一個幀快取物件才有意義,並且需要保證影象快取格式 必須與OpenGL ES要求的渲染格式相符.
幀快取:它是螢幕所顯示畫面的一個直接映象,又稱為位對映圖(Bit Map)或光柵。幀快取的每一儲存單元對應螢幕上的一個畫素,整個幀快取對應一幀影象。
函式解釋
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer[0]);
引數:
target: 指定的幀緩衝區目標 必須是 GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER, 或 GL_FRAMEBUFFER. (GL_FRAMEBUFFER = GL_DRAW_FRAMEBUFFER); attachment: 幀快取物件依附的目標 GL_COLOR_ATTACHMENT(0~i) ---> 第i個顏色快取 0為預設值, GL_DEPTH_ATTACHMENT ---> 深度快取, GL_STENCIL_ATTACHMENT ---> 模板快取 renderbuffertarget :必須為 GL_RENDERBUFFER,指定的渲染快取區目標 renderbuffer: 渲染緩衝區物件控制程式碼.
準備著色器原始碼
OpenGL中,任何事物都在3D空間中,而螢幕和視窗卻是2D畫素陣列,這導致OpenGL的大部分工作都是關於把3D座標轉變為適應你螢幕的2D畫素。3D座標轉為2D座標的處理過程是由OpenGL的圖形渲染管線.OpenGL中,任何事物都在3D空間中,而螢幕和視窗卻是2D畫素陣列,這導致OpenGL的大部分工作都是關於把3D座標轉變為適應你螢幕的2D畫素。3D座標轉為2D座標的處理過程是由OpenGL的圖形渲染管線
**著色器(Shader)**是執行在GPU上的小程式。這些小程式為圖形渲染管線的某個特定部分而執行。從基本意義上來說,著色器只是一種把輸入轉化為輸出的程式。著色器也是一種非常獨立的程式,因為它們之間不能相互通訊;它們之間唯一的溝通只有通過輸入和輸出。
著色器是使用一種叫GLSL的類C語言寫成的。GLSL是為圖形計算量身定製的,它包含一些針對向量和矩陣操作的有用特性。
頂點著色器
#version 300 es //OpenGL ES 3.0
//接受的輸入變數
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
//輸出變數
out vec3 outColor;
//相當於C語言的main函式
void main()
{
//繪製圖形
gl_Position = vec4(position[0],position[1],position[2], 1.0);
outColor = color;
}
複製程式碼
圖形渲染管線的第一個部分是頂點著色器(Vertex Shader),它把一個單獨的頂點作為輸入.一個頂點(Vertex)是一個3D座標的資料的集合。而頂點資料是用頂點屬性(Vertex Attribute)表示的,它可以包含任何我們想用的資料.
片段著色器
#version 300 es
precision mediump float; //表示 資料精確度 這裡設定的為中級
in vec3 outColor;
out vec4 FragColor; //輸出的色彩
void main()
{
FragColor = vec4(outColor.x,outColor.y,outColor.z, 1.0);;
}
複製程式碼
片段著色器的主要目的是計算一個畫素的最終顏色,這也是所有OpenGL高階效果產生的地方。通常,片段著色器包含3D場景的資料(比如光照、陰影、光的顏色等等),這些資料可以被用來計算最終畫素的顏色。
在所有對應顏色值確定以後,最終的物件將會被傳到最後一個階段,我們叫做Alpha測試和混合(Blending)階段。這個階段檢測片段的對應的深度(和模板(Stencil))值,用它們來判斷這個畫素是其它物體的前面還是後面,決定是否應該丟棄。這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)並對物體進行混合(Blend)。所以,即使在片段著色器中計算出來了一個畫素輸出的顏色,在渲染多個三角形的時候最後的畫素顏色也可能完全不同。
建立著色器物件
static GLuint createGLShader(const char *shaderText, GLenum shaderType)
{
//建立著色器,將根據傳入的type引數 建立一個新的 頂點或片段著色器,返回值為新的著色器物件控制程式碼
//GL_VERTEX_SHADER(頂點著色器) GL_FRAGMENT_SHADER(片段著色器)
GLuint shader = glCreateShader(shaderType);
//為著色器物件 提供著色器原始碼.
//引數: shader --> 著色器物件控制程式碼
// count --> 著色器源字串數量
// string --> 字串的陣列指標
// length ---> 指向儲存美工著色器字串大小且元素數量為count的整數陣列指標.如果length為NULL 著色器字串將被認定為空.
glShaderSource(shader, 1, &shaderText, NULL);
//呼叫該方法,將指定的著色器原始碼 進行編譯
//引數shader 為著色器控制程式碼
glCompileShader(shader);
//呼叫該方法獲取 著色器原始碼編譯是否成功,並獲取其他相關資訊
//第二個引數 pname 表示要查詢什麼資訊
/*
GL_COMPILE_STATUS ---> 是否編譯成功 成功返回 GL_TRUE
GL_INFO_LOG_LENGTH ---> 查詢原始碼編譯後長度
GL_SHADER_SOURCE_LENGTH ---> 查詢原始碼長度
GL_SHADER_TYPE ---> 查詢著色器型別()
GL_DELETE_STATUS ---> 著色器是否被標記刪除
*/
int compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char *infoLog = (char *)malloc(sizeof(char) * infoLen);
if (infoLog) {
//檢索資訊日誌
//引數: shader 著色器物件控制程式碼
// maxLength 儲存資訊日誌的緩衝區大小
// length 寫入資訊日誌長度 ,不需要知道可傳NULL
// infoLog 儲存日誌資訊的指標
glGetShaderInfoLog (shader, infoLen, NULL, infoLog);
GLlog("Error compiling shader: %s\n", infoLog);
free(infoLog);
}
}
//刪除著色器物件, 引數shader為要刪除的著色器物件的控制程式碼
//若一個著色器連結到一個程式物件,那麼該方法不會立刻刪除著色器,而是將著色器標記為刪除,當著色器不在連線到任何程式物件時,它的記憶體將被釋放.
glDeleteShader(shader);
return 0;
}
return shader;
}
複製程式碼
建立程式物件
// 建立一個程式物件,返回程式物件的控制程式碼
GLuint program = glCreateProgram();
// 得到需要的著色器
GLuint vertShader = createGLShader(vertext, GL_VERTEX_SHADER); //頂點著色器
GLuint fragShader = createGLShader(frag, GL_FRAGMENT_SHADER); //片元著色器
if (vertShader == 0 || fragShader == 0) {
return 0;
}
//將程式物件和 著色器物件連結 //在ES 3.0中,每個程式物件 必須連線一個頂點著色器和片段著色器
//program程式物件控制程式碼 shader著色器控制程式碼
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
//連結程式物件 生成可執行程式(在著色器已完成編譯 且程式物件連線了著色器)
//連結程式會檢查各種物件的數量,和各種條件.
//在連結階段就是生成最終硬體指令的時候(和C語言一樣)
glLinkProgram(program);
//檢查連結是否成功
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
GLint infoLen;
//使用 GL_INFO_LOG_LENGTH 表示獲取資訊日誌
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
GLchar *infoText = (GLchar *)malloc(sizeof(GLchar)*infoLen + 1);
if (infoText) {
memset(infoText, 0x00, sizeof(GLchar)*infoLen + 1);
// 從資訊日誌中獲取資訊
glGetProgramInfoLog(program, infoLen, NULL, infoText);
GLlog("%s", infoText);
free(infoText);
//此函式用於校驗當前的程式物件,校驗結果可通過 glGetProgramiv函式檢查,此函式只用於除錯,因為他很慢.
//glValidateProgram(program);
}
}
glDeleteShader(vertShader);
glDeleteShader(fragShader);
//刪除程式物件
glDeleteProgram(program);
return 0;
}
/*
* 連結完著色器,生成可執行程式. 將著色器斷開刪除
*/
//斷開指定程式物件和片段著色器
glDetachShader(program, vertShader);
glDetachShader(program, fragShader);
//將著色器標記為刪除
glDeleteShader(vertShader);
glDeleteShader(fragShader);
複製程式碼
程式物件就是一個容器物件,將著色器與之連線,最後連結生成最終的可執行程式.
輸入頂點資料
//三角形的三點座標+顏色座標
static GLfloat 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.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f
};
static unsigned int indices[] = {
0,1,3,
1,2,3
};
unsigned int VAO,VBO,EBO;
//建立VAO物件,VBO物件,EBO物件
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//繫結VAO VBO EBO
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
將頂點資料 和 索引資料 複製到緩衝區中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//設定頂點屬性指標 輸入資料
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0);
//啟用 0號變數,為了效能,若不啟用著色器無法接受資料
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
複製程式碼
VAO VBO EBO
不使用VAO VBO繪製程式碼:
static GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
GLint posSlot = glGetAttribLocation(_program, "position");
glVertexAttribPointer(posSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(posSlot);
static GLfloat colors[] = {
0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f
};
GLint colorSlot = glGetAttribLocation(_program, "color");
glVertexAttribPointer(colorSlot, 3, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(colorSlot);
複製程式碼
VBO
如上面的例子所示, 普通的頂點陣列的傳輸,需要在繪製的時候頻繁地從CPU到GPU傳輸頂點資料,這種做法效率低下. 為了加快顯示速度,顯示卡增加了一個擴充套件 VBO (Vertex Buffer object),即頂點快取。它直接在 GPU 中開闢一個快取區域來儲存頂點資料,因為它是用來快取儲頂點資料,因此被稱之為頂點快取。使用頂點快取能夠大大較少了CPU到GPU 之間的資料拷貝開銷,因此顯著地提升了程式執行的效率。
函式
1, 建立頂點快取物件
void glGenBuffers (GLsizei n, GLuint* buffers);
引數 n : 表示需要建立頂點快取物件的個數 引數 buffers :用於儲存建立好的頂點快取物件控制程式碼
2, 將頂點快取物件設定為當前陣列快取物件
void glBindBuffer (GLenum target, GLuint buffer);
target :指定繫結的目標,取值為 GL_ARRAY_BUFFER(用於頂點資料) 或 GL_ELEMENT_ARRAY_BUFFER(用於索引資料) buffer :頂點快取物件控制程式碼
3, 為頂點快取物件分配空間(這裡就是將資料一次性 拷貝至視訊記憶體中)
void glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
target:指定繫結的目標,取值為 GL_ARRAY_BUFFER(用於頂點資料) 或 GL_ELEMENT_ARRAY_BUFFER(用於索引資料). size :指定頂點快取區的大小,以位元組為單位計數; data :用於初始化頂點快取區的資料,可以為 NULL,表示只分配空間,之後再由 glBufferSubData 進行初始化; usage :表示該快取區域將會被如何使用,它的主要目的是用於對該快取區域做何種程度的優化,比如經常修改的資料可能就會放在GPU快取中達到快速操作的目的.
usage:
GL_STATIC_DRAW 表示該快取區不會被修改
GL_DYNAMIC_DRAW 表示該快取區會被週期性更改
GL_STREAM_DRAW 表示該快取區會被頻繁更改
複製程式碼
4,更新頂點緩衝區資料
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
offset: 表示需要更新的資料的起始偏移量; size: 表示需要更新的資料的個數,也是以位元組為計數單位; data: 用於更新的資料;
5,釋放頂點快取
void glDeleteBuffers (GLsizei n, const GLuint* buffers);
n : 表示頂點快取物件的個數 buffers :頂點快取物件控制程式碼
VAO
VAO的全名是 Vertex Array Object。它不用作儲存資料,但它與頂點繪製相關。 它的定位是狀態物件,記錄儲存狀態資訊。VAO記錄的是一次繪製中做需要的資訊,這包括資料在哪裡、資料格式是什麼等資訊。VAO其實可以看成一個容器,可以包括多個VBO。 由於它進一步將VBO容於其中,所以繪製效率將在VBO的基礎上更進一步。目前OpenGL ES3.0及以上才支援頂點陣列物件。
函式
1, 建立頂點陣列物件
glGenVertexArrays (GLsizei n, GLuint* arrays) ;
n : 表示頂點陣列物件的個數 arrays :頂點陣列物件控制程式碼
2, 將頂點陣列物件設定為當前頂點陣列物件
glBindVertexArray (GLuint array) ;
arrays :頂點陣列物件控制程式碼
3,釋放頂點陣列物件
glDeleteVertexArrays (GLsizei n, const GLuint* arrays);
n : 表示頂點陣列物件的個數 arrays :頂點陣列物件控制程式碼
使用
如程式碼中所寫,在繫結VAO後,後續的VBO操作都會儲存到當前繫結的VAO中.這樣就將當前繪製狀態記錄下來了. 當下次還要繪製當前圖形時, 只需再次繫結當前VAO, 進行後面的繪製操作即可.對於OpenGL ES2.0 使用VAO 則需要使用另外提供的API來實現.
GLvoid glGenVertexArraysOES(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArrayOES(GLuint array);
GLvoid glDeleteVertexArraysOES(GLsizei n, const GLuint *arrays);;
GLboolean glIsVertexArrayOES(GLuint array);
複製程式碼
EBO
索引緩衝物件(Element Buffer Object,EBO,也叫Index Buffer Object,IBO),當繪製現有圖形時,存在頂點資料複用時,可以使用EBO.
函式
1,建立EBO(和VBO類似)
unsigned int EBO;
glGenBuffers(1, &EBO);
複製程式碼
2,繫結EBO,將索引複製到緩衝裡
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
複製程式碼
3, 使用glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
替代glDrawArrays
函式補充
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
該函式用於將頂點屬性傳入頂點著色器 引數:
index: 對應頂你個點著色器中變數的location size :表示該頂點屬性對應的分量數量.也就是接收者為幾位向量 如寫入3 則表示為
vec3
接收者為3維向量. 必須是 1~4. type :表明每個分量的型別 可用的符號常量有GL_BYTE
,GL_UNSIGNED_BYTE
,GL_SHORT
,GL_UNSIGNED_SHORT
,GL_FIXED
, 和GL_FLOAT
,初始值為GL_FLOAT
; normalized: 是否對每個分量進行歸一化處理, 也就是若type為float型別. stride:指定連續頂點屬性之間的偏移量,如果設定0,則表示各個分量是緊密排在一起,中間沒有其他多餘資料. ptr 頂點資料指標
此函式在有無VBO的情況下,使用有所差異~,在不適用VBO時,ptr確實是頂點資料指標. 當使用VBO時,頂點資料都已經拷貝至視訊記憶體中,這裡的ptr 就表示為緩衝區資料的便宜量了.
無EBO:
glVertexAttribPointer(colorSlot, 3, GL_FLOAT, GL_FALSE, 0, colors);
有EBO:
static GLfloat 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.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f
};
.......(VBO與其他程式碼).......
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
複製程式碼
在這裡0號屬性和1號屬性緊密相連,且0號和1號的分量數都為3,以0號屬性開頭.
故: 第一個0號和第二個0號 中間有6個間距 stride = 6*sizeof(float).
1號在0號後面, 0號ptr為 (void*)0
. 1號ptr為 (void* )(3*sizeof(float))
繪製
使用 EBO:
glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, 0);
引數:
model:指定呈現那種圖元(將這些點繪製成怎樣的形狀). 可選項:
GL_POINTS(點), GL_LINE_STRIP(多端線), GL_LINE_LOOP(線圈), GL_LINES(線段), GL_TRIANGLE_FAN, (三角形扇) GL_TRIANGLES, (三角形) count: 傳入頂點資料的數量 type: 索引陣列的元素屬性
GL_UNSIGNED_BYTE
,GL_UNSIGNED_SHORT
, orGL_UNSIGNED_INT
. indices: 指向索引陣列的指標, 當使用VBO時,則表示為偏移量,若為緊密相連時則傳入0.
不使用EBO
glDrawArrays(GL_TRIANGLES, 0, 3);
引數: model 和上面那個含義一樣.
first 表示頂點資料起始索引, 從頭開始則為0.
count 表示要傳入頂點資料的數量.
最後顯示
[_context presentRenderbuffer:GL_RENDERBUFFER];