本文記錄了我初學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格式bash
_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要求的渲染格式相符.ide
幀緩存:它是屏幕所顯示畫面的一個直接映象,又稱爲位映射圖(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上的小程序。這些小程序爲圖形渲染管線的某個特定部分而運行。從基本意義上來講,着色器只是一種把輸入轉化爲輸出的程序。着色器也是一種很是獨立的程序,由於它們之間不能相互通訊;它們之間惟一的溝通只有經過輸入和輸出。ui
着色器是使用一種叫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繪製代碼:
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);
複製代碼
如上面的例子所示, 普通的頂點數組的傳輸,須要在繪製的時候頻繁地從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的全名是 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);
複製代碼
索引緩衝對象(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))
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.
glDrawArrays(GL_TRIANGLES, 0, 3);
參數: model 和上面那個含義同樣. first 表示頂點數據起始索引, 從頭開始則爲0. count 表示要傳入頂點數據的數量.
[_context presentRenderbuffer:GL_RENDERBUFFER];