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

テスト用のプログラムですが、描画速度の変化が見やすいように動くようにしてみました :D

モデルを動かすには座標変換を行うための行列を使うことになるのですが、OpenGL ES2では行列関連のAPIがごっそり消えていますので必要なものはアプリで用意する必要があります。ここではPinball Tristan用に作った行列ライブラリを使う事にします。最小限の機能しかありませんが、この方が単純で読みやすいかもしれません :)

/*
 *  Matrix.h
 *
 *  Created by fujita-y
 *  Copyright 2010 LittleWing Co. Ltd. All rights reserved.
 *
 */

/*
    m[ 0] m[ 1] m[ 2] m[ 3]
    m[ 4] m[ 5] m[ 6] m[ 7]
    m[ 8] m[ 9] m[10] m[11]
    m[12] m[13] m[14] m[15]

    mm[0][0] mm[0][1] mm[0][2] mm[0][3]
    mm[1][0] mm[1][1] mm[1][2] mm[1][3]
    mm[2][0] mm[2][1] mm[2][2] mm[2][3]
    mm[3][0] mm[3][1] mm[3][2] mm[3][3]
*/

#pragma once
#ifndef __MATRIX_H__
#define __MATRIX_H__

#include <math.h>
#include <string.h>
#include <assert.h>

#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>

class Matrix4D {
public:

    union {
        GLfloat m[16];
        GLfloat mm[4][4];
    };

private:
    void clear() {
        m[ 0] = m[ 1] = m[ 2] = m[ 3] =
        m[ 4] = m[ 5] = m[ 6] = m[ 7] =
        m[ 8] = m[ 9] = m[10] = m[11] =
        m[12] = m[13] = m[14] = m[15] = 0.0f;
    }
    
    float normalize(float& x, float& y, float& z) {
        float d = sqrtf(x * x + y * y + z * z);
        if (d == 0.0f) return 0.0f;
        x /= d;
        y /= d;
        z /= d;
        return d;
    }
      
    bool lu_decomposition(Matrix4D& mat, int* idx) {
        for (int i = 0; i < 4; i++) idx[i] = i;        
        float weight[4];
        for (int i = 0; i < 4; i++) {
            float u = 0.0f;
            for (int j = 0; j < 4; j++) {
                float t = fabsf(mat.mm[i][j]);
                if (t > u) u = t;
            }
            if (u == 0.0f) return false;
            weight[i] = 1.0f / u;
        }
        for (int k = 0; k < 4; k++) {
            float u = fabsf(mat.mm[idx[k]][k]) * weight[idx[k]];
            int j = k;
            for (int i = k + 1; i < 4; i++) {
                int ii = idx[i];
                float t = fabsf(mat.mm[ii][k]) * weight[ii];
                if (t > u) {
                    u = t;
                    j = i;
                }
            }
            if (j != k) {
                int t = idx[j];
                idx[j] = idx[k];
                idx[k] = t;
            }
            int ik = idx[k];
            u = mat.mm[ik][k];
            if (u == 0.0) return false;
            for (int i = k + 1; i < 4; i++) {
                int ii = idx[i];
                float t = mat.mm[ii][k] / u;
                mat.mm[ii][k] = t;
                for (int j = k + 1; j < 4; j++) mat.mm[ii][j] -= mat.mm[ik][j] * t;
            }
        }
        return true;
    }

public:

    bool operator== (const Matrix4D& rhs) const { 
        return (this == &rhs) || (memcmp(m, rhs.m, sizeof(m)) == 0);
    }
    
    bool operator!= (const Matrix4D& rhs) const { 
        return (this != &rhs) && (memcmp(m, rhs.m, sizeof(m)) != 0);
    }

    void setTranspose(const Matrix4D& src) {
        assert(this != &src);
        m[ 0] = src.m[ 0];
        m[ 1] = src.m[ 4];
        m[ 2] = src.m[ 8];
        m[ 3] = src.m[12];
        m[ 4] = src.m[ 1];
        m[ 5] = src.m[ 5];
        m[ 6] = src.m[ 9];
        m[ 7] = src.m[13];
        m[ 8] = src.m[ 2];
        m[ 9] = src.m[ 6];
        m[10] = src.m[10];
        m[11] = src.m[14];
        m[12] = src.m[ 3];
        m[13] = src.m[ 7];
        m[14] = src.m[11];
        m[15] = src.m[15];
    }

    void setInverse(const Matrix4D& src) {
        assert(this != &src);
        Matrix4D lu(src);
        int idx[4];
        if (lu_decomposition(lu, idx)) {
            for (int k = 0; k < 4; k++) {
                for (int i = 0; i < 4; i++) {
                    int ii = idx[i];
                    float t = (ii == k) ? 1.0f : 0.0f;
                    for (int j = 0; j < i; j++) t -= lu.mm[ii][j] * mm[j][k];
                    mm[i][k] = t;
                }
                for (int i = 3; i >= 0; i--) {
                    int ii = idx[i];
                    float t = mm[i][k];
                    for (int j = i + 1; j < 4; j++) t -= lu.mm[ii][j] * mm[j][k];
                    mm[i][k] = t / lu.mm[ii][i];
                }
            }
            return;                    
        }
        setIdentity();           
    }
    
    void setMul(const Matrix4D& lhs, const Matrix4D& rhs) {
        assert(this != &lhs);
        assert(this != &rhs);
        for ( int i = 0; i < 4; i++ ) 
            for ( int j = 0; j < 4; j++ )
                mm[ i ][ j ] = lhs.mm[ 0 ][ j ] * rhs.mm[ i ][ 0 ]
                             + lhs.mm[ 1 ][ j ] * rhs.mm[ i ][ 1 ]
                             + lhs.mm[ 2 ][ j ] * rhs.mm[ i ][ 2 ]
                             + lhs.mm[ 3 ][ j ] * rhs.mm[ i ][ 3 ];
                    
    }    

    void setIdentity() {
        clear();
        m[0] = m[5] = m[10] = m[15] = 1.0f;
    }

    void setTranslate(GLfloat x, GLfloat y, GLfloat z) {
        setIdentity();
        m[12] = x;
        m[13] = y;
        m[14] = z;
    }
    
    void setRotate(GLfloat degree, GLfloat x, GLfloat y, GLfloat z) {
        float radian = degree * 3.141593f / 180.0f;
        float c = cos(radian);
        float s = sin(radian);
        normalize(x, y, z);
        m[ 0] = x * x + (1.0f - x * x) * c;
        m[ 1] = x * y * (1.0f - c) + z * s;
        m[ 2] = x * z * (1.0f - c) - y * s;
        m[ 3] = 0.0f;
        m[ 4] = x * y * (1.0f - c) - z * s;
        m[ 5] = y * y + (1.0f - y * y) * c;
        m[ 6] = y * z * (1.0f - c) + x * s;
        m[ 7] = 0.0f;
        m[ 8] = x * z * (1.0f - c) + y * s;
        m[ 9] = y * z * (1.0f - c) - x * s;
        m[10] = z * z + (1.0f - z * z) * c;
        m[11] = 0.0f;
        m[12] = 0.0f;
        m[13] = 0.0f;
        m[14] = 0.0f;
        m[15] = 1.0f;
    }

    void setScale(float x, float y, float z) {
        clear();
        m[ 0] = x;
        m[ 5] = y;
        m[10] = z;
        m[15] = 1.0f;
    }

    void setFrustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar) {
        clear();
        m[ 0] = (2.0f * zNear) / (right - left);
        m[ 5] = (2.0f * zNear) / (top - bottom);
        m[ 8] = (right + left) / (right - left);
        m[ 9] = (top + bottom) / (top - bottom);
        m[10] = -(zFar + zNear) / (zFar - zNear);
        m[11] = -1.0f;
        m[14] = -(2.0f * zNear * zFar) / (zFar - zNear);
    }
    
    void setOrtho(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar) {
        clear();
        m[ 0] = 2.0f / (right - left);
        m[ 5] = 2.0f / (top - bottom);
        m[10] = -2.0f / (zFar - zNear);
        m[12] = -(right + left) / (right - left);
        m[13] = -(top + bottom) / (top - bottom);
        m[14] = -(zFar + zNear) / (zFar - zNear);
        m[15] = 1.0f;
    }

    void setLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, GLfloat centery, GLfloat centerz, GLfloat upx, GLfloat upy, GLfloat upz) {
        GLfloat z0 = eyex - centerx;
        GLfloat z1 = eyey - centery;
        GLfloat z2 = eyez - centerz;
        normalize(z0, z1, z2); 
        GLfloat y0 = upx;
        GLfloat y1 = upy;
        GLfloat y2 = upz;
        GLfloat x0 =  y1 * z2 - y2 * z1;
        GLfloat x1 = -y0 * z2 + y2 * z0;
        GLfloat x2 =  y0 * z1 - y1 * z0;
        y0 =  z1 * x2 - z2 * x1;
        y1 = -z0 * x2 + z2 * x0;
        y2 =  z0 * x1 - z1 * x0;
        normalize(x0, x1, x2);
        normalize(y0, y1, y2);        
        m[0] = x0;
        m[1] = y0;
        m[2] = z0;
        m[3] = 0.0;
        m[4] = x1;
        m[5] = y1;
        m[6] = z1;
        m[7] = 0.0;
        m[8] = x2;
        m[9] = y2;
        m[10] = z2;
        m[11] = 0.0;
        m[12] = 0.0;
        m[13] = 0.0;
        m[14] = 0.0;
        m[15] = 1.0;
        translate(-eyex, -eyey, -eyez);
    }

    void mul(const Matrix4D& rhs) {
        Matrix4D lhs(*this);
        setMul(lhs, rhs);
    }

    void scale(GLfloat x, GLfloat y, GLfloat z) {
        Matrix4D lhs(*this);
        Matrix4D rhs;
        rhs.setScale(x, y, z);
        setMul(lhs, rhs);
    }

    void rotate(GLfloat degree, GLfloat x, GLfloat y, GLfloat z) {
        Matrix4D lhs(*this);
        Matrix4D rhs;
        rhs.setRotate(degree, x, y, z);
        setMul(lhs, rhs);
    }

    void translate(GLfloat x, GLfloat y, GLfloat z) {
        Matrix4D lhs(*this);
        Matrix4D rhs;
        rhs.setTranslate(x, y, z);
        setMul(lhs, rhs);
    }

    void inverse() {
        Matrix4D lhs(*this);
        setInverse(lhs);
    }

    void transpose() {
        Matrix4D lhs(*this);
        setTranspose(lhs);
    }
    
    void lookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, GLfloat centery, GLfloat centerz, GLfloat upx, GLfloat upy, GLfloat upz) {
        Matrix4D lhs(*this);
        Matrix4D rhs;
        rhs.setLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz);
        setMul(lhs, rhs);
    }

};

struct Matrix3D {

    union {
        GLfloat m[9];
        GLfloat mm[3][3];
    };

    Matrix3D() {}

    Matrix3D(const Matrix4D& rhs) {
        m[0] = rhs.m[0];
        m[1] = rhs.m[1];
        m[2] = rhs.m[2];
        m[3] = rhs.m[4];
        m[4] = rhs.m[5];
        m[5] = rhs.m[6];
        m[6] = rhs.m[8];
        m[7] = rhs.m[9];
        m[8] = rhs.m[10];    
    }

    void set(const Matrix4D& rhs) {
        m[0] = rhs.m[0];
        m[1] = rhs.m[1];
        m[2] = rhs.m[2];
        m[3] = rhs.m[4];
        m[4] = rhs.m[5];
        m[5] = rhs.m[6];
        m[6] = rhs.m[8];
        m[7] = rhs.m[9];
        m[8] = rhs.m[10];    
    }

};

struct Vector3D {

    GLfloat vec[3];
    
    Vector3D() {}
    
    Vector3D(GLfloat v0, GLfloat v1, GLfloat v2) {
        vec[0] = v0;
        vec[1] = v1;
        vec[2] = v2;
    }
    
    void setMul(const Matrix3D& lhs, const Vector3D& rhs) {
        assert(this != &rhs);
        for ( int i = 0; i < 3; i++ )
            vec[i] = lhs.mm[0][i] * rhs.vec[0]
                   + lhs.mm[1][i] * rhs.vec[1]
                   + lhs.mm[2][i] * rhs.vec[2];
    }
    
    void setCrossProduct(const Vector3D& lhs, const Vector3D& rhs) {
        vec[0] = lhs.vec[1] * rhs.vec[2] - lhs.vec[2] * rhs.vec[1];
        vec[1] = lhs.vec[2] * rhs.vec[0] - lhs.vec[0] * rhs.vec[2];
        vec[2] = lhs.vec[0] * rhs.vec[1] - lhs.vec[1] * rhs.vec[0];
    }
    
    GLfloat magnitude() {
        return sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
    }
    
    void normalize() {
        GLfloat m = 1.0 / magnitude();
        vec[0] *= m;
        vec[1] *= m;
        vec[2] *= m;
    }
    
    static GLfloat dotProduct(const Vector3D& lhs, const Vector3D& rhs) {
        return lhs.vec[0] * rhs.vec[0] + lhs.vec[1] * rhs.vec[1] + lhs.vec[2] * rhs.vec[2];
    }
    
};

struct Vector4D {

    GLfloat vec[4];

    Vector4D() {}
    
    Vector4D(GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
        vec[0] = v0;
        vec[1] = v1;
        vec[2] = v2;
        vec[3] = v3;
    }
    
    void setMul(const Matrix4D& lhs, const Vector4D& rhs) {
        assert(this != &rhs);
        for ( int i = 0; i < 4; i++ )
            vec[i] = lhs.mm[0][i] * rhs.vec[0]
                   + lhs.mm[1][i] * rhs.vec[1]
                   + lhs.mm[2][i] * rhs.vec[2]
                   + lhs.mm[3][i] * rhs.vec[3];
    }
    
    GLfloat magnitude() {
        return sqrtf(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2] + vec[3] * vec[3]);
    }
    
    void normalize() {
        GLfloat m = 1.0 / magnitude();
        vec[0] *= m;
        vec[1] *= m;
        vec[2] *= m;
        vec[3] *= m;
    }

};

#endif

次にNotepadSampleViewController.mmに変更を加えます。NotepadSampleViewControllerの拡張子が.mmになっていることに注意してください。これはインポートするMatrix.hがC++で書いてあるのでObjective-C++でコンパイルしなければならないためです。

:

#import "Matrix.h" // これを上の方で追加

:

enum {
    UNIFORM_ROTATE, // 元のプログラムではUNIFORM_TRANSLATEとなっています
    NUM_UNIFORMS
};

:

- (void)drawFrame // 回転行列を計算してuniform変数に書き込むように書き換えます
{
    [(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 = (GLfloat *)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();
        }
    }
    
    // モデルの回転行列をつくります
    static GLfloat x_degree;
    static GLfloat y_degree;
    if ((x_degree = x_degree + 1.0) > 360.0) x_degree -= 360.0;
    if ((y_degree = y_degree + 2.0) > 360.0) y_degree -= 360.0;
    Matrix4D rotate;
    rotate.setIdentity();
    rotate.rotate(x_degree, 1.0, 0.0, 0.0);
    rotate.rotate(y_degree, 0.0, 1.0, 0.0);

    glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(program);
    
    // uniform変数に回転行列をセット
    glUniformMatrix4fv(uniforms[UNIFORM_ROTATE], 1, GL_FALSE, rotate.m);

    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, vertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    
    glDrawArrays(GL_TRIANGLES, 0, numVertices);
    
    [(EAGLView *)self.view presentFramebuffer];
}

:

    // uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");
    uniforms[UNIFORM_ROTATE] = glGetUniformLocation(program, "rotate"); // uniform変数の場所を取得

最後にバーテクスシェーダShader.vshを書き換えて回転行列を適用するようにします

uniform mat4 rotate; // モデルの回転行列
attribute vec4 position;
varying vec4 colorVarying;

void main()
{
    gl_Position = rotate * position; // 座標変換
    colorVarying = abs(position);
}

Xcode4のプロジェクトをダウンロードできるようにしましたので、どうぞお試しください :)
http://www.littlewing.jp/blog/NotepadSample_20110714.zip