OpenGL ES學(xué)習(xí)筆記之三(創(chuàng)建立方體)

這次要做的是一個(gè)立方體,在學(xué)完紋理之后其實(shí)大家應(yīng)該能自己實(shí)現(xiàn)一個(gè)立方體,也就是把Z軸利用上就可以實(shí)現(xiàn)了。唯一的難點(diǎn)是這里涉及一個(gè)深度測(cè)試,如果不加深度測(cè)試渲染出來(lái)的立方體會(huì)是一個(gè)有缺失面的立方體。現(xiàn)在我們進(jìn)入正題,和以前一樣我們用兩套代碼來(lái)實(shí)現(xiàn)。

學(xué)習(xí)的代碼都在我的github倉(cāng)庫(kù)歡迎大家學(xué)習(xí)指教!

不使用GLKit的方式

一、設(shè)置頂點(diǎn)和紋理數(shù)據(jù)

為了避免重復(fù)同樣的話,前面講過(guò)的知識(shí)點(diǎn)將不再說(shuō)明,我們是在上一篇代碼的基礎(chǔ)之上進(jìn)行修改的。前面我們已經(jīng)做出了一個(gè)紋理,只要用六個(gè)面就可以拼一個(gè)立方體,和單個(gè)紋理面的區(qū)別是空間坐標(biāo)不一樣而已。現(xiàn)將頂點(diǎn)坐標(biāo)數(shù)組寫成以下方式:

// 頂點(diǎn)結(jié)構(gòu)體
typedef struct{
    
    GLfloat position[3];
    GLfloat texturePosion[2];
} Vertex;


const Vertex vertexes[] = {
    // 頂點(diǎn)                   紋理
    // 前面
    {{-0.5f, 0.5f, 0.5f},   {0.0f, 0.0f}}, // 前左上 0
    {{-0.5f, -0.5f, 0.5f},  {0.0f, 1.0f}}, // 前左下 1
    {{0.5f, -0.5f, 0.5f},   {1.0f, 1.0f}}, // 前右下 2
    {{0.5f, 0.5f, 0.5f},    {1.0f, 0.0f}}, // 前右上 3
    // 后面
    {{-0.5f, 0.5f, -0.5f},   {1.0f, 0.0f}}, // 后左上 4
    {{-0.5f, -0.5f, -0.5f},  {1.0f, 1.0f}}, // 后左下 5
    {{0.5f, -0.5f, -0.5f},   {0.0f, 1.0f}}, // 后右下 6
    {{0.5f, 0.5f, -0.5f},    {0.0f, 0.0f}}, // 后右上 7
    // 左面
    {{-0.5f, 0.5f, -0.5f},   {0.0f, 0.0f}}, // 后左上 8
    {{-0.5f, -0.5f, -0.5f},  {0.0f, 1.0f}}, // 后左下 9
    {{-0.5f, 0.5f, 0.5f},   {1.0f, 0.0f}}, // 前左上 10
    {{-0.5f, -0.5f, 0.5f},  {1.0f, 1.0f}}, // 前左下 11
    // 右面
    {{0.5f, 0.5f, 0.5f},    {0.0f, 0.0f}}, // 前右上 12
    {{0.5f, -0.5f, 0.5f},   {0.0f, 1.0f}}, // 前右下 13
    {{0.5f, -0.5f, -0.5f},   {1.0f, 1.0f}}, // 后右下 14
    {{0.5f, 0.5f, -0.5f},    {1.0f, 0.0f}}, // 后右上 15
    // 上面
    {{-0.5f, 0.5f, -0.5f},   {0.0f, 0.0f}}, // 后左上 16
    {{-0.5f, 0.5f, 0.5f},   {0.0f, 1.0f}}, // 前左上 17
    {{0.5f, 0.5f, 0.5f},    {1.0f, 1.0f}}, // 前右上 18
    {{0.5f, 0.5f, -0.5f},    {1.0f, 0.0f}}, // 后右上 19
    // 下面
    {{-0.5f, -0.5f, 0.5f},  {0.0f, 0.0f}}, // 前左下 20
    {{0.5f, -0.5f, 0.5f},   {1.0f, 0.0f}}, // 前右下 21
    {{-0.5f, -0.5f, -0.5f},  {0.0f, 1.0f}}, // 后左下 22
    {{0.5f, -0.5f, -0.5f},   {1.0f, 1.0f}}, // 后右下 23
};

// 頂點(diǎn)索引
const GLbyte indexes[] = {
    // 前面
    0, 1, 2,
    0, 2, 3,
    // 后面
    4, 5, 6,
    4, 6, 7,
    // 左面
    8, 9, 11,
    8, 11, 10,
    // 右面
    12, 13, 14,
    12, 14, 15,
    // 上面
    16, 17, 18,
    16, 18, 19,
    // 下面
    20, 22, 23,
    20, 23, 21,
};

你們可能已經(jīng)看到了,這次的頂點(diǎn)坐標(biāo)和上一篇筆記的不一樣,這里是把頂點(diǎn)對(duì)應(yīng)的紋理坐標(biāo)也寫在一起了。這樣寫我們也可以學(xué)習(xí)另一種方式設(shè)置頂點(diǎn)和紋理坐標(biāo),這里我們還用到了索引,這樣有可以實(shí)現(xiàn)頂點(diǎn)數(shù)據(jù)的重復(fù)利用。

二、設(shè)置VBO(頂點(diǎn)緩存)

這次繪制立方體的時(shí)候有沒(méi)有會(huì)發(fā)現(xiàn)頂點(diǎn)數(shù)據(jù)比原來(lái)多很多!其實(shí)這還算是少的。簡(jiǎn)單的圖形我們可以通過(guò)手寫頂點(diǎn)數(shù)據(jù)來(lái)實(shí)現(xiàn),但是如果是一個(gè)復(fù)雜的圖形就不行了,我們?cè)谕娲笮陀螒虻臅r(shí)候都看過(guò)人物的結(jié)構(gòu)很復(fù)雜,這個(gè)用我們手寫頂點(diǎn)數(shù)據(jù)是不可能實(shí)現(xiàn)的!一般是由設(shè)計(jì)師用建模工具來(lái)實(shí)現(xiàn)的,最終的文件還是頂點(diǎn)數(shù)據(jù)。這些數(shù)據(jù)是在內(nèi)存中的,只有在繪制的時(shí)候才會(huì)從CPU傳給GPU,這種數(shù)據(jù)傳遞本身的性能也不是很高,當(dāng)頂點(diǎn)數(shù)據(jù)很大的時(shí)候?qū)π阅艿挠绊懜黠@。一般的優(yōu)化方案就是將頂點(diǎn)數(shù)據(jù)緩存的GPU的顯存里,這樣不用每次從CPU傳遞頂點(diǎn)數(shù)據(jù)到GPU從而提高圖形的性能。下面我們就用代碼實(shí)現(xiàn)VBO:

/**
 設(shè)置頂點(diǎn)緩存
 */
- (void)setupVBO{
    
    // 設(shè)置VBO
    // 設(shè)置頂點(diǎn)緩存
    GLuint bufferVBO;
    glGenBuffers(1, &bufferVBO);
    glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
    // 設(shè)置索引緩存
    GLuint bufferIndex;
    glGenBuffers(1, &bufferIndex);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
    // 設(shè)置頂點(diǎn)
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    // 設(shè)置紋理坐標(biāo)
    glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}
三、引入矩陣變換的庫(kù)

在圖像的世界里圖像的放大、縮小、位移都是通過(guò)圖片乘以一個(gè)矩陣來(lái)實(shí)現(xiàn)的(通過(guò)矩陣相乘效率更高)你在iOS開(kāi)發(fā)中是否遇到過(guò)圖形的transform屬性,你打印一下看看里面的結(jié)構(gòu)你就會(huì)明白,這也是一個(gè)矩陣數(shù)據(jù)。這個(gè)涉及線性代數(shù)的東西,你大學(xué)里學(xué)的線性代數(shù)的東西是不是已經(jīng)忘記了?不過(guò)不用怕網(wǎng)上已經(jīng)有很多矩陣計(jì)算相關(guān)的庫(kù),我們直接用就可以了。其實(shí)GLKit里也有矩陣計(jì)算的庫(kù),因?yàn)槲覀冞@里不打算用GLKit所以我們用一個(gè)別人寫好的庫(kù)。可以從這里下載這個(gè)庫(kù)。下載圖片中的這幾個(gè)文件。

矩陣庫(kù)圖片.png

四、實(shí)現(xiàn)觸摸來(lái)滾動(dòng)立方體

如果我們不添加讓立方體滾動(dòng)的功能就只能看到一個(gè)面,看不出立方體的效果,而且深度測(cè)試的效果也看不出來(lái)。滾動(dòng)的實(shí)現(xiàn)方式也很簡(jiǎn)單讓其乘以一個(gè)矩陣即可,我們用兩個(gè)變量來(lái)記錄手指在屏幕上劃動(dòng)的量變。代碼如下:

// 限制上下轉(zhuǎn)動(dòng)的角度
#define kLimitDegreeUpDown 40.0

@interface ViewController ()
// 記錄x軸方向上的變量
@property(nonatomic,assign)GLfloat degreeX;
// 記錄y軸方向上的變量
@property(nonatomic,assign)GLfloat degreeY;

@end

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch * touch = touches.anyObject;
    
    CGPoint currentPoint = [touch locationInView:self.view];
    CGPoint previousPoint = [touch previousLocationInView:self.view];
    
    self.degreeX += previousPoint.y - currentPoint.y;
    
    // 限制上下轉(zhuǎn)動(dòng)的角度
    if (self.degreeX > kLimitDegreeUpDown) {
        self.degreeX = kLimitDegreeUpDown;
    }
    
    if (self.degreeX < -kLimitDegreeUpDown) {
        self.degreeX = -kLimitDegreeUpDown;
    }
    
    self.degreeY += previousPoint.x - currentPoint.x;
}
五、設(shè)置視角

可能你會(huì)在上一個(gè)筆記中看到我們創(chuàng)建的紋理是一個(gè)長(zhǎng)方形的,但我們?cè)O(shè)置的坐標(biāo)應(yīng)該出現(xiàn)的是一個(gè)正方形才對(duì)。其實(shí)我以前也糾結(jié)這個(gè)問(wèn)題,查了很多資料都沒(méi)找到相關(guān)說(shuō)明,后來(lái)在我無(wú)意間設(shè)置完視角發(fā)現(xiàn)一切都正常了,所以我們要對(duì)代碼進(jìn)行以下更改:

  • 將頂點(diǎn)著色器文件修改如下
attribute vec4 myPosition;
// 添加物體運(yùn)動(dòng)的變換矩陣
uniform mat4 modelView;
// 添加視角的變換矩陣
uniform mat4 projection;

attribute vec2 textureCoordsIn;
varying vec2 textureCoordsOut;

void main()
{
    // 將原來(lái)的頂點(diǎn)乘以矩陣數(shù)據(jù),注意乘的前后順序
    gl_Position = projection * modelView * myPosition;
    textureCoordsOut = textureCoordsIn;
}
  • 添加相應(yīng)的槽
    // 添加以下成員變量
    GLuint _modelViewSlot; // 物體變換的槽
    GLuint _projectionSlot; // 攝像機(jī)的槽

    // 在原來(lái)設(shè)置槽的位置添加以下代碼
    _modelViewSlot = glGetUniformLocation(_program, "modelView");
    _projectionSlot = glGetUniformLocation(_program, "projection");
  • 用一個(gè)方法來(lái)傳遞視角數(shù)據(jù)和用一個(gè)方法來(lái)傳遞圖形的變換矩陣,該方法在render方法里調(diào)用
- (void)setupPerspactive{
    
    GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
    
    ksMatrix4 tempMatrix;
    
    ksMatrixLoadIdentity(&tempMatrix);
    // 設(shè)置視角矩陣
    ksPerspective(&tempMatrix, 60, aspect, 0.1f, 10.0f);
    // 傳遞視角矩陣數(shù)據(jù)
    glUniformMatrix4fv(_projectionSlot, 1 , GL_FALSE, (GLfloat *)&tempMatrix.m[0][0]);
}

/**
 設(shè)置圖形的變換矩陣
 */
- (void)setupModelViewMatrix{
    
    // 設(shè)置物體的變換
    ksMatrixLoadIdentity(&_matrix4);
    // 遠(yuǎn)離視野,不然是在視角會(huì)在立方體的中心
    ksMatrixTranslate(&_matrix4, 0, 0, -3);
    // x方向旋轉(zhuǎn)
    ksMatrixRotate(&_matrix4, self.degreeX, 1, 0, 0);
    // y方向旋轉(zhuǎn)
    ksMatrixRotate(&_matrix4, self.degreeY, 0, 1, 0);
    
    glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat *)&_matrix4.m[0][0]);
}
六、實(shí)現(xiàn)視圖的刷新

前面我們的代碼都是只渲染一次,在修改了數(shù)據(jù)后視圖是不會(huì)改變的。為了能及時(shí)渲染每次修改的數(shù)據(jù)我們要用CADisplayLink來(lái)不停渲染數(shù)據(jù)。實(shí)現(xiàn)代碼正如下:

@interface ViewController ()

@property (nonatomic,strong)CADisplayLink * link;

@end


- (void)viewDidLoad {
    [super viewDidLoad];

    // 設(shè)置視圖刷新
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
    
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

修改渲染方法,因?yàn)槲覀兏某闪耸褂盟饕齺?lái)繪制圖形,所以繪制方法也需要修改,修改后的渲染方法如下:

/**
 渲染
 */
- (void)render {
    
    [self setupPerspactive];
    [self setupModelViewMatrix];
    glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 用索引要設(shè)置要繪制的方法
    glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
    
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
七、實(shí)現(xiàn)深度測(cè)試

通過(guò)以上步驟我們已經(jīng)可實(shí)現(xiàn)一個(gè)半成品了,我們先運(yùn)行看一下結(jié)果。


沒(méi)有深度測(cè)試.gif

你可能已經(jīng)看到了問(wèn)題:有的面有缺失。對(duì)于該問(wèn)題是因?yàn)楹箐秩镜拿娓采w了先前渲染的面所造成的,解決方法是設(shè)置深度測(cè)試即可。這樣在渲染的時(shí)候會(huì)檢測(cè)各個(gè)面的前后關(guān)系,在后面被擋住的面將不會(huì)渲染,從而來(lái)解決此問(wèn)題。深度測(cè)試的代碼如下:

  • 在原來(lái)設(shè)置渲染緩存的方法里添加深度測(cè)試緩存代碼
- (void)setupFrameAndRenderBuffer {
    
    // 申請(qǐng)渲染緩存
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
    // 該方法最好在綁定渲染后立即設(shè)置,不然后面會(huì)被綁定為深度渲染緩存
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_glLayer];
    
    // 設(shè)置深度調(diào)試
    GLint width;
    GLint height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    
    // 申請(qǐng)深度渲染緩存
    glGenRenderbuffers(1, &_depthRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
    // 設(shè)置深度測(cè)試的存儲(chǔ)信息
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    
    // 申請(qǐng)幀緩存
    glGenFramebuffers(1, &_frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
    
    // 將渲染緩存掛載到GL_DEPTH_ATTACHMENT這個(gè)掛載點(diǎn)上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
    // GL_RENDERBUFFER綁定的是深度測(cè)試渲染緩存,所以要綁定回色彩渲染緩存
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
    
    // 檢查幀緩存狀態(tài)
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Error: Frame buffer is not completed.");
        exit(1);
    }
}
  • 在渲染方法里將原來(lái)的清空緩存區(qū)數(shù)據(jù)的方法改成以下方法
    glEnable(GL_DEPTH_TEST);
    // 清空色彩數(shù)據(jù)的同時(shí)清空深度數(shù)據(jù)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

運(yùn)行結(jié)果如下:


添加深度測(cè)試.gif

實(shí)現(xiàn)只有線連成的方法體

我在評(píng)論區(qū)看到有人想問(wèn)的問(wèn)題可能是在問(wèn)怎么實(shí)現(xiàn)用線繪制立方體,就在這里補(bǔ)充一下評(píng)論區(qū)里的問(wèn)題的答案。我們目前都是通過(guò)將頂點(diǎn)繪制成三角形的方式來(lái)實(shí)現(xiàn)一些圖形效果,除此之外我們還可以將頂點(diǎn)數(shù)據(jù)繪制成點(diǎn)(也可以說(shuō)成點(diǎn)精靈)或者直線。這次我們用直線實(shí)現(xiàn),首先將頂點(diǎn)和索引數(shù)據(jù)要修改如下:

const GLfloat vertexes[] = {

    -0.5f, 0.5f, 0.5f, // 前左上 0
    -0.5f, -0.5f, 0.5f, // 前左下 1
    0.5f, -0.5f, 0.5f, // 前右下 2
    0.5f, 0.5f, 0.5f, // 前右上 3
    // 后面
    -0.5f, 0.5f, -0.5f, // 后左上 4
    -0.5f, -0.5f, -0.5f, // 后左下 5
    0.5f, -0.5f, -0.5f, // 后右下 6
    0.5f, 0.5f, -0.5f // 后右上 7
};

const GLbyte indexes[] = {
    0, 1,
    1, 2,
    2, 3,
    3, 0,

    4, 5,
    5, 6,
    6, 7,
    7, 4,

    0, 4,
    1, 5,
    2, 6,
    3, 7
};

由于我們的頂點(diǎn)數(shù)據(jù)沒(méi)有了紋理坐標(biāo),指針的跨度由5變成了3所以我們要將設(shè)置頂點(diǎn)參數(shù)的代碼修改如下:

glVertexAttribPointer(_vertexSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, NULL);

我們的片元著色器也得修改,因?yàn)樵瓉?lái)是從紋理中獲取顏色數(shù)據(jù)而現(xiàn)在我們直接設(shè)置顏色數(shù)據(jù),將著色器修改如下:

precision mediump float;

void main()
{
  // 我們?cè)谶@里將顏色設(shè)成了紅色,下面4個(gè)數(shù)分別對(duì)應(yīng)RGBA
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

上面說(shuō)過(guò)我們這次要繪制成直線,所以在繪制的方法里將GL_TRIANGLES修改成GL_LINES,表示用線繪制。

glDrawElements(GL_LINES, sizeof(indexes) / sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);

好了,現(xiàn)在運(yùn)行一下代碼看看效果吧。代碼也同步更新了,可以在我的github下載。

立方體.gif

知識(shí)擴(kuò)展

后面會(huì)講到天空盒子,其實(shí)天空盒子就是一個(gè)立方體只是視角在立方體的中心而已,還記得我們?cè)O(shè)置圖形變換矩陣的時(shí)候有一行這樣的代碼:

    // 遠(yuǎn)離視野,不然是在視角會(huì)在立方體的中心
    ksMatrixTranslate(&_matrix4, 0, 0, -3);

這是為了看到圖形的全貌才設(shè)置的讓其在Z軸上遠(yuǎn)離我們,如果將這行代碼注釋了你就會(huì)看到我們的視角在立方體的中心,平面是雙面顯示的所以在立方體中心也能看到美女的圖片(后面我們會(huì)講到面剔除讓其只在一面顯示從而提高性能),你可以設(shè)置每個(gè)面都不同就可以實(shí)現(xiàn)天空盒子了,這個(gè)先留給大家嘗試一下吧!

使用GLKit的方式

使用GLKit的方法要簡(jiǎn)單很多,簡(jiǎn)單到不用再寫shader了。上面的原理能講的都已經(jīng)講完了,這里就只提供相關(guān)代碼和注釋。需要說(shuō)明的會(huì)在代碼注釋里。

// 限制上下轉(zhuǎn)動(dòng)的角度
#define kLimitDegreeUpDown 40.0

// 頂點(diǎn)結(jié)構(gòu)體
typedef struct{
    
    GLfloat position[3];
    GLfloat texturePosion[2];
} Vertex;


const Vertex vertexes[] = {
    // 頂點(diǎn)                   紋理
    // 前面
    {{-0.5f, 0.5f, 0.5f},   {0.0f, 1.0f}}, // 前左上 0
    {{-0.5f, -0.5f, 0.5f},  {0.0f, 0.0f}}, // 前左下 1
    {{0.5f, -0.5f, 0.5f},   {1.0f, 0.0f}}, // 前右下 2
    {{0.5f, 0.5f, 0.5f},    {1.0f, 1.0f}}, // 前右上 3
    // 后面
    {{-0.5f, 0.5f, -0.5f},   {1.0f, 1.0f}}, // 后左上 4
    {{-0.5f, -0.5f, -0.5f},  {1.0f, 0.0f}}, // 后左下 5
    {{0.5f, -0.5f, -0.5f},   {0.0f, 0.0f}}, // 后右下 6
    {{0.5f, 0.5f, -0.5f},    {0.0f, 1.0f}}, // 后右上 7
    // 左面
    {{-0.5f, 0.5f, -0.5f},   {0.0f, 1.0f}}, // 后左上 8
    {{-0.5f, -0.5f, -0.5f},  {0.0f, 0.0f}}, // 后左下 9
    {{-0.5f, 0.5f, 0.5f},   {1.0f, 1.0f}}, // 前左上 10
    {{-0.5f, -0.5f, 0.5f},  {1.0f, 0.0f}}, // 前左下 11
    // 右面
    {{0.5f, 0.5f, 0.5f},    {0.0f, 1.0f}}, // 前右上 12
    {{0.5f, -0.5f, 0.5f},   {0.0f, 0.0f}}, // 前右下 13
    {{0.5f, -0.5f, -0.5f},   {1.0f, 0.0f}}, // 后右下 14
    {{0.5f, 0.5f, -0.5f},    {1.0f, 1.0f}}, // 后右上 15
    // 上面
    {{-0.5f, 0.5f, -0.5f},   {0.0f, 1.0f}}, // 后左上 16
    {{-0.5f, 0.5f, 0.5f},   {0.0f, 0.0f}}, // 前左上 17
    {{0.5f, 0.5f, 0.5f},    {1.0f, 0.0f}}, // 前右上 18
    {{0.5f, 0.5f, -0.5f},    {1.0f, 1.0f}}, // 后右上 19
    // 下面
    {{-0.5f, -0.5f, 0.5f},  {0.0f, 1.0f}}, // 前左下 20
    {{0.5f, -0.5f, 0.5f},   {1.0f, 1.0f}}, // 前右下 21
    {{-0.5f, -0.5f, -0.5f},  {0.0f, 0.0f}}, // 后左下 22
    {{0.5f, -0.5f, -0.5f},   {1.0f, 0.0f}}, // 后右下 23
};

const GLbyte indexes[] = {
    // 前面
    0, 1, 2,
    0, 2, 3,
    // 后面
    4, 5, 6,
    4, 6, 7,
    // 左面
    8, 9, 11,
    8, 11, 10,
    // 右面
    12, 13, 14,
    12, 14, 15,
    // 上面
    16, 17, 18,
    16, 18, 19,
    // 下面
    20, 22, 23,
    20, 23, 21,
};


@interface ViewController ()

@property (nonatomic,strong)GLKBaseEffect * effect;
@property(nonatomic,assign)GLfloat degreeX;

@property(nonatomic,assign)GLfloat degreeY;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    GLKView * glView = (GLKView *)self.view;
    
    EAGLContext * contex = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    if (!contex) {
        NSLog(@"context創(chuàng)建失敗");
    }
    if (![EAGLContext setCurrentContext:contex]) {
        NSLog(@"設(shè)置當(dāng)前context失敗");
    }
    
    glView.context = contex;
    glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
    
    self.effect = [[GLKBaseEffect alloc] init];
    
    // 加載紋理圖片
    NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];
    
    NSError * error;
    CGImageRef image = [UIImage imageNamed:@"girl"].CGImage;
    GLKTextureInfo * textureInfo = [GLKTextureLoader textureWithCGImage:image options:options error:&error];
    if (error) {
        NSLog(@"%@", error);
    }
    
    // 設(shè)置紋理可用
    self.effect.texture2d0.enabled = GL_TRUE;
    // 傳遞紋理信息
    self.effect.texture2d0.name = textureInfo.name;
    
    [self setupVBO];
    [self setupPerspective];
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glEnable(GL_DEPTH_TEST);
}

/**
 設(shè)置頂點(diǎn)緩存
 */
- (void)setupVBO{
    
    // 設(shè)置VBO
    GLuint bufferVBO;
    glGenBuffers(1, &bufferVBO);
    glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
    
    GLuint bufferIndex;
    glGenBuffers(1, &bufferIndex);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
    // 設(shè)置頂點(diǎn)
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    // 設(shè)置紋理坐標(biāo)
    glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}

- (void)setupPerspective{
    
    GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
    
    GLKMatrix4 perspective = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(60), aspect, 0.1, 200);
    
    self.effect.transform.projectionMatrix = perspective;
}


- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    UITouch * touch = touches.anyObject;
    
    CGPoint currentPoint = [touch locationInView:self.view];
    CGPoint previousPoint = [touch previousLocationInView:self.view];
    
    self.degreeX += currentPoint.y - previousPoint.y;
    
    // 限制上下轉(zhuǎn)動(dòng)的角度
    if (self.degreeX > kLimitDegreeUpDown) {
        self.degreeX = kLimitDegreeUpDown;
    }
    
    if (self.degreeX < -kLimitDegreeUpDown) {
        self.degreeX = -kLimitDegreeUpDown;
    }
    
    self.degreeY += currentPoint.x - previousPoint.x;
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 遠(yuǎn)離視野,不然是在正方體內(nèi)部
    GLKMatrix4 modelMat = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -3);
    modelMat = GLKMatrix4RotateX(modelMat, GLKMathDegreesToRadians(self.degreeX));
    modelMat = GLKMatrix4RotateY(modelMat, GLKMathDegreesToRadians(self.degreeY));
    self.effect.transform.modelviewMatrix = modelMat;
    
    [self.effect prepareToDraw];
    
    glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評(píng)論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,083評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,706評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,442評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,802評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,983評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,287評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,486評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,710評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,116評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,412評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,224評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,462評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容