OpenGL ES 案例02:GLKit繪制立方體+旋轉(zhuǎn)

OpenGL + OpenGL ES +Metal 系列文章匯總

本案例是實現(xiàn)一個有紋理的立方體噪舀,并根據(jù)任意軸旋轉(zhuǎn)壹瘟,整體效果如下

  • 未加光照


    無光照效果圖
  • 增加光照效果


    有光照效果圖

增加光照的主要的思路如下


整體思路

代碼的實現(xiàn)主要分為4部分:

  • 準(zhǔn)備工作:這部分主要的相關(guān)庫的導(dǎo)入及屬性的創(chuàng)建(這里不做過多闡述)
  • ViewDidLoad函數(shù):初始化OpenGL ES相關(guān)屬性命锄,加載頂點&紋理坐標(biāo)數(shù)據(jù)霸褒,以及設(shè)置定時器
  • GLKViewDelegate函數(shù):視圖的繪制
  • update函數(shù):定時器方法膛檀,計算旋轉(zhuǎn)角度并修改矩陣堆棧,重新渲染立方體斟赚,以實現(xiàn)立方體的旋轉(zhuǎn)

ViewDidLoad函數(shù)

這部分主要是一些初始化工作

  • commonInit: OpenGL ES相關(guān)初始化
  • setupVertex: 加載頂點&紋理坐標(biāo)數(shù)據(jù)
  • addCADisplayLink: 添加定時器

commonInit函數(shù)

OpenGL ES初始化分為五部分:

  • 初始化上下文 & 設(shè)置當(dāng)前上下文
  • 創(chuàng)建GLKView對象着降,并設(shè)置context,加入view中
  • 配置深度緩沖區(qū)
  • 獲取紋理圖片 & 設(shè)置紋理參數(shù)
  • 初始化effect拗军,并使用effect

注:代碼分為OC版本和Swift版本

  • OC
- (void) commonInit{
//    1任洞、創(chuàng)建context
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    [EAGLContext setCurrentContext:context];
    
//    2、創(chuàng)建GLKView并設(shè)置代理
    CGRect frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width );
    self.glkView = [[GLKView alloc] initWithFrame:frame context:context];
    self.glkView.backgroundColor = [UIColor clearColor];
    self.glkView.delegate = self;
    
//    3发侵、使用深度測試
    self.glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    
//    4交掏、將glkView加入到view上
    [self.view addSubview:self.glkView];
    
//    5、獲取紋理圖片
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mouse" ofType:@"jpg"];
    UIImage *image = [UIImage imageWithContentsOfFile:filePath];
    
//    6刃鳄、設(shè)置紋理參數(shù)(紋理倒置翻轉(zhuǎn)的策略)
    NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft: @(YES)};
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:[image CGImage] options:options error:NULL];
    
//    7盅弛、使用effect
    self.baseEffect = [[GLKBaseEffect alloc] init];
    self.baseEffect.texture2d0.name = textureInfo.name;
    self.baseEffect.texture2d0.target = textureInfo.target;
    
}
  • Swift:其中有些變量使用了懶加載具體的代碼請見文末的鏈接
fileprivate func commonInit(){
         
 //        1、初始化上下文 & 設(shè)置當(dāng)前上下文
         guard let context = EAGLContext(api: .openGLES3) else{
             return
         }
         EAGLContext.setCurrent(context)
         glkView.context = context
         
 //        讀取紋理圖片
         let filePath = Bundle.main.path(forResource: "mouse", ofType: "jpg")
         let image = UIImage(contentsOfFile: filePath!)
         guard let textureInfo: GLKTextureInfo = try? GLKTextureLoader.texture(with: (image?.cgImage)!, options: [GLKTextureLoaderOriginBottomLeft:NSNumber.init(integerLiteral: 1)]) else{
             return
         }
         
 //        使用effect
         effect = GLKBaseEffect()
         effect.texture2d0.name = textureInfo.name
         effect.texture2d0.target = GLKTextureTarget(rawValue: textureInfo.target)!
     }
     

setupVertex函數(shù)

這部分主要是設(shè)置頂點數(shù)據(jù)(頂點坐標(biāo) & 紋理坐標(biāo) & 法線)叔锐,并將這些數(shù)據(jù)從CPU拷貝至GPU

設(shè)置頂點數(shù)據(jù)
下圖是立方體的頂點坐標(biāo)與紋理坐標(biāo)圖示

頂點坐標(biāo)與紋理坐標(biāo)圖示

其中6個面與紋理的映射關(guān)系如下


6個面與紋理的映射關(guān)系

頂點數(shù)據(jù)使用結(jié)構(gòu)體定義

  • OC版本
typedef struct {
    GLKVector3 positionCoord;   //頂點坐標(biāo)
    GLKVector2 textureCoord;    //紋理坐標(biāo)
    GLKVector3 normal;          //法線
} CCVertex;

//初始化--這里數(shù)據(jù)的初始化方式是c語言中的結(jié)構(gòu)體賦值
(CCVertex){{-0.5, 0.5, 0.5}, {0, 1}, {0, 0, 1}};
  • Swift版本(暫時未包括法線)
struct CCVertex {
     var positionCoord: GLKVector3
     var textureCoord: GLKVector2
 }
 
 //初始化
 CCVertex(positionCoord: GLKVector3(v: (-0.5, 0.5, 0.5)), textureCoord: GLKVector2(v: (0, 1)))

頂點數(shù)據(jù)的具體代碼見完整demo代碼挪鹏,這里不做過多說明

開辟緩存區(qū),copy頂點數(shù)據(jù)到GPU
將頂點數(shù)據(jù)從內(nèi)存(CPU)拷貝至顯存(GPU)中
頂點緩沖區(qū):簡稱VBO
頂點數(shù)組:簡稱VAO
glBufferData中確認(rèn)了緩存區(qū)的大小

  • OC版本
 glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(CCVertex)*kCoordCount, self.vertices, GL_STATIC_DRAW);
  • Swift版本
//2掌腰、拷貝到頂點緩沖區(qū)
         glGenBuffers(1, &vertexBuffer)
 //        綁定頂點緩沖區(qū)
         glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
 //        coppy頂點數(shù)據(jù)
         glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<CCVertex>.size*kVertexCount, vertices, GLenum(GL_STATIC_DRAW))

打開通道
attribute的開關(guān)在ios中是默認(rèn)關(guān)閉的狰住,需要使用代碼手動開啟张吉,同時通道需要打開三次(頂點齿梁,紋理,法線各需要打開一次)肮蛹,將頂點數(shù)據(jù)從顯存中讀取到GLKit的著色器中

  • OC版本
    其中的NULL是可以省略的勺择,但是加上代碼可讀性強
//    頂點數(shù)據(jù)
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, positionCoord));
    
//    紋理數(shù)據(jù)
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, textureCoord));
    
//    光照
    glEnableVertexAttribArray(GLKVertexAttribNormal);
    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, normal));
  • Swift版本(暫未加光照)
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
          glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<CCVertex>.size), UnsafeMutableRawPointer(bitPattern: 0))
         
         glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
         //這里加4的原因是因為蘋果對部分包含vector類型數(shù)據(jù)的結(jié)構(gòu)體加了一個padding,此處這個padding等于4個字節(jié)。CCVertex占24個字節(jié)伦忠,而不是5個float所占的20個字節(jié)
         glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<CCVertex>.size), UnsafeMutableRawPointer(bitPattern: MemoryLayout<GLKVector3>.size+4))

addCADisplayLink函數(shù)

初始化定時器省核,并將定時器加入runloop中,用于立方體旋轉(zhuǎn)效果的實現(xiàn)

  • OC版本
- (void)addCADisplayLink{
    
    self.angle = 0;
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    
}
  • Swift版本
fileprivate func addCADisplayLink(){
         displayLink.add(to: RunLoop.main, forMode: .common)
     }

update更新

CADisplayLink定時器的刷新的頻率與屏幕刷新頻率一致昆码,每次刷新都需要計算旋轉(zhuǎn)角度气忠,并應(yīng)用于立方體

  • OC版本
- (void) update{
    //計算旋轉(zhuǎn)度數(shù)
    self.angle = (self.angle +5) % 360;
//    修改baseEffect.transform.modelviewMatrix
    self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(self.angle), 0.3, 1, 0.7);
    
//    重新渲染
    [self.glkView display];
}
  • Swift版本
@objc fileprivate func update(){
 //        計算旋轉(zhuǎn)角度
         angle = (angle + 5).truncatingRemainder(dividingBy: 360)
         
 //        修改矩陣堆棧
         effect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(angle), 0.3, 1, -0.7)
         
 //        重新渲染邻储,回調(diào)代理方法重新繪制
         glkView.display()
     }

GLKViewDelegate代理

由于GLKView是自定義的,所以需要在前面設(shè)置delegate旧噪,當(dāng)然也可以將控制器默認(rèn)的view的父類改為GLKView吨娜,不需要設(shè)置delegate

代理方法的主要目的是繪制視圖的內(nèi)容,并根據(jù)定時器的旋轉(zhuǎn)變換淘钟,重新渲染視圖

  • OC版本
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{

//    開啟深度測試
    glEnable(GL_DEPTH_TEST);
    
//    清除緩存區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
//    準(zhǔn)備繪制
    [self.baseEffect prepareToDraw];
    
//    繪圖(數(shù)組繪制)
    glDrawArrays(GL_TRIANGLES, 0, kCoordCount);
   
}
  • Swift版本
extension ViewController: GLKViewDelegate{
     func glkView(_ view: GLKView, drawIn rect: CGRect) {
         
 //       開啟深度測試
         glEnable(GLenum(GL_DEPTH_TEST));
         glClear(GLbitfield(GL_COLOR_BUFFER_BIT) | UInt32(GL_DEPTH_BUFFER_BIT))
         
         //準(zhǔn)備繪制
         effect.prepareToDraw()
         
         //開始繪制
         glDrawArrays(GLenum(GL_TRIANGLES), 0, GLsizei(kVertexCount))
     }
 }

注:swift與OC代碼的難點主要還是在于對指針的操作

完整的代碼見github - 09_GLKit_立方體+旋轉(zhuǎn)OC宦赠、09_GLKit立方體+旋轉(zhuǎn)_Swift(未包含光照效果),分別提供了OC和Swift版本

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末米母,一起剝皮案震驚了整個濱河市勾扭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铁瞒,老刑警劉巖妙色,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慧耍,居然都是意外死亡燎斩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門蜂绎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栅表,“玉大人,你說我怎么就攤上這事师枣」制浚” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵践美,是天一觀的道長洗贰。 經(jīng)常有香客問我,道長陨倡,這世上最難降的妖魔是什么敛滋? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮兴革,結(jié)果婚禮上绎晃,老公的妹妹穿的比我還像新娘。我一直安慰自己杂曲,他們只是感情好庶艾,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擎勘,像睡著了一般咱揍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棚饵,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天煤裙,我揣著相機與錄音掩完,去河邊找鬼。 笑死硼砰,一個胖子當(dāng)著我的面吹牛藤为,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夺刑,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼缅疟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了遍愿?” 一聲冷哼從身側(cè)響起存淫,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沼填,沒想到半個月后桅咆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡坞笙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年岩饼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薛夜。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡籍茧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梯澜,到底是詐尸還是另有隱情寞冯,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布晚伙,位于F島的核電站吮龄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咆疗。R本人自食惡果不足惜漓帚,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望午磁。 院中可真熱鬧尝抖,春花似錦、人聲如沸漓踢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喧半。三九已至,卻和暖如春青责,著一層夾襖步出監(jiān)牢的瞬間挺据,已是汗流浹背取具。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扁耐,地道東北人暇检。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像婉称,于是被迫代替她去往敵國和親块仆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359