FCS(3) - OpenGLで描画!GLKitを活用しようの続き

GLKitによる2次元描画の準備ができましたので、今回はこのように、画面全体を4つの3角形で描画してカラーグラデーションをかけてみたいと思います。

頂点の定義

OpenGLでは基本的に3角形を使った描画を組み合わせて複雑な表現を実現します。サンプルアプリで定義されているgCubeVetexDataは、以下のように、3角形のそれぞれの頂点を示す頂点情報と(positionX,positionY,positionZ)、3角形の面のどちらが表なのか示す法線ベクトルの情報(normalX,normalY,normalZ)の両方を一つの配列で表現しています。

GLfloat gCubeVertexData[216] =  
{
    // Data layout for each line below is:
    // positionX, positionY, positionZ,     normalX, normalY, normalZ,
    0.5f, -0.5f, -0.5f,        1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, -0.5f,          1.0f, 0.0f, 0.0f,
    0.5f, 0.5f, 0.5f,         1.0f, 0.0f, 0.0f,
    ...

今回の実装は、最初は2次元の描画なので、頂点だけを示す頂点情報を定義してみます。頂点の表現は将来の3次元対応のためにZ軸の値も含むようにしてみましょう。

// Vertex and color structure.
typedef struct  
{
    float Position[3];
} Vertex;

これをもとに3角形を定義します。OpenGLの座標系は真ん中が(0, 0, 0)で、右と上が+方向、左と下が−方向になります。3次元の場合は手前が−方向、奥が+方向になります。画面比率調整をする前は、幅が2.0f、高さが2.0fになっていますので、上の一番左の画像を表現するために、左下->右下->左中央という頂点を結ぶとすると、

const Vertex gVertices[] = {  
    {-1, -1,  0},
    { 1, -1,  0},
    {-1,  0,  0},
}

このようになります。次にこの頂点情報をどのような順番で結ぶかという指定をGLubyteの配列gIndicesで定義します。

const GLubyte gIndices[] = {  
    0, 1, 2
};

OpenGLでの描画 - 頂点バッファ

では、早速ですが、この定義をつかって実際にOpenGLで描画をしてみましょう。OpenGL ES 2.0以降には頂点の情報を事前にGPUに送る頂点バッファの機能があるので、それを活用します。頂点および頂点インデックスのためのバッファとして、vertexBufferとindexBufferを定義します。

@interface FCBViewController () {
@property (nonatomic) GLuint vertexBuffer;
@property (nonatomic) GLuint indexBuffer;
@end

これをつかって、setupGLをこのように書き換えましょう。

- (void)setupGL
{
    [EAGLContext setCurrentContext:self.context];

    self.effect = [[GLKBaseEffect alloc] init];
    self.effect.light0.enabled = GL_TRUE;
    self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f);

    glEnable(GL_DEPTH_TEST);

    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)NULL);

    glGenBuffers(1, &_indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(gIndices), gIndices, GL_STATIC_DRAW);
}

ここではvertexBufferについて、

  • バッファの生成 - glGenBuffer
  • バッファのバインド - glBindBuffer
  • バッファへの情報転送 - glBufferData
  • バッファ情報の有効化 - glEnableVertexAttribArray
  • バッファ内の情報オフセット - glVertexAttribPointer

を行っています。頂点インデックスのindexBufferのほうは、描画時のglDrawElements呼び出し時に、バッファ情報の有効化や情報オフセット指定と同等のことを行いますのでここでは行いません。

実際の描画ですが、

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Render the object with GLKit
    [self.effect prepareToDraw];
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, 0);
}

と、glDrawElemensを呼びます。最初の引数にGL_TRIANGLESを、続いて頂点数3、各インデックスのサイズをGL_UNSIGNED_BYTEに、最後の引数のoffsetを0にして呼び出します。これで黒い三角形が画面の左下に描画されたと思います。

OpenGLでの描画 - 色の指定

次に色の指定を加えてみましょう。色の指定はRGBAの4要素なので、

typedef struct  
{
    float Color[4];
} Color;

を定義して、

Color gColors[] = {  
    {0, 1, 1, 1},
    {0, 1, 1, 1},
    {0, 0, 1, 1}
};

と下の2辺を水色に上の頂点を青にしてみます。カラーを有効にしますので、自動生成されたコードからlight0の設定を削除し、かわりに、colorMaterialEnabledをYESに設定します。頂点情報と同じように、colorBufferを設定し、以下のように、頂点バッファへ情報を転送し、カラー情報を有効化します。

- (void)setupGL
{
    [EAGLContext setCurrentContext:self.context];

    self.effect = [[GLKBaseEffect alloc] init];
    self.effect.colorMaterialEnabled = YES;

    glEnable(GL_DEPTH_TEST);

    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)NULL);

    glGenBuffers(1, &_colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(gColors), gColors, GL_STATIC_DRAW);
    glEnableVertexAttribArray(GLKVertexAttribColor);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Color), (void *)NULL);

    glGenBuffers(1, &_indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(gIndices), gIndices, GL_STATIC_DRAW);
}

これで準備は完了です。色情報がありますので、glDrawElementsの呼び出しをGL_TRIANLESではなくGL_TRIANGLE_STRIPに変更します。

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Render the object with GLKit
    [self.effect prepareToDraw];
    glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_BYTE, 0);
}

これで、上の画像の真ん中のように、青いグラデーションで左下の三角形が描画されたと思います。

画面全体を描画

この三角形をベースに画面全体を描画してみましょう。

const Vertex gVertices[] = {  
    {-1, -1,  0},
    { 1, -1,  0},
    {-1,  0,  0},
    { 1,  0,  0},
    {-1,  1,  0},
    { 1,  1,  0}
};

Color gColors[] = {  
    {0, 1, 1, 1},
    {0, 1, 1, 1},
    {0, 0, 1, 1},
    {1, 1, 1, 1},
    {1, 0, 1, 1},
    {1, 1, 1, 1}
};

const GLubyte gIndices[] = {  
    0, 1, 2, 3, 4, 5
};

とし、glDrawElementsの頂点数を6にします。

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Render the object with GLKit
    [self.effect prepareToDraw];
    glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_BYTE, 0);
}

これで上の画像の右のように画面全体がグラデーションで描画されたかと思います。コードは

github:ESFullColorBossa

に置きましたので、興味のあるかたは是非どうぞ。次回はQuartzComposerのPatchにあたる機能を実装して、今回の描画機能とつなげてみたいと思います。