OpenGL ES 案例02:GLKit繪制立方體+旋轉

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

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

  • 未加光照


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


    有光照效果圖

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


整體思路

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

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

ViewDidLoad函數(shù)

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

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

commonInit函數(shù)

OpenGL ES初始化分為五部分:

  • 初始化上下文 & 設置當前上下文
  • 創(chuàng)建GLKView對象,并設置context,加入view中
  • 配置深度緩沖區(qū)
  • 獲取紋理圖片 & 設置紋理參數(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并設置代理
    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ù)(紋理倒置翻轉的策略)
    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、初始化上下文 & 設置當前上下文
         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ù)據(jù)(頂點坐標 & 紋理坐標 & 法線),并將這些數(shù)據(jù)從CPU拷貝至GPU

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

頂點坐標與紋理坐標圖示

其中6個面與紋理的映射關系如下


6個面與紋理的映射關系

頂點數(shù)據(jù)使用結構體定義

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

//初始化--這里數(shù)據(jù)的初始化方式是c語言中的結構體賦值
(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ù)從內存(CPU)拷貝至顯存(GPU)中
頂點緩沖區(qū):簡稱VBO
頂點數(shù)組:簡稱VAO
glBufferData中確認了緩存區(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的開關在ios中是默認關閉的,需要使用代碼手動開啟,同時通道需要打開三次(頂點,紋理,法線各需要打開一次),將頂點數(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ù)的結構體加了一個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中,用于立方體旋轉效果的實現(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定時器的刷新的頻率與屏幕刷新頻率一致,每次刷新都需要計算旋轉角度,并應用于立方體

  • OC版本
- (void) update{
    //計算旋轉度數(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(){
 //        計算旋轉角度
         angle = (angle + 5).truncatingRemainder(dividingBy: 360)
         
 //        修改矩陣堆棧
         effect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(angle), 0.3, 1, -0.7)
         
 //        重新渲染,回調代理方法重新繪制
         glkView.display()
     }

GLKViewDelegate代理

由于GLKView是自定義的,所以需要在前面設置delegate,當然也可以將控制器默認的view的父類改為GLKView,不需要設置delegate

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

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

//    開啟深度測試
    glEnable(GL_DEPTH_TEST);
    
//    清除緩存區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
//    準備繪制
    [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))
         
         //準備繪制
         effect.prepareToDraw()
         
         //開始繪制
         glDrawArrays(GLenum(GL_TRIANGLES), 0, GLsizei(kVertexCount))
     }
 }

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容