glBegin/glEndが消えた訳/頂点配列ヘルパークラス

やあ子供たち元気にしてたかな。今日はOpenGLESでちょっとした形状を表示させたいときに便利なヘルパークラスの紹介だよ。OpenGLESと言えばiPhoneAndroidアプリ開発なんかにおける、glBegin/glEndがホントに使えなくなてしまったという冗談みたいな状況がホントになってしまっている環境だね。
たとえば形状決め打ちの三角ポリゴン一枚描画するのにglBegin/glEndを使えば色指定を含めたところで7行もあれば描画できてしまうところを、それが使えない最新のOpenGL仕様やOpenGLES/ES2環境においては、何やら配列変数を複数本定義したり状態変数を有効化/無効化したりと、とてもじゃないが数行でできる世界ではなくなってしまっているんだ。
これはドライバ実装上の都合やパフォーマンスの観点からすれば、歓迎すべきことではあるのだけれども、上記のようなちょっとした形状の作成と表示がとてもやりにくくなってしまった。
これは何とも残念なことなんだ。というのもOpenGLは世の中のあらゆる分野目的で使われているし、パフォーマンスなんて二の次で、簡単な形状の描画で十分だったり、デバッグとして何かを表示させたいだけの場合などもたくさんあるはずで、何も重たい立派な3Dデータばかり表示させたいというわけではないのだからね。
でも何でそんな便利なものがなくなっちゃったんだろうか。おじさんの理解しているところでいくと、グラフィックハードの製造会社は、経験の浅い開発者に、ハードの特性を極小化するような下手なコードや、仕様外のあり得ない描画コードを書かれることで(あるいは何かを書き忘れたりとか)、ある特定のソフトやデータ処理においてハード本来のパフォーマンスが出ずに、他社のデバイスより劣ると不当評価されてしまうという事態をとても嫌っている。
なのでAPIの中の曖昧な仕様の撲滅や、あまりに人間の書きやすさ側に振れてしまっているがために、オプティマイズのためのドライバ側での無駄な推測処理をめぐって、競合他社とのやや本質から外れた不毛な競争を余儀なくさせる原因となってきた困った仕様撲滅ための働きかけをしているわけで、glBegin/glEndなどはその筆頭だったのかも知れない。(同一のマテリアルが複数にわたって繰り返し定義されていればそれを一カ所にまとなきゃなんてこと一つ考えてみればその効率化の厄介さは明らかだろう)
彼らにとって、glBegin/glEndを使って書かれた描画処理を「そこそこに」オプティマイズする処理を考えるのはわけないことだが、最善のやり方が一つではなかったのだった。そんな永遠のテーマみたいな暇そうな難問はどっかの大学の研究室で永久に研究されてればよさそうな話で、ここでベンダーが最善をつくすのを強いられるのは違うだろうと。俺たちベンダーどうしが争う土俵はここじゃないよねと。ハード開発の本質から外れるだろうと。同じマテリアルアトリビュートの頂点をひとまとめにして呼ぶなんざAPIのユーザー側で考えればいいことじゃないのか、そもそもなんでそんなことまで俺たちが考えなきゃいけない、標準APIにあるから問題なんだということになってそれはついにOpenGLそのものから姿を消したという事情だと思う。
そう、つまりハード開発側としては、glBegin/glEndの動作保証ができないんじゃなくて、それを保証する責任を自らから外したかったのだろう。
なのでglBegin/glEnd的な感じで作りたいんだったらそのためのしくみは下々の末端ユーザー側のコードでいくらでも勝手にできることだよね、っていうわけだ。そこでおじさんは以下のようなものを自前で用意せざるを得なかったというわけだよ。(使い方は以下コードの後に)

#import <opengl/opengl.h>
#import <opengl/gl.h>
#import <opengl/glext.h>

class pocVertexArrayHelper
{
public:
	pocVertexArrayHelper( void ){}
	virtual ~pocVertexArrayHelper( void ){}
	
	void DrawVCT( GLenum primitive_type ){
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY );		
		glVertexPointer( 3, GL_FLOAT, 0, _varr.data());
		glColorPointer( 4, GL_UNSIGNED_BYTE, 0, _carr.data() );
		glTexCoordPointer( 2, GL_FLOAT, 0, _tarr.data() );
		glDrawArrays( primitive_type, 0, _varr.size()/3 );	
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY );
	}
	void DrawVC( GLenum primitive_type ){
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glVertexPointer( 3, GL_FLOAT, 0, _varr.data());
		glColorPointer( 4, GL_UNSIGNED_BYTE, 0, _carr.data() );
		glDrawArrays( primitive_type, 0, _varr.size()/3 );	
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
	}
	void Vertex3f( const float x, const float y, const float z ){
		_varr.push_back( x ); _varr.push_back( y ); _varr.push_back( z );
		_narr.push_back( _nx ); _narr.push_back( _ny ); _narr.push_back( _nz );
		_carr.push_back( _cr ); _carr.push_back( _cg ); _carr.push_back( _cb ); _carr.push_back( _ca );
		_tarr.push_back( _tu ); _tarr.push_back( _tv );
	}
    void Vertex3fv( const float* v ){
        _varr.insert( _varr.end(), v, v+3 );
		_narr.push_back( _nx ); _narr.push_back( _ny ); _narr.push_back( _nz );
		_carr.push_back( _cr ); _carr.push_back( _cg ); _carr.push_back( _cb ); _carr.push_back( _ca );
		_tarr.push_back( _tu ); _tarr.push_back( _tv );        
    }
	void Normal3f( const float nx, const float ny, const float nz ){
		_nx = nx; _ny = ny; _nz = nz;
	}
    void Color4f( const GLfloat r, const GLfloat g, const GLfloat b, const GLfloat a ){
        _cr = r*255; _cg = g*255; _cb = b*255; _ca = a*255;
    }
	void TexCoord2f( const float u, const float v ){
		_tu = u; _tv = v;
	}
	float _nx, _ny, _nz;
	GLubyte _cr, _cg, _cb, _ca;
	float _tu, _tv;
	POC_PROP( vector< float >, _varr, VArr );
	POC_PROP( vector< float >, _narr, NArr );
	POC_PROP( vector< GLubyte >, _carr, CArr );
	POC_PROP( vector< float >, _tarr, TArr );
};

おっとPOC_PROPは過去の記事を参照してくれ。上記頂点配列ヘルパークラスの使い方は以下のようだよ。

    // 床の描画
    {
        pocVertexArrayHelper vah;
        vah.Color4f( 1.0, 1.0, 1.0, 1.0);
        for( int i=-5; i<=5; ++i ){
            vah.Vertex3f( -5, 0, i);
            vah.Vertex3f( 5, 0, i );
            vah.Vertex3f( i, 0, -5 );
            vah.Vertex3f( i, 0, 5 );
        }// i
        vah.DrawVC( GL_LINES );
    }
    // 三角の描画
    {
        pocVertexArrayHelper vah;
        vah.Color4f( 1.0, 0, 0, 0.8 );
        vah.Vertex3f( -1, 0, 0 );
        vah.Color4f( 0.1, 1.0, 0, 0.8 );
        vah.Vertex3f( 1, 0, 0 );
        vah.Color4f( 0.1, 0, 1.0, 0.8 );
        vah.Vertex3f( 0, 2, 0 );
        vah.DrawVC( GL_TRIANGLES );
    }
    // ポリラインの描画
    {
        vector< Vec3 > pl;
        // 骨格形状
        {
            pl.push_back( Vec3( -2, 0, 0 ) );
            pl.push_back( Vec3( -1, 2, 0 ) );
            pl.push_back( Vec3( 0, 0, 0 ) );
            pl.push_back( Vec3( 1, 2, 0 ) );
            pl.push_back( Vec3( 2, 0, 0 ) );
        }
        // 細分化
        {
           //  中略
        }
        pocVertexArrayHelper vah;
        POC_FOREACH( pl, const Vec3& ipos ){
            vah.Color4f( 1, 1, 0, 1 );
            vah.Vertex3f( ipos.X(), ipos.Y(), ipos.Z() );
        }// ipos
        glLineWidth( 1.0 );
        glPointSize( 3.0 );
        vah.DrawVC( GL_LINE_STRIP );
        vah.DrawVC( GL_POINTS );
    }

どうだろう。かなりglBegin/glEnd的な感覚でイージーな図形の描画ができているんじゃないかこれ。
書きやすさだけじゃなくて、一連のgl描画のコードをオブジェクトの中に蓄えておけるっていう、うれしい副次的効果もありそうだぜ。それに何と言ってもglBegin/glEndよりも描画が明らかに高速だ。(←まー当然だわなっ!)なので、ESじゃない環境、glBegin/glEndが使えてしまう環境でも、本クラスを使うメリットは多大なはずさ。
法線やテクスチャをサポートするやつとか、いくらでも拡張してみてほしい。いいのができたらフィードバックしてくれよな。じゃっ!
(後日記)より新しいOpenGLES2にも対応した、VertexAttribArrayを使ったバージョンはこちらの未来日記を参照。