> 作為入門的小Demo,先帶大家學習怎樣在屏幕上渲染一張圖片。
其實很簡單:
先定義兩個屬性。
```
EAGLContext *context; //EAGLContent是蘋果在ios平臺下實現(xiàn)的opengles渲染層,用于渲染結(jié)果在目標surface上的更新。
GLKBaseEffect *mEffect;//著色器或者光照
```
在viewDidLoad中調(diào)用自定義方法。
```
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? //1.設置OpenGL ES 配置
? ? [self setUpConfig];
? ? //2.加載頂點數(shù)據(jù)
? ? [self uploadVertexArray];
? ? //3.加載紋理
? ? [self uploadTexture];
}
```
# 1. 設置OpenGL ES 配置
由于OpenGL ES 沒有提供窗口層,托管系統(tǒng)(iOS、安卓)必須提供函數(shù)來創(chuàng)建一個能夠接受OpenGL ES命令的渲染上下文,以及寫入任何繪制命令結(jié)果的緩沖區(qū)。
```
//新建OpenGL ES上下文
? ? context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];//這里用的是opengles3.
? ? if (!context) {
? ? ? ? NSLog(@"創(chuàng)建上下文失敗!");
? ? ? ? return;
? ? }
```
`EAGLContent`就是接受OpenGL ES命令的渲染上下文,是蘋果在ios平臺下實現(xiàn)的OpenGL ES渲染層,用于渲染結(jié)果在目標surface上的更新。
其中初始化參數(shù)API有以下幾種:
- kEAGLRenderingAPIOpenGLES1 ->OpenGL ES 1.0,固定管線。
- kEAGLRenderingAPIOpenGLES2 ->OpenGL ES 2.0,非固定管線。
- kEAGLRenderingAPIOpenGLES3 ->OpenGL ES 3.0,也是非固定管線,比2.0多了一些API而已。
接下來將context綁定到view上,這里需要在storyBoard中把控制器的view改為GLKView,當然如果你要用storyBoard的話。

```
//創(chuàng)建一個OpenGL ES上下文并將其分配給從storyboard加載的視圖
GLKView * view? = (GLKView *)self.view;
view.context = context;
```
同時,配置視圖創(chuàng)建所需要的渲染緩沖區(qū),有四個緩沖區(qū)可以設置:
- 顏色緩沖區(qū),它用以存儲將在屏幕中顯示的顏色。你可以使用GLKView的`drawableColorFormat`屬性來設置緩沖區(qū)中的每個像素的顏色格式。
```
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
```
GLKViewDrawableColorFormatRGBA8888:即緩存區(qū)的每個像素的最小組成部分(RGBA)使用8個bit,所以每個像素4個字節(jié),4*8個bit,默認格式。
GLKViewDrawableColorFormatRGB565:如果你的APP允許更小范圍的顏色,即可設置這個。會讓你的APP消耗更小的資源(內(nèi)存和處理時間)。
- 深度緩沖區(qū),保存深度值,OpenGL在深度測試時會把接近觀察者的對象的所有像素存儲到深度緩沖區(qū),當開始繪制一個像素時,OpenGL首先檢查深度緩沖區(qū),看是否已經(jīng)繪制了更接近觀察者的什么東西,如果是則忽略它,不用繪制它了,否則把它增加到深度緩沖區(qū)和顏色緩沖區(qū)進行繪制處理。
通過`drawableDepthFormat`屬性設置深度緩沖區(qū)。
```
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
```
GLKViewDrawableDepthFormatNone:完全沒有深度緩沖區(qū)。
GLKViewDrawableDepthFormat16:用于一般簡單的2D場景中,將消耗更少的資源,但是當對象非常接近彼此時,你可能存在渲染問題。
GLKViewDrawableDepthFormat24: 用于3D游戲。
- stencil(模板)緩沖區(qū),它幫助你把繪制區(qū)域限定到屏幕的一個特定部分。它還用于像影子一類的事物,比如你可以使用stencil緩沖區(qū)確保影子投射到地板。
```
view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
```
GLKViewDrawableStencilFormatNone:沒有stencil緩沖區(qū)。
GLKViewDrawableStencilFormat8。
- 多重采樣緩沖區(qū),用于抗鋸齒,它將一個像素分成更小的單元, 并在更細微的層面上多次調(diào)用fragment shader,之后它將返回的顏色合并,生成更光滑的幾何邊緣效果,但是該屬性比較占用內(nèi)存,耗時。
```
view.drawableMultisample = GLKViewDrawableMultisample4X;
```
GLKViewDrawableMultisampleNone:沒有多重采樣緩沖區(qū)。
GLKViewDrawableMultisample4X。
```
[EAGLContext setCurrentContext:context]; //設置當前緩沖區(qū)。
glEnable(GL_DEPTH_TEST); //開啟深度測試,就是讓離你近的物體可以遮擋離你遠的物體。
glClearColor(0.1, 0.2, 0.3, 1); //設置surface的清除顏色,也就是渲染到屏幕上的背景色。
```
# 2. 加載頂點數(shù)據(jù)
```
//頂點數(shù)據(jù),前三個是頂點坐標(x、y、z軸),后面兩個是紋理坐標(x,y)
GLfloat vertexData[] = {
? ? ? ? 0.5, -0.5, 0.0f,? ? 1.0f, 0.0f, //右下
? ? ? ? 0.5, 0.5, -0.0f,? ? 1.0f, 1.0f, //右上
? ? ? ? -0.5, 0.5, 0.0f,? ? 0.0f, 1.0f, //左上
? ? ? ? 0.5, -0.5, 0.0f,? ? 1.0f, 0.0f, //右下
? ? ? ? -0.5, 0.5, 0.0f,? ? 0.0f, 1.0f, //左上
? ? ? ? -0.5, -0.5, 0.0f,? 0.0f, 0.0f, //左下
? ? };
```
頂點坐標,OpenGLES的世界坐標系是[-1, 1],故而點(0, 0)是在屏幕的正中間。
紋理坐標系的取值范圍是[0, 1],原點是在左下角。故而點(0, 0)在左下角,點(1, 1)在右上角。
開啟頂點緩沖區(qū):
```
//頂點緩存區(qū)
? ? GLuint buffer;
? ? //申請一個緩存區(qū)標識符
? ? glGenBuffers(1, &buffer);
? ? //glBindBuffer把標識符綁定到GL_ARRAY_BUFFER上
? ? glBindBuffer(GL_ARRAY_BUFFER, buffer);
? ? //glBufferData把頂點數(shù)據(jù)從CPU內(nèi)存復制到GPU內(nèi)存
? ? glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
```
開啟對應的頂點屬性:
```
glEnableVertexAttribArray(GLKVertexAttribPosition);
```
默認情況下,出于性能考慮,所有頂點著色器的屬性(Attribute)變量都是disable的,意味著哪怕數(shù)據(jù)已經(jīng)上傳到GPU,頂點著色器也不能讀取到數(shù)據(jù)。由`glEnableVertexAttribArray`啟用指定屬性,才可在頂點著色器中訪問頂點的屬性數(shù)據(jù)。
設置合適的格式從buffer里面讀取數(shù)據(jù):
```
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
```
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
是用來上傳頂點數(shù)據(jù)到顯卡的方法。
參數(shù)解釋:
- indx: 指定要修改的頂點屬性的索引值
- size : 指定每個頂點屬性的組件數(shù)量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
- type : 指定數(shù)組中每個組件的數(shù)據(jù)類型。可用的符號常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值為GL_FLOAT。
- normalized : 指定當被訪問時,固定點數(shù)據(jù)值是否應該被歸一化(GL_TRUE)或者直接轉(zhuǎn)換為固定點值(GL_FALSE)
- stride : 指定連續(xù)頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0
- ptr? ? : 就是一個指針,指向的是需要上傳到頂點數(shù)據(jù)指針,通常是數(shù)組名的偏移量,指向數(shù)組中第一個頂點屬性的第一個組件,上面(GLfloat *)NULL + 0 指針,指向數(shù)組首地址。
開啟紋理的頂點屬性:
```
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
```
通過(GLfloat *)NULL + 3,指到紋理數(shù)據(jù):
```
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat * )NULL + 3);
```
# 3. 加載紋理
```
-(void)uploadTexture
{
? ? //獲取紋理圖片保存路徑
? ? NSString *filePath = [[NSBundle mainBundle]pathForResource:@"sf" ofType:@"jpg"];
? ? //GLKTextureLoaderOriginBottomLeft,紋理坐標是相反的
? ? NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft,NULL];
? ? GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:NULL];
? ? //著色器
? ? mEffect = [[GLKBaseEffect alloc]init];
? ? //第一個紋理屬性
? ? mEffect.texture2d0.enabled = GL_TRUE;
? ? //紋理的名字
? ? mEffect.texture2d0.name = textureInfo.name;
}
```
# 4. 在GLKViewDelegate的代理方法中啟動著色器
```
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
? ? glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
? ? //清除surface內(nèi)容,恢復至初始狀態(tài)
? ? glClear(GL_DEPTH_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
? ? //啟動著色器
? ? [mEffect prepareToDraw];
? ? glDrawArrays(GL_TRIANGLES, 0, 6);
}
```
最后完成效果:
