Hatena::ブログ(Diary)

Y.FUJITA::NOTEPAD::YPSILON このページをアンテナに追加 RSSフィード

2011-07-11

iPhone/iPadでゲームを開発しよう - その3

| 14:50 | iPhone/iPadでゲームを開発しよう - その3 を含むブックマーク iPhone/iPadでゲームを開発しよう - その3 のブックマークコメント

まずは簡単にトライアングルを1万枚ほどレンダリングするサンプルプログラムを作ってみましょう :D

f:id:fujita-y:20110711132029p:image

Xcode4のOpenGL ES Applicationテンプレートに以下の変更を行います。

== EAGLView.h

@interface EAGLView : UIView {
@private
    // The pixel dimensions of the CAEAGLLayer.
    GLint framebufferWidth;
    GLint framebufferHeight;
    
    // The OpenGL ES names for the framebuffer and renderbuffer used to render to this view.
    GLuint defaultFramebuffer, colorRenderbuffer;

    // @fujita-y 深度バッファ用定義をここに追加
    GLuint depthRenderbuffer; 
    // @fujita-y end
}

== EAGLView.m

- (void)createFramebuffer
{
    if (context && !defaultFramebuffer) {
        [EAGLContext setCurrentContext:context];
        
        // Create default framebuffer object.
        glGenFramebuffers(1, &defaultFramebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
        
        // Create color render buffer and allocate backing store.
        glGenRenderbuffers(1, &colorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
        [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);
        
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

        // @fujita-y 深度バッファの作成をここに追加
        glGenRenderbuffers(1, &depthRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, framebufferWidth, framebufferHeight); 
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
        // @fujita-y end

        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
}

- (void)deleteFramebuffer
{
    if (context) {
        [EAGLContext setCurrentContext:context];
        
        if (defaultFramebuffer) {
            glDeleteFramebuffers(1, &defaultFramebuffer);
            defaultFramebuffer = 0;
        }
        
        if (colorRenderbuffer) {
            glDeleteRenderbuffers(1, &colorRenderbuffer);
            colorRenderbuffer = 0;
        }
        
        // @fujita-y 深度バッファのデリートをここに追加
        if (depthRenderbuffer) {
            glDeleteRenderbuffers(1, &depthRenderbuffer);
            depthRenderbuffer = 0;
        }
        // @fujita-y end
        
    }
}

== notepad_sampleViewController.m(このファイル名は作成したプロジェクトの名前+"ViewController.m"になります)

// @fujita-y GLfloatの乱数その1
static GLfloat random_zero_one() // 0.0 .. 1.0
{
    return (float)rand() / (float)RAND_MAX;
}

// @fujita-y GLfloatの乱数その2
static GLfloat random_delta() // -0.025 .. 0.025
{
    return (random_zero_one() - 0.5) * 0.5;
}

// @fujita-y drawFrameを書き換え(OpenGL ES2必須)
- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
    
    static GLfloat *vertices;
    const int numTriangles = 10000;
    const int numVertices = numTriangles * 3;
    const int numFloats = numVertices * 3;
    
    // ここで GLfloat *vertices に三角板のデータを用意します。作成するのは最初の一回だけです。
    if (vertices == NULL) {
        vertices = malloc(sizeof(GLfloat) * numFloats);
        for (int i = 0; i < numTriangles; i++) {
            GLfloat cx = random_zero_one() - 0.5;
            GLfloat cy = random_zero_one() - 0.5;
            GLfloat cz = random_zero_one() - 0.5;
            // vertex 1
            vertices[i * 9 + 0] = cx + random_delta();
            vertices[i * 9 + 1] = cy + random_delta(); 
            vertices[i * 9 + 2] = cz + random_delta();
            // vertex 2
            vertices[i * 9 + 3] = cx + random_delta();
            vertices[i * 9 + 4] = cy + random_delta(); 
            vertices[i * 9 + 5] = cz + random_delta();
            // vertex 3
            vertices[i * 9 + 6] = cx + random_delta();
            vertices[i * 9 + 7] = cy + random_delta(); 
            vertices[i * 9 + 8] = cz + random_delta();
        }
    }

    glClearColor(0.2f, 0.1f, 0.2f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(program);
    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, vertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glDrawArrays(GL_TRIANGLES, 0, numVertices);
    
    [(EAGLView *)self.view presentFramebuffer];
}

== Shader.vsh

attribute vec4 position;
varying vec4 colorVarying;

void main()
{
    gl_Position = position;
    colorVarying = abs(gl_Position);
}

で、これをiPod touch 3Gで実行するとCoreAnimationのフレームレートが30fps・・・この段階では目眩のするような遅さです・・・が、最終的には下のような1万5千トライアングルのモデルにアンチエイリアスをかけても60fpsでグリグリ回るようになるのです :)

f:id:fujita-y:20110711143223p:image

親うずら親うずら 2011/11/10 00:22 サンプルでは平均すると1ポリゴンあたりの面積が広く、しかも重なりが多いためにPowerVRの不得意な条件だったのではないでしょうか?
続きを楽しみにしております。

fujita-yfujita-y 2011/11/10 09:03 コメントありがとうございます。新タイトルの開発に忙しく、なかなか続きが書けなくて申し訳ないです。ところで「しかも重なりが多いためにPowerVRの不得意な条件だったのではないでしょうか?」とのご意見は鋭いです:D。PowerVRはタイルベース遅延レンダリングという変わった方法を使いますが、このタイルのサイズはかなり小さいです。そこでポリゴンを分割して細かくした同じ形のモデルと比較を行ってみたことがあります。そしてモデルの形状によってはポリゴンの枚数が増えても細かく分割した方がレンダリングが速くなることがわかっています(普通はこの方法は推奨されません)ただ、今回の結果が遅かったのは「Vertex Buffer Objectを使わない場合にiPhoneのドライバーではパフォーマンスが大幅に低下する」という特性の影響が大きいです。その辺りは時間ができたら続編で書きたいと思っています m(_ _)m

親うずら親うずら 2011/11/10 12:10 こちらこそご回答ありがとうございました、
VBOをはじめ、データの最適化など様々な苦労(楽しみ?)があっての高速化なのですね。
お時間できたら続編をお願いいたします。