OpenGL ES2時代の形状情報構築について


やあ子どもたち。新しいOpenGL使ってるか。でっかい組織の中で毎日何やってるかわからない人生送るよりは理学療法士とか社会福祉士とかになった方がよっぽど充実した人生になるという考え方もあるからみんなも人生プランよく考えろな。
glBegin()/glEnd()を使ってきた私にとって、旧来のDeprecatedなAPIを殆ど使えなくしてしまった今のOpenGLES2は、強制的に新しいAPIやしくみを勉強する機会をくれたという意味でとても有り難かったのですがその反面、とてもわかりにくいと感じたので、これをサクサク違和感なく使えるようにするためのサバイバルキットを作成しました。
(2年前に似たようなものを作ったのですが、それでさえも想定していたOpenGLのバージョンが古すぎました。)
モデル形状情報という切り口で、新しいOpenGLES2の描画処理の流れを見てみると、

  1. 描画したいモデルを構成する全頂点についての情報を、頂点配列としてまとめる。
  2. 1.で用意した頂点配列が、別途定義した頂点シェーダの入力へとなるよう関連付ける。
  3. 頂点配列の各頂点について、頂点シェーダの記述通りに、その頂点についての各アトリビュートを計算。
  4. 3.で求めた頂点のプロパティをフラグメントシェーダーで線形補間して頂点以外のポリゴン表面の色を決定。

この中でとくに1.と2.で使うAPIの意味や呼び出しがパラメータも多くとてもわかりにくい。そこで今回は上記1.,2.の手間を限りなく軽減しようというネタです。そのために以下の3つをねらいとしました。

  • 頂点配列は慣れ親しんだglBeginやglNormal, glVertex感覚で構築できるといいな。
  • モデルは描画の最小単位。よってその頂点配列情報も一つのオブジェクトとしてまとまっているべき。
  • わかりにくいAPIの使い方は覚えられそうにないので、よりわかりやすいラッパーを作ってしまえ

以上を実現するものが以下です。ずばり名付けて
「VertexAttribArrayHelper」です。
これを使うと、モデル形状の定義コードは以下のように簡単になります。モデル形状の定義は、描画とは異なり、描画前に一度だけやっておけばよい処理です。

        // vaah の生成と構築
        VertexAttribArrayHelper vaah( vshader_position_index, 0, vshader_color_index, vshader_texcoord_index );
        auto& gl = vaah;
        for( int i=0; i<=_tile.size(); ++i )
        {
            gl.Begin( GL_TRIANGLE_STRIP );
            const auto& ipr = _section_tips[ic(i)];
            const auto& iipr = _section_tips[ic(i+1)];
            const float c = 1.0;
            gl.Color4f( c, c, c, 1.0 );
            gl.TexCoord2f( 0.0, 1.0 );
            gl.Vertex3fv( glm::value_ptr( iipr.first ) );
            gl.TexCoord2f( 1.0, 1.0 );
            gl.Vertex3fv( glm::value_ptr( iipr.second ) );
            gl.TexCoord2f( 0.0, 0.0 );
            gl.Vertex3fv( glm::value_ptr( ipr.first ) );
            gl.TexCoord2f( 1.0, 0.0 );
            gl.Vertex3fv( glm::value_ptr( ipr.second ) );
            gl.End();
        }// ipositions
        // vaah のコンパイル(VBO,VAOの生成)
        gl.Compile();

そしてこれの描画処理は以下のようにやはり簡単となります。

void MODEL::Draw( void ) const
{
    // モデルの描画処理では以下のようにDraw()を呼ぶだけ。
    vaah.Draw();
}

冒頭の絵は本ヘルパークラスを用いて描画しています。
本ヘルパークラスのソースは以下のようになります。それほど短いコードではないけど長くはないし、やってることは単純。シンプル。とてもシンプルね。

// glBeginの再現というよりかは、描画手順そのものをオブジェクト化できるという点が大きい。
class VertexAttribArrayHelper
{
public:
	VertexAttribArrayHelper(
		GLuint index_position,
		GLuint index_normal,
		GLuint index_color,
		GLuint index_texuv,
		GLuint index_texuv2,
		GLuint uniform_use_texture
		)
	{
		// 1つのVAHの中に入れるSTRIPsは、全て、同じアトリビュートを指定する描画単位である
		// ものとする(頂点バッファフォーマットを共有するため)
		_vertexArray = _vertexBuffer = 0;
		_idx_position = index_position;
		_idx_normal = index_normal;
		_idx_color = index_color;
		_idx_texuv = index_texuv;
		_idx_texuv2 = index_texuv2;
		_num_vertices = 0;
		_varr.clear();
		_narr.clear();
		_tarr.clear();
		_tarr2.clear();
		_carr.clear();
		_is_normal = _is_color = _is_tex_coord = 0;
		_uniform_use_texture = uniform_use_texture;
	}
	VertexAttribArrayHelper(const VertexAttribArrayHelper& x)
	{
		_idx_position = x._idx_position;
		_idx_normal = x._idx_normal;
		_idx_color = x._idx_color;
		_idx_texuv = x._idx_texuv;
		_idx_texuv2 = x._idx_texuv2;
		_uniform_use_texture = x._uniform_use_texture;
		//
		_vertexArray = _vertexBuffer = 0;
		_num_vertices = 0;
		_varr.clear(); _narr.clear(); _tarr.clear(); _carr.clear();
		_is_normal = _is_color = _is_tex_coord = 0;
	}
	virtual ~VertexAttribArrayHelper(void){}
public:
	void Compile(GLenum usage = GL_STATIC_DRAW)
	{
		// VAOの生成と有効化
		glGenVertexArrays(1, &_vertexArray);
		// VBOの生成と指定ターゲットへのバインド
		glGenBuffers(1, &_vertexBuffer);
		this->UpdateBuffer(usage);
		return;
	}
	void InitBuffer(void)
	{
		_varr.clear();
		_carr.clear();
		_narr.clear();
		_tarr.clear();
		_tarr2.clear();
		_draw_mode_vec.clear();
		_is_normal = _is_color = _is_tex_coord = 0;
		_strip_count_vec.clear();
		_num_vertices = 0;
	}
private:
public:
	void UpdateBuffer(GLenum usage = GL_DYNAMIC_DRAW)
	{
		glBindVertexArray(_vertexArray);
		glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
		{
			auto add_chunk = [](
				GLuint index_attrib,
				const vector< GLfloat >& iarr,
				const int attrib_unit_size,
				vector< GLfloat >& varr)
			{
				//		if (!iarr.empty())
				if (index_attrib != -1)
				{
					glEnableVertexAttribArray(index_attrib);
					glVertexAttribPointer(
						index_attrib, // アトリビュートのシェーダープログラム内位置
						attrib_unit_size,// アトリビュートの単位サイズ
						GL_FLOAT, // アトリビュート要素値の型
						GL_FALSE, // 正規化フラグ
						0,// ストライド
						(void*)(varr.size() * sizeof(GLfloat))// データ配列のバッファー内位置
					);
					// 既存バッファにデータを追加。
					varr.insert(varr.end(), iarr.begin(), iarr.end());
				}
				return;
			};
			// 各種バッファを1つに統合 & バッファのフォーマットを指定
			vector< GLfloat > tmp;
			add_chunk(_idx_position, _varr, 3, tmp);
			add_chunk(_idx_normal, _narr, 3, tmp);
			add_chunk(_idx_color, _carr, 4, tmp);
			add_chunk(_idx_texuv, _tarr, 2, tmp);
			if( !_tarr2.empty())
				add_chunk(_idx_texuv2, _tarr2, 2, tmp);
			glBufferData(GL_ARRAY_BUFFER,
				sizeof(GLfloat)*tmp.size(), tmp.data(), usage);
		}
		return;
	}
#if 0
	void UpdateBufferSub(void)
	{
		// Compile() の引数に、GL_DYNAMIC_DRAW を指定した場合のみ有効。
		glBindVertexArray(_vertexArray);
		glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
		// 頂点情報をグラフィックハード(指定ターゲットに現在バインドされているBO)に転送
		{
			// 各種バッファを1つに統合
			_varr.insert(_varr.end(), _narr.begin(), _narr.end());// 法線配列を合体
			_varr.insert(_varr.end(), _carr.begin(), _carr.end());// カラー配列を合体
			_varr.insert(_varr.end(), _tarr.begin(), _tarr.end());// テクスチャ座標を合体
			// 1つにまとめた(巨大な)バッファの内容を転送
			glBufferSubData(GL_ARRAY_BUFFER, 0, _varr.size()*sizeof(GLfloat), _varr.data());
		}
		return;
	}
#endif
	void Draw(void) const
	{
		// 必ず本関数呼び出しに先立って、
		// 意志あるglUseProgram()呼び出し、続いて各種glUniform*()呼び出しを
		// 行うようにして下さい。
		//
		// テクスチャ座標が一切セットされていなければ
		// フラグメントシェーダではテクスチャを参照しません。
		//glUniform1i(_uniform_use_texture, (_tarr.empty()) ? 0 : 1);
		//
		glBindVertexArray(_vertexArray);
		int prev_pos = 0;
		for (int i = 0; i<_draw_mode_vec.size(); ++i)
		{
			int cur_pos = _strip_count_vec[i];
			glDrawArrays(_draw_mode_vec[i], prev_pos, cur_pos - prev_pos);
			prev_pos = cur_pos;
		}// i
	}
#if 0
	void DrawStripWithMode(const int strip_id, const GLenum mode)
	{
		//            int cur_pos = _strip_count_vec[id];
		//            glDrawArrays( _draw_mode_vec[i], prev_pos, cur_pos - prev_pos );
	}
	void Draw(GLenum mode) const
	{
		glBindVertexArrayAPPLE(_vertexArray);
		glDrawArrays(mode, 0, _num_vertices);
	}
	void Draw(GLenum mode, const int start, const int end)
	{
		glBindVertexArrayAPPLE(_vertexArray);
		glDrawArrays(mode, start, end);
	}
#endif
	// ancient spells
public:
	void Begin(const GLenum mode)
	{
		_draw_mode_vec.push_back(mode);
	}
	void End(void)
	{
		_strip_count_vec.push_back(_num_vertices);
	}
	void Vertex3f_sub(void)
	{
		if (_is_normal){
			_narr.push_back(_nx);
			_narr.push_back(_ny);
			_narr.push_back(_nz);
		}
		if (_is_color){
			_carr.push_back(_cr);
			_carr.push_back(_cg);
			_carr.push_back(_cb);
			_carr.push_back(_ca);
		}
		if (_is_tex_coord){
			_tarr.push_back(_tu);
			_tarr.push_back(_tv);
			_tarr2.push_back(_tu2);
			_tarr2.push_back(_tv2);
		}
		++_num_vertices;
	}
	void Vertex3f(const float x, const float y, const float z){
		_varr.push_back(x); _varr.push_back(y); _varr.push_back(z);
		Vertex3f_sub();
	}
	void Vertex3fv(const float* v)
	{
		_varr.insert(_varr.end(), v, v + 3);
		Vertex3f_sub();
	}
	void Normal3f(const float nx, const float ny, const float nz)
	{
		_nx = nx; _ny = ny; _nz = nz;
		_is_normal = !0;
	}
	void Normal3fv(const float* v)
	{
		_nx = v[0]; _ny = v[1]; _nz = v[2];
		_is_normal = !0;
	}
	void Color4f(const GLfloat r, const GLfloat g, const GLfloat b, const GLfloat a)
	{
		_cr = r; _cg = g; _cb = b; _ca = a;
		_is_color = !0;
	}
	void Color3fv(const GLfloat* v)
	{
		_cr = v[0]; _cg = v[1]; _cb = v[2]; _ca = 1.0f;
		_is_color = !0;
	}
	void TexCoord2f(const float u, const float v){
		_tu = u; _tv = v;
		_is_tex_coord = !0;
	}
	void TexCoord2fv(const GLfloat* v)
	{
		_tu = v[0]; _tv = v[1];
		_is_tex_coord = !0;
	}
	void TexCoord2fv2(const GLfloat* v)
	{
		_tu2 = v[0]; _tv2 = v[1];
		_is_tex_coord = !0;
	}
private:
	// 頂点シェーダ "position", "normal", "color", "texuv"の位置
	GLint _idx_position, _idx_normal, _idx_color, _idx_texuv, _idx_texuv2;
	GLuint _vertexArray, _vertexBuffer;// VAO id, VBO id
	GLfloat _nx, _ny, _nz, _tu, _tv, _cr, _cg, _cb, _ca, _tu2, _tv2;
	bool _is_normal, _is_color, _is_tex_coord;
	int _num_vertices;
	vector< GLfloat > _varr, _narr, _tarr, _carr, _tarr2;
	vector< GLenum > _draw_mode_vec;
	vector< int > _strip_count_vec;
	GLuint _uniform_use_texture;
};

まあ平たく行ってしまうと、vao/vboのラッパーです。vaahのインスタンスを作っておけば、その中に形状情報・描画情報など全部持ってるので、これをシリアライズすれば形状・描画情報の保存という用途にもそのまま使えてしまうかも知れません。

Dyson digital slim DC45MH

Dyson digital slim DC45MH