iOSでのOpenGLをちゃんと理解する(GLKitでブラックボックスになっている部分を理解する)

  • 86
    いいね
  • 0
    コメント

最近はiOSでOpenGLをやるにもGLKitがあるので初期設定などスキップできて非常に便利になりました。反面、OpenGLの基礎を学ぼうにもブラックボックスな部分が多くなってしまいiOS開発を通して汎用的なOpenGLの基礎知識を得るのは難しいかと思いました。そんなOpenGLの基礎をあらためてしっかり理解したかったり、僕のようにOpenGLES1.1が強制される環境で開発している方向けにGLKitを使わずにOpenGLで描画する方法をまとめました。

改めて下記環境での記事です。

  • iOS
  • Swift
  • OpenGL1.1
  • GLKitを使用しない

動くコードをGithubに用意してあります。
https://github.com/ykensuke/OpenGLSample
画面に三角形を描画するサンプルです。

本題ですが、GLKitなしでOpenGL1.1をswiftで書くとこんな感じのコードになります。重要なポイントにコメントがしてあり、それがこの記事の7割説明したいことです。

import Foundation
import UIKit
import OpenGLES

class GLSurfaceView: UIView {

    fileprivate var context: EAGLContext?
    fileprivate var viewFramebuffer = GLuint()
    fileprivate var viewRenderbuffer = GLuint()

    override class var layerClass() : AnyClass {
        return CAEAGLLayer.self
    }

    override init (frame: CGRect) {

        super.init(frame: frame)

        let eaglLayer: CAEAGLLayer = self.layer as! CAEAGLLayer
        eaglLayer.isOpaque = true
        eaglLayer.drawableProperties = [
            kEAGLDrawablePropertyRetainedBacking: false,
            kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
            ] as NSDictionary as! [AnyHashable: Any]

        //コンテキストの生成。
        context = EAGLContext(api: EAGLRenderingAPI.openGLES1)

        //対象のコンテキストを「現在の」コンテキストとして設定
        EAGLContext.setCurrent(context)

        //フレームバッファを生成。そのIDをviewFramebufferに代入してもらう
        glGenFramebuffersOES(1, &viewFramebuffer)
        //対象のフレームバッファを「現在の」フレームバッファとして設定
        glBindFramebufferOES(GLenum(GL_FRAMEBUFFER_OES), viewFramebuffer)

        //レンダーバッファを生成。そのIDをviewRenderbufferに代入してもらう
        glGenRenderbuffersOES(1, &viewRenderbuffer)
        //対象のレンダーバッファを「現在の」レンダーバッファとして設定
        glBindRenderbufferOES(GLenum(GL_RENDERBUFFER_OES), viewRenderbuffer)

        //フレームバッファにレンダーバッファを、カラーバッファとしてアタッチ
        glFramebufferRenderbufferOES(GLenum(GL_FRAMEBUFFER_OES), GLenum(GL_COLOR_ATTACHMENT0_OES), GLenum(GL_RENDERBUFFER_OES), viewRenderbuffer)

        //レンダーバッファオブジェクトに、描画可能なオブジェクトのストレージをバインド    
        self.context!.renderbufferStorage(Int(GL_RENDERBUFFER_OES), from:eaglLayer)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {

        let width = self.bounds.size.width
        let height = self.bounds.size.height

        //ビューポートの設定
        glViewport(0, 0, GLsizei(width), GLsizei(height));

        self.render()
    }

    /**
     * 明示的な再描画したいタイミング、もしくはこのViewのサイズが変更されるなどで
     * layoutSubviewsが実行される時に、実行される予定。
     */
    func render() {

        //先ほどのコンテキストを「現在の」コンテキストとして設定
        EAGLContext.setCurrent(context)

        //対象のフレームバッファを「現在の」フレームバッファとして設定
        glBindFramebufferOES(GLenum(GL_FRAMEBUFFER_OES), GLenum(viewFramebuffer))

        //クリアする色を設定
        glClearColor(0.0, 0.0, 0.0, 0.0)
        //描画領域全体をクリア
        glClear(GLenum(GL_COLOR_BUFFER_BIT))

        //**********************
        //ここに描画したい内容を書く
        //**********************

        //対象のレンダーバッファを「現在の」レンダーバッファとして設定
        glBindRenderbufferOES(GLenum(GL_RENDERBUFFER_OES), viewRenderbuffer)

        //レンダバッファを画面に表示
        self.context?.presentRenderbuffer(Int(GL_RENDERBUFFER_OES))
    }

}

コードの基本的な流れ

コンテキストの生成

そのコンテキスト上でフレームバッファを生成

そのコンテキスト上でレンダーバッファを生成

フレームバッファとレンダーバッファを紐付け

レンダーバッファとレイヤーを紐付け

描画したい内容をセット

レンダバッファをレイヤーに描画

用語の説明

説明
コンテキスト 描画処理のためのデータの集合体
フレームバッファ 頂点データや行列などレンダリングに必要なデータを保持する
レンダーバッファ レンダリングされた結果を保持する。ビットマップのようなピクセル配列。(色、Z値、ステンシル値を保持するタイプがある)

すごくざっくりした関係図

無題図形描画.png

参考

この記事はすごく噛み砕いた内容になっているので、下記参考のリンクを読むことをお勧めします。

○○バッファみたいなのがなんなのかわからない人はこちらのリンクがとても参考になります。
http://qiita.com/edo_m18/items/95483cabf50494f53bb5

関数に関する詳細の説明はこちらが参考になります。
http://ameblo.jp/xcc/entry-10283875170.html

2016/11/23 更新

投稿して一年以上経つのに、今だにストックしてくれる方がいるようなので、Swift3.0に対応するようアップデートしました。Gitのリポジトリも更新してあります。