前言
- 因為在書上和網上看到說寫Blog各種好, 所以就來嘗試一下,雖然不會有人看,但是沒關系畢竟可以鞏固和更加深入的了解.
- 我公司所處的行業是安防監控, 所以剛好有機會接觸音視頻相關的內容.雖然我是個菜鳥, 但是菜鳥也會有春天的O(∩_∩)O哈哈~.
- 在這里跟大家一起學習OpenGLES,一起進步.
- 本系列不會講太多的理論知識, 會講解盡量多的用法, 畢竟了解更多的用法在實際使用中可以產生更多想法.
最終效果
實現流程
-
1.設置上下文
-
2.設置Layer
-
3.設置渲染緩沖區
-
4.設置幀緩沖區
-
5.創建頂點著色器和片段著色器
-
6.編譯著色器
-
7.鏈接著色器
-
8.聲明矩形四個角對應的數據
-
9.開始渲染
-
10.釋放資源
自定義的OpenGLView有點小復雜, 實現過程比系統提供的GLKView復雜了一丟丟.但是可以更深入的了解OpenGLES實現過程
實現過程
1.設置上下文
/**
* 設置上下文
*/
- (void)setupContext {
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
2.設置Layer
+(Class)layerClass {
return [CAEAGLLayer class];
}
- (void)setupLayer {
_eaglLayer = (CAEAGLLayer*)self.layer;
_eaglLayer.opaque = YES;
}
3.設置渲染緩沖區
/**
* 創建一個渲染緩沖
*/
- (void)setupRenderBuffer {
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
4.設置幀緩沖區
/**
* 設置幀緩沖區
*/
- (void)setupFrameBuffer {
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
}
5.創建頂點著色器和片段著色器
(1)創建頂點著色器RectangleVertex.glsl
attribute vec4 Position;
attribute vec4 InColor;
varying vec4 OutColor;
void main(void){
OutColor = InColor;
gl_Position = Position;
}
/*
vec2.vec3.vec4.表示對應的234階矩陣
attribute修飾符表示從"應用程序"傳過來的數據(也就是下面這些數據傳過來)
//4個頂點(分別表示xyz軸)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4個點的顏色(分別表示RGBA值)
static const float Colors[] = {
1,0,0,1,
0,1,0,1,
0,0,1,1,
0,0,0,1,
};
varying修飾符是用于和片段著色器通訊的接口
*/
(2)創建片段著色器RectangleFragment.glsl
varying lowp vec4 OutColor;
void main(void){
gl_FragColor = OutColor;
}
上面的這些修飾符可以在這里看.
6.編譯著色器
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
// 獲取資源路徑
NSString *shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath
encoding:NSUTF8StringEncoding
error:&error];
if (!shaderString) {
NSLog(@"Error loading shader: %@", error.localizedDescription);
exit(1);
}
/*
* GL_FRAGMENT_SHADER 創建一個片段著色器
* GL_VERTEX_SHADER 創建一個頂點著色器
*/
GLuint shaderHandle = glCreateShader(shaderType);
// 轉換成char*類型(給予OpenGL著色器)
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
/**
* @param shader 所產生的著色器名稱
* @param count 表示多少資源傳遞一次(如果只上傳一個著色代碼,這里必須填1)
* @param string C語言的資源路徑
* @param length C語言資源路徑的字符長度
*/
glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
// 調用運行時編譯的著色器
glCompileShader(shaderHandle);
// 查看是否有錯誤,有的話獲取錯誤信息
GLint compileSuccess;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages[256];
glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
glDeleteShader(shaderHandle);
exit(1);
}
return shaderHandle;
}
7.鏈接著色器
/**
* 編譯著色器
*/
- (void)compileShaders {
//編譯頂點著色器和片段著色器
GLuint vertexShader = [self compileShader:@"RectangleVertex" withType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShader:@"RectangleFragment" withType:GL_FRAGMENT_SHADER];
//把頂點和片段著色器鏈接到一個完整的程序
_program = glCreateProgram();
glAttachShader(_program, vertexShader);
glAttachShader(_program, fragmentShader);
//連接程序
glLinkProgram(_program);
//檢查是否有錯誤, 有的話獲取錯誤信息
if(![self validateProgram:_program]) {
glDeleteProgram(_program);
exit(1);
}
//告訴OpenGL使用該程序
glUseProgram(_program);
//這里是獲取剛才著色器里面的變量并使用
_positionSlot = glGetAttribLocation(_program, "Position");
_colorSlot = glGetAttribLocation(_program, "InColor");
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_colorSlot);
}
//驗證鏈接程序是否有效
- (BOOL)validateProgram:(GLuint)prog {
GLint linkSuccess;
glGetProgramiv(prog, GL_LINK_STATUS, &linkSuccess);
if(linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(prog, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
return NO;
}
return YES;
}
8.聲明矩形四個角對應的數據
//4個頂點(分別表示xyz軸)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4個點的顏色(分別表示RGBA值)
static const float Colors[] = {
1,0,0,1,
0,1,0,1,
0,0,1,1,
0,0,0,1,
};
9.開始渲染
- (void)render:(CADisplayLink *)displayLink {
//用指定的顏色清除,清除顏色被設置為(0.5f, 0.5f, 0.5f, 1.0f), 所以為黑色
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//設定窗口的范圍(如果不是很明白, 可以自己動手修改下試試)
//他這個是左下角為(0,0) 右上角為(width,height)
glViewport(0, 0, _width, _height);
//指定了渲染時索引值為 index 的頂點屬性數組的數據格式和位置。
/*
* indx:指定要修改的頂點屬性的索引值
* size:指定每個頂點屬性的組件數量。(必須坐標xyz軸就是3, 顏色rgba就是4)
* type:指定數組中每個組件的數據類型。(一般為GL_FLOAT)
* normalized:一般為GL_FALSE
* stride:指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
* ptr:指向數據的指針
*/
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 0, Colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(Vertices) / (sizeof(int) * 3));
//把緩沖區的數據呈現到UIView上
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
glDrawArrays的第一個屬性有如下圖三種繪制方式.
我們例子中用到的是GL_TRIANGLE_STRIP
順序是(左下右下左上右上).
當然我們也可以用GL_TRIANGLE_FAN
,但是順序需要修改為(左下右下右上左上)這種方式,
第一種GL_TRIANGLES
表示沒玩過~(> _ <)~. 原文鏈接
10.釋放資源
- (void)dealloc {
//刪除綁定的渲染緩沖區
if(_renderBuffer) {
glDeleteRenderbuffers(GL_RENDERBUFFER, &_renderBuffer);
}
//刪除綁定的幀緩沖區
if(_frameBuffer) {
glDeleteFramebuffers(GL_FRAMEBUFFER, &_frameBuffer);
}
//釋放著色器
if(_vertexShader) {
//刪除頂點著色器連接
glDetachShader(_program, _vertexShader);
//刪除頂點著色器
glDeleteShader(_vertexShader);
}
if(_fragmentShader) {
//刪除片段著色器連接
glDetachShader(_program, _fragmentShader);
//刪除片段著色器
glDeleteShader(_fragmentShader);
}
if(_program) {
glDeleteProgram(_program);
}
}