我是搞iOS開發(fā)的缸逃,所以本博客會用到OC的代碼作例子针饥。OpenGL ES的接口是用C寫的,對于一些矩陣的操作還需要第三方式的庫需频,看到這里是不是整個人都不好了的感覺丁眼!我開始學(xué)的時候也是這種感覺。但有一個好消息要告訴你蘋果對OpenGL ES進(jìn)行的部分封裝出了一個GLKit昭殉,你在對矩陣操作的時候不再需要借助第三方也可以實現(xiàn)苞七。下面的講解會使用兩方式,一種是使用GLKit一種是不使用GLKit挪丢。
我也是在學(xué)習(xí)階段蹂风,可能里面會有錯誤如有錯誤還請各位大神指出。本博客不能算是基礎(chǔ)教程里面介紹會少一點乾蓬,如果你一點都不了解OpenGL ES可以先看這個博客然后回來再看我這里的惠啄。
學(xué)習(xí)的代碼都在我的github倉庫歡迎大家學(xué)習(xí)指教!
不使用GLKit的方法
代碼的步驟如下:
- 創(chuàng)建和設(shè)置CAEAGLLayer
- 創(chuàng)建和設(shè)置EAGLContext
- 申請和綁定渲染緩存
- 申請和綁定幀緩存
- 編譯鏈接著色器
- 創(chuàng)建和設(shè)置頂點信息
- 渲染顯示頂點圖形
創(chuàng)建項目
首先創(chuàng)建一個空項目任内,然后導(dǎo)入兩個庫
在Xcode中創(chuàng)建一個空項目撵渡,因為需要用到著色器我們我們要創(chuàng)建2個空文件一個命名為vertexShader.glsl(頂點著色器)一個命名為fragmentShader.glsl(片元著色器)。最后項目如下樣子:
在著色器文件里要用著色器語言來寫代碼死嗦,這種語言叫OpenGL Shading Language簡稱glsl趋距,其語法有點像C,如果你會C就很容易明白代碼的含意不過我會盡量在代碼里添加注釋越走。我們在vertexShader.glsl文件里寫入好下代碼:
attribute vec4 myPosition; // 聲明一個變量
void main()
{
gl_Position = myPosition; // 設(shè)置頂點位置信息
}
在fragmentShader.glsl語言里添加如下代碼:
precision mediump float; // 聲明精度棚品,在頂點著色器里可以省略精度但是片元著色器里不能省略
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 為了簡單這里將顏色設(shè)成固定值
}
在控制器里導(dǎo)入OpenGL ES的頭文件:
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
然后聲明幾個變量
CAEAGLLayer * _myLayer; // 用于顯示的layer
EAGLContext * _myContext; // 用于管理狀態(tài)的context
GLuint _myFrameBuffer; // 幀緩存
GLuint _myRenderBuffer; // 渲染緩存
GLuint _myPrograme; // 用于鏈接著色器的程序
GLuint _myPositionSlot; // 用于向著色器傳遞頂點數(shù)據(jù)的槽
變量的用途上面的注釋已經(jīng)寫清楚了。下面就是完整的代碼了:
- (void)viewDidLoad {
[super viewDidLoad];
/*** 創(chuàng)建顯示的layer ***/
_myLayer = [[CAEAGLLayer alloc] init];
_myLayer.frame = self.view.frame;
_myLayer.opaque = YES; // 設(shè)置為不透明
// 設(shè)置kEAGLDrawablePropertyRetainedBacking為NO表示不維持上一次繪制的內(nèi)容
// kEAGLColorFormatRGBA8表示設(shè)置色彩空間為RGBA8
_myLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@(NO), kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
// 將創(chuàng)建的layer添加到視圖
[self.view.layer addSublayer:_myLayer];
/*** 創(chuàng)建和設(shè)置context ***/
// 創(chuàng)建的context為OpenGL ES 2.0廊敌,因為2.0開始支持可編輯管線且大多數(shù)蘋果設(shè)備都支持
_myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (nil == _myContext) {
NSLog(@"context 創(chuàng)建失敗");
}
// 將創(chuàng)建的context設(shè)置為當(dāng)前context
if (![EAGLContext setCurrentContext:_myContext]) {
NSLog(@"context 設(shè)置失敗");
}
/*** 申請和綁定渲染緩存和幀緩存 ***/
// 為renderbuffer申請一個id
glGenRenderbuffers(1, &_myRenderBuffer);
// 設(shè)置當(dāng)前的renderbuffer為剛申請的
glBindRenderbuffer(GL_RENDERBUFFER, _myRenderBuffer);
// 為renderbuffer分配存儲空間
[_myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_myLayer];
// 為framebuffer申請一個id
glGenFramebuffers(1, &_myFrameBuffer);
// 設(shè)置當(dāng)前的framebuffer為剛申請的
glBindFramebuffer(GL_FRAMEBUFFER, _myFrameBuffer);
// 將renderbuffer關(guān)聯(lián)到framebuffer上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _myRenderBuffer);
/*** 編譯鏈接著色器 ***/
[self setupPrograme];
/*** 設(shè)置頂點信息 ***/
// 創(chuàng)建三角形的頂點
GLfloat vertexes[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 設(shè)置頂點數(shù)據(jù)的指針信息
glVertexAttribPointer(_myPositionSlot, // 傳入對應(yīng)數(shù)據(jù)槽的位置
3, // 一組有多少數(shù)據(jù),即3個一組
GL_FLOAT, // 數(shù)據(jù)類型
GL_FALSE, // 是否是正交視圖
sizeof(GLfloat) * 3, // 數(shù)據(jù)跨度
vertexes // 頂點數(shù)據(jù)
);
// 啟用頂點數(shù)據(jù)
glEnableVertexAttribArray(_myPositionSlot);
/*** 渲染顯示三角形 ***/
// 設(shè)置顯示區(qū)域
glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);
// 設(shè)置清屏顏色
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 繪制三角開
glDrawArrays(GL_TRIANGLES, 0, 3);
// 顯示三角形
[_myContext presentRenderbuffer:GL_RENDERBUFFER];
}
/**
編譯和鏈接程序
*/
- (void)setupPrograme{
GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER withFileName:@"vertexShader.glsl"];
GLuint fragmentShader = [self loadShader:GL_FRAGMENT_SHADER withFileName:@"fragmentShader.glsl"];
// 創(chuàng)建一個程序
_myPrograme = glCreateProgram();
if (!_myPrograme) {
NSLog(@"創(chuàng)建programe失敗");
}
// 添加著色器到程序中并鏈接
glAttachShader(_myPrograme, vertexShader);
glAttachShader(_myPrograme, fragmentShader);
glLinkProgram(_myPrograme);
GLint success = 0;
// 獲取程序信息
glGetProgramiv(_myPrograme, GL_LINK_STATUS, &success);
if (success == GL_FALSE) { // 程序鏈接失敗
GLint infoLen = 0;
// 獲取錯誤信息長度
glGetProgramiv(_myPrograme, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 0) {
// 申請內(nèi)存存放錯誤信息
GLchar * info = malloc(sizeof(GLchar) * infoLen);
glGetProgramInfoLog(_myPrograme, sizeof(GLchar) * infoLen, &infoLen, info);
NSLog(@"%s", info);
}
// 刪除鏈接失敗的程序
glDeleteProgram(_myPrograme);
_myPrograme = 0;
return;
}
// 啟用程序
glUseProgram(_myPrograme);
// 獲取頂點著色器myPosition的內(nèi)存地址
_myPositionSlot = glGetAttribLocation(_myPrograme, "myPosition");
}
/**
創(chuàng)建和編譯著色器
@param type 著色器類型
@param fileName 著色器文件
@return 返回創(chuàng)建好的著色器门怪,創(chuàng)建失敗則返回0
*/
- (GLuint)loadShader:(GLenum)type withFileName:(NSString *)fileName{
// 獲取文件路徑
NSString * path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
NSError * error;
NSString * shaderString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
// 轉(zhuǎn)成C字符串
const GLchar * shaderStringUTF8 = shaderString.UTF8String;
if (error) {
NSLog(@"文件讀取錯誤:%@",error);
}
// 創(chuàng)建著色器程序
GLuint shader = glCreateShader(type);
// 給著色器程序傳遞著色器字符串
glShaderSource(shader, 1, &shaderStringUTF8, NULL);
// 編譯
glCompileShader(shader);
// 查看編譯情況
GLint compiled = 0;
// 獲取著色器信息
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
GLint infoLen = 0;
// 獲取錯誤信息的長度
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 0) {
GLchar * info = malloc(sizeof(GLchar) * infoLen);
glGetShaderInfoLog(shader, // 對應(yīng)的著色器
sizeof(GLchar) * infoLen, // buffer的大小
&infoLen, // 傳入錯誤長度
info); // 存放錯誤信息的內(nèi)存
NSLog(@"著色器錯誤:%s", info);
free(info); // 釋放內(nèi)存
}
// 移除創(chuàng)建失敗的著色器
glDeleteShader(shader);
return 0;
}
return shader;
}
顯示渲染結(jié)果:
上面是控制器里主要的代碼骡澈,注釋已經(jīng)加的很詳細(xì)的就不再解釋了。下面看用使用GLKit后代碼的樣子掷空。
使用GLKit的方法
首先創(chuàng)建一個空項目和上面一樣先導(dǎo)入2個庫肋殴,然后讓ViewController繼承自GLKViewController囤锉。再到Main.storyboard里添加一個GLKViewController控制器并設(shè)成初始化控制器。別忘了與ViewController綁定护锤。
接下來是代碼時間官地,先在ViewController里聲明一個屬性:
@property (nonatomic,strong)GLKBaseEffect * effect;
然后我們在全局區(qū)聲明一個頂點數(shù)組:
GLfloat vertexes[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
接下來就是全部主要代碼了:
- (void)viewDidLoad {
[super viewDidLoad];
// 獲取控制器的view
GLKView * glView = (GLKView *)self.view;
// 設(shè)置當(dāng)前的context
EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (nil == context) {
NSLog(@"context create failed");
}
glView.context = context;
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"set currentContext failed");
}
// 創(chuàng)建GLKBaseEffect
self.effect = [[GLKBaseEffect alloc] init];
// 設(shè)置使用的顏色
self.effect.useConstantColor = GL_TRUE;
self.effect.constantColor = GLKVector4Make(1.0f, 0.0f, 0.0f, 1.0f);
// 設(shè)置清屏顏色
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// 開啟頂點
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 設(shè)置數(shù)據(jù)指針
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, vertexes);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 3);
}
顯示結(jié)果如下:
對比一下是不是用了GLKit的代碼很簡單!