« Android Market で有料アプリが1ヶ月でどれくらい売れたか | メイン | 球の描画2 »

2011年06月12日

3D プログラミング:: 球の描画1

    

球の描画は、主に2パターンの方法があると思う。
1. 地球儀のような、緯度と経度で割って描画する方法。
2. 正二十面体を、さらに割って描画する方法。

1 の方法は赤道付近でポリゴンが荒く、北極・南極に近づくと細かくなっていき効率が悪い。
ただ、プログラムで球を描くケースはゲーム画面内ではほとんどない。
単にデバッグ時に当たり描画用に出す程度ではないかと思う。

とりあえず、今回は地球儀のような方法で描画してみる。
以下ソースはJava ( JOGL 2.0 ) 。

まずトライアングルストリップで描ける中間部分を描画し、後で上下端をトライアングルファンで描画している。
三角関数がわかればそれほど難しくないと思う。
加法定理を使っているのは、何度も sin/cos を計算するコストを減らすため。
上下端を別に分けているが、頂点のリストを作って後で描画するのなら、まとめてやってしまった方が加法定理の計算部分うかせられる。
そもそも毎回計算して描かなくても、頂点のリストを1度作って、マトリックスで拡大縮小すれば済む話でもあるんだけど。

public void draw( Sphere shape, GL2 gl ) {
    gl.glTranslatef( shape.center.x, shape.center.y, shape.center.z ); // 原点を中心座標に移動

    final int div_h = 40;      // 水平方向分割数
    final int div_v = div_h/2; // 垂直方向分割数
    final float theta = (float) (Math.PI * 2.0 / div_h);
    final float sinTheta = (float) Math.sin( theta );
    final float cosTheta = (float) Math.cos( theta );
    float radius = shape.radius; // 球の半径

    // 中間の帯の部分はトライアングルストリップで描ける
    float accu_v_s = sinTheta; // 垂直方向 sin
    float accu_v_c = cosTheta; // 垂直方向 cos
    float ru = radius * accu_v_s; // 現theta値で水平スライスした時の半径
    float yu = radius * accu_v_c; // 垂直方向Y軸値
    for( int y = 0; y < (div_v-2); y++ ) {

        // 加法定理によって、垂直方向のsin/cos値を更新する
        float tmp = cosTheta * accu_v_c - sinTheta * accu_v_s;
        accu_v_s = sinTheta * accu_v_c + cosTheta * accu_v_s;
        accu_v_c = tmp;

        float rd = radius * accu_v_s; // 1個後の半径
        float yd = radius * accu_v_c; // 1個後の垂直方向Y軸値

        float accu_s = sinTheta;    // 水平方向sin
        float accu_c = cosTheta;    // 水平方向cos
        gl.glBegin( GL.GL_TRIANGLE_STRIP );
        for( int x = 0; x <= div_h; x++ ) {
            gl.glVertex3f( rd * accu_c, yd, rd * accu_s );
            gl.glVertex3f( ru * accu_c, yu, ru * accu_s );

            // 加法定理によって、水平方向のsin/cos値を更新する
            tmp = cosTheta * accu_c - sinTheta * accu_s;
            accu_s = sinTheta * accu_c + cosTheta * accu_s;
            accu_c = tmp;
        }
        gl.glEnd();
        ru = rd;
        yu = yd;
    }

    // 上半球の描画
    gl.glBegin( GL.GL_TRIANGLE_FAN );
    gl.glVertex3f( 0.0f, radius, 0.0f );
    final float cap_r = radius * sinTheta; // 半径
    final float cap_y_u = radius * cosTheta; // 上半球Y軸値
    float accu_s = sinTheta;    // 水平方向sin
    float accu_c = cosTheta;    // 水平方向cos
    for( int i = 0; i <= div_h; i++ ) {
        gl.glVertex3f( cap_r * accu_c, cap_y_u, cap_r * accu_s );

        // 加法定理によって、水平方向のsin/cos値を更新する
        float tmp = cosTheta * accu_c - sinTheta * accu_s;
        accu_s = sinTheta * accu_c + cosTheta * accu_s;
        accu_c = tmp;
    }
    gl.glEnd();

    // 下半球の描画
    gl.glBegin( GL.GL_TRIANGLE_FAN );
    gl.glVertex3f( 0.0f, -radius, 0.0f );
    final float cap_y_d = -cap_y_u; // 下半球Y軸値
    for( int i = 0; i <= div_h; i++ ) {
        gl.glVertex3f( cap_r * accu_c, cap_y_d, cap_r * accu_s );

        // 加法定理によって、水平方向のsin/cos値を更新する ( 逆回転 )
        float tmp = accu_c * cosTheta + accu_s * sinTheta;
        accu_s = accu_s * cosTheta - accu_c * sinTheta;
        accu_c = tmp;
    }
    gl.glEnd();
}

で、上記ソースをコンパイルしてアプレットにしたものをここに置く
分割数が多いのでそれなりに綺麗。



投稿者 Takenori : 2011年06月12日 01:49




comments powered by Disqus
Total : Today : Yesterday : なかのひと