OpenGL を利用して立方体を描こう!

一番最初の OpenGL 利用例として 3D な立方体を描いてみましょう!

基本的なアプリケーション構成についてはわかっていることとしていますので、 必要なら最初に Android で OpenGL を利用する方法 をチェックしておいてください。

OpenGL を利用する基本構成

まずはこちらを参考にして、基本的な OpenGL プログラムとして View, Renderer などの要素を確認しておいてください。

ではコードを書いていきましょう。 この後みていきますが、大半が Android のプログラムのため、というよりは OpenGL 的なものであることがわかるはずです。

まずは座標、フラスタム、ビューポートとは何か?ということ・・・

3次元の物体の位置を指定するためには、なんらかの座標が必要になりますよね。 まず座標を次のように決めます。

座標

この座標の中に物体を定義してそれをみるわけですが、どこに物体をおいたものをみるのか?ということを決めるのがフラスタム (frustum) の設定です。 フラスタムは 「表示可能空間領域」 などともいいます。

座標

フラスタムは視線の方向は -z 方向を向いているとして、 そこからみる角度と、近い側の切り取り面、遠い側の切り取り面で決まります。

上の図のフラスタムで近い側の面を通して、フラスタムをみることになりますが、この面をビューポート (viewport) といいます。

OpenGL でビューポートとフラスタムを決める

glViewport は正規化されたデバイス座標から Window 座標へのアフィン変換 (affine transformation) を指定します。 アフィン変換というのは、回転、拡大縮小、剪断といった線形変換と平行移動の組み合わせのことです。

座標

glViewport(x, y, width, height) を呼び出すとビューポートを、 デバイス座標の 左下 (x,y) から幅 width、高さ height に (アフィン変換で) マップします。

ある領域の図形をことなる領域にマップするわけですから、縦横比が異なってしまうと、上の挿絵のスマイルフェイスのように、形が歪んでしまいます。このため、 歪まないようにするためには、フラスタムの縦横比 (aspect ratio) を、実際のデバイス座標のそれと合わせます。

デバイスのサイズや縦横の向きが変わったときは、onSurfaceChanged が呼び出されます。 それに合わせて、フラスタムとビューポートを表示が歪まないように更新するなら次のようにします。

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);
    
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();    
    GLU.gluPerspective(gl, 45f,(float) width / height, 1f, 50f);
  }

onSurfaceCreated では Depth Test を有効にしておきます。

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glDepthFunc(GL10.GL_LEQUAL);
  }

立方体の定義は後ほど示しますので、ここでは立方体が MyCube というクラスで定義されるということを前提にします。

描画するコードは Renderer の onDrawFrame に記述します。

package com.keicode.android.test;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLU;
import android.opengl.GLSurfaceView.Renderer;

public class MyRenderer implements Renderer {

  MyCube cube = new MyCube();
  
  @Override
  public void onDrawFrame(GL10 gl) {
    
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT 
        | GL10.GL_DEPTH_BUFFER_BIT);
    
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glTranslatef(0, 0, -3f);
    
    cube.draw(gl);
    
  }

  ...

glClear でバッファをクリアして、 glTranslate で物体の座標を全体的に -z 軸方向に 3 だけずらします。 デフォルトの視線は、 -z 方向で原点にあるのでしたね。だから、その物体をみるには少しずらさないといけないのです。

MyCube オブジェクトの draw メソッドを呼ぶことで最終的に描画しています。

立方体の定義と描画コード

上で既に MyCube オブジェクトを利用するコードを書いていますが、実際にその部分を書いてみましょう。

package com.keicode.android.test;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class MyCube {
  
  private final FloatBuffer mVertexBuffer;
  
  public MyCube(){
    
    float vertices[] = {
      // 前
      -0.5f, -0.5f, 0.5f,
      0.5f, -0.5f, 0.5f,
      -0.5f, 0.5f, 0.5f,
      0.5f, 0.5f, 0.5f,
      
      // 後
      -0.5f, -0.5f, -0.5f,
      0.5f, -0.5f, -0.5f,
      -0.5f, 0.5f, -0.5f,
      0.5f, 0.5f, -0.5f,
      
      // 左
      -0.5f, -0.5f, 0.5f,
      -0.5f, -0.5f, -0.5f,
      -0.5f, 0.5f, 0.5f,
      -0.5f, 0.5f, -0.5f,
      
      // 右
      0.5f, -0.5f, 0.5f,
      0.5f, -0.5f, -0.5f,
      0.5f, 0.5f, 0.5f,
      0.5f, 0.5f, -0.5f,
      
      // 上
      -0.5f, 0.5f, 0.5f,
      0.5f, 0.5f, 0.5f,
      -0.5f, 0.5f, -0.5f,
      0.5f, 0.5f, -0.5f,
      
      // 底
      -0.5f, -0.5f, 0.5f,
      0.5f, -0.5f, 0.5f,
      -0.5f, -0.5f, -0.5f,
      0.5f, -0.5f, -0.5f
    };
    
    ByteBuffer vbb = 
      ByteBuffer.allocateDirect(vertices.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    mVertexBuffer = vbb.asFloatBuffer();
    mVertexBuffer.put(vertices);
    mVertexBuffer.position(0);
    
  }

  public void draw(GL10 gl){
    
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
    
    // Front
    gl.glNormal3f(0, 0, 1.0f);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
    
    // Back
    gl.glNormal3f(0, 0, -1.0f);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
    
    // Left
    gl.glNormal3f(-1.0f, 0, 0);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);
  
    // Right
    gl.glNormal3f(1.0f, 0, 0);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);
  
    // Top
    gl.glNormal3f(0, 1.0f, 0);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
  
    // Right
    gl.glNormal3f(0, -1.0f, 0);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
    
  }
}

ここでは中心が座標の原点にある、辺の長さが 1 である立方体を定義しています。

座標は (x,y,z) がひとまとまりで、1点を表しています。どの座標をどんな順番で定義しているか追ってもらえれば何をしているか分かると思います。

以上のコードを書いたら、いよいよ OpenGL でレンダリング可能です。さっそく実行してみましょう!

座標

ただの正方形・・・、まぁ、考えてみれば座標の原点に配置した立方体をまっすぐにみているので、ただの正方形になりますよね・・・。 だからこれはこれであっているのですが、わざわざ 3D で描画した意味がないですよね(苦笑)

もう少し3Dであることがわかるように、直してみましょう。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Android 開発入門