2017/03/12(日) [n年前の日記]
#1 [ruby] Ruby + gosu + opengl で頂点配列とかVBOとか
_Ruby
+
_gosu
+
_opengl
で、OpenGL の glBegin, glEnd を使ってポリゴンを描画する書き方は分かったけれど。頂点配列とかVBOとかを使う際の書き方が分からなかったので、そのあたりを実験。
以下の記事が分かりやすくて、とても参考になった。OpenGL って、どんどん面倒臭くなっていったのだな…。
_算譜記録帳: OpenGLでの頂点データの扱いの変化
とりあえず、結果はこんな感じに。
一応描画できてるっぽい。
以下の記事が分かりやすくて、とても参考になった。OpenGL って、どんどん面倒臭くなっていったのだな…。
_算譜記録帳: OpenGLでの頂点データの扱いの変化
とりあえず、結果はこんな感じに。
一応描画できてるっぽい。
◎ 頂点配列を渡すやり方。 :
前述の記事によると OpenGL 1.1 で頂点配列を渡せるようになったらしいので、まずはそこから試してみたり。以下のようなソースになった。
_gosu_opengl_11.rb
ソース中で、UVグリッドのテクスチャ画像を使ってるけど、以下で公開されてる画像を使わせてもらったり。Public Domainらしいので安心して使えます。ありがたや。
_Arahnoid/UVChecker-map: A collection of free images what can be used during unwrapping of 3D models
ちなみに、他にもヨサゲなUVグリッド画像があったりしないかとWeb上で結構探してみたものの、えてしてどれもライセンスが CC-BY だったりして。サンプルソース等に同梱したい、てな時は CC-BY は不便ではないのかと思うのだけど…。その点、上記の画像群は実にありがたい。作者様にはひたすら感謝。
さておき。とりあえず、ソース中のポイントになる部分だけメモ。「Ruby + opengl ではこう書く」みたいなところだけでも伝わればいいかなと…。
頂点配列をそのまま渡して描画する例。glDrawArrays を使う。
頂点配列と、頂点インデックス番号の配列を渡して描画する例。glDrawElements を使う。
ところで。頂点インデックス配列、法線インデックス配列、uvインデックス配列を別々に渡せないっぽいあたりで首を捻ったり。
頂点を共有できれば扱うデータ数は少なくなるわけだけど…。頂点座標、法線ベクトル、uv座標の全てが他の頂点とピッタリ一致することってまず無いので、つまり各頂点が共有される場面なんてほとんどあり得なくて、結局はずらずらと重複値を並べていくことになるわけで。これじゃわざわざインデックスを使う意味ねえなと思いました。
いや、待て。自分の書いたソースは四角形で描画してるからそうなっちゃうのか。これがもし、全部三角形で描画するとなれば、一つの四角形を二つの三角形で表現することになるから、共有可能な頂点が結構出てくる、のかな。であれば、インデックスを使う意味があるか…。
_gosu_opengl_11.rb
ソース中で、UVグリッドのテクスチャ画像を使ってるけど、以下で公開されてる画像を使わせてもらったり。Public Domainらしいので安心して使えます。ありがたや。
_Arahnoid/UVChecker-map: A collection of free images what can be used during unwrapping of 3D models
ちなみに、他にもヨサゲなUVグリッド画像があったりしないかとWeb上で結構探してみたものの、えてしてどれもライセンスが CC-BY だったりして。サンプルソース等に同梱したい、てな時は CC-BY は不便ではないのかと思うのだけど…。その点、上記の画像群は実にありがたい。作者様にはひたすら感謝。
さておき。とりあえず、ソース中のポイントになる部分だけメモ。「Ruby + opengl ではこう書く」みたいなところだけでも伝わればいいかなと…。
頂点配列をそのまま渡して描画する例。glDrawArrays を使う。
# 頂点配列を渡して三角形を描画する例
def draw_triangle
glEnableClientState(GL_VERTEX_ARRAY) # 頂点配列を有効化
glEnableClientState(GL_NORMAL_ARRAY) # 法線配列を有効化
# 頂点配列の指定
glVertexPointer(
3, # size. 1頂点に値をいくつ使うか。x,yなら2 x,y,zなら3
GL_FLOAT, # 値の型
0, # stride. データの間隔。詰まってるなら0
@vertexs_a # 頂点データが入った配列
)
# 法線配列の指定
# 法線は1頂点につき必ずx,y,zの3値を持っているのでsize指定は不要
glNormalPointer(
GL_FLOAT, # 値の型
0, # stride
@normals_a # 法線データが入った配列
)
# 頂点群を渡して描画. glDrawArrays を使う
glDrawArrays(
GL_TRIANGLE_STRIP, # プリミティブの種類
0, # スタート地点
3 # 頂点数
)
glDisableClientState(GL_VERTEX_ARRAY) # 頂点配列を無効化
glDisableClientState(GL_NORMAL_ARRAY) # 法線配列を無効化
end
頂点配列と、頂点インデックス番号の配列を渡して描画する例。glDrawElements を使う。
# 頂点配列とインデックス配列を渡してcubeを描画する例
def draw_cube
glEnableClientState(GL_VERTEX_ARRAY) # 頂点配列を有効化
glEnableClientState(GL_NORMAL_ARRAY) # 法線配列を有効化
glEnableClientState(GL_TEXTURE_COORD_ARRAY) # uv配列を有効化
glVertexPointer(3, GL_FLOAT, 0, @vertexs_b) # 頂点配列の指定
glNormalPointer(GL_FLOAT, 0, @normals_b) # 法線配列の指定
glTexCoordPointer(2, GL_FLOAT, 0, @uvs_b) # uv配列の指定
glEnable(GL_TEXTURE_2D) # テクスチャ有効化
id = @texinfo.tex_name
glBindTexture(GL_TEXTURE_2D, id) # テクスチャ割り当て
# インデックス配列を渡して描画. glDrawElements を使う
glDrawElements(
GL_QUADS, # プリミティブ種類
@indices_b.size, # インデックス数
GL_UNSIGNED_SHORT, # インデックスの型
@indices_b # 頂点インデックスの配列
)
glDisable(GL_TEXTURE_2D) # テクスチャ無効化
# 頂点配列、法線配列、uv配列を無効化
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
end
ところで。頂点インデックス配列、法線インデックス配列、uvインデックス配列を別々に渡せないっぽいあたりで首を捻ったり。
頂点を共有できれば扱うデータ数は少なくなるわけだけど…。頂点座標、法線ベクトル、uv座標の全てが他の頂点とピッタリ一致することってまず無いので、つまり各頂点が共有される場面なんてほとんどあり得なくて、結局はずらずらと重複値を並べていくことになるわけで。これじゃわざわざインデックスを使う意味ねえなと思いました。
いや、待て。自分の書いたソースは四角形で描画してるからそうなっちゃうのか。これがもし、全部三角形で描画するとなれば、一つの四角形を二つの三角形で表現することになるから、共有可能な頂点が結構出てくる、のかな。であれば、インデックスを使う意味があるか…。
◎ VBOを使うやり方。 :
前述の、頂点配列を渡して描画するやり方は、頂点データがメインメモリ上に載ってるので、ちと処理が遅いらしくて。
GPUが管理してるメモリ(VRAM?)上にそれらデータを置けたら処理を速くできるんじゃね? そっちにデータを置いちゃおうぜ! てのがVBO(Vertex Buffer Object)、らしいです。それが OpenGL 1.5 あたりから使えるようになった、と。
ということで、ソースは以下。
_gosu_opengl_15.rb
ポイントになるところをメモ。
描画は以下のような感じ。
「使い終わったらバッファを削除せよ」と、その手の説明ページには書いてあったのだけど。C++での書き方は載ってるものの、Ruby でどう書けばいいのかが分からなくて。こういう書き方でいいのだろうか…?
「使い終わったら」の意味もちょっと不明で。毎フレーム、描画が終わったタイミングで削除しないといかんのかなと試してみたら異常終了するようになってしまって。いや、次回のフレームでも描画に使うんだから当たり前だろうけど。「もう、このデータを使って描画する機会は今後無い」という状態ならバッファを削除せよ、という話なのかも。
GPUが管理してるメモリ(VRAM?)上にそれらデータを置けたら処理を速くできるんじゃね? そっちにデータを置いちゃおうぜ! てのがVBO(Vertex Buffer Object)、らしいです。それが OpenGL 1.5 あたりから使えるようになった、と。
ということで、ソースは以下。
_gosu_opengl_15.rb
ポイントになるところをメモ。
# VBOを作成
# 頂点、法線、uv、インデックス用に、4つのバッファを生成
@buffers = glGenBuffers(4)
# 頂点群
glBindBuffer(GL_ARRAY_BUFFER, @buffers[0]) # バッファの種類を設定
vtx = @vtx_b.pack("f*")
glBufferData(GL_ARRAY_BUFFER, vtx.size, vtx, GL_STATIC_DRAW)
# 法線群
glBindBuffer(GL_ARRAY_BUFFER, @buffers[1])
nml = @nml_b.pack("f*")
glBufferData(GL_ARRAY_BUFFER, nml.size, nml, GL_STATIC_DRAW)
# uv群
glBindBuffer(GL_ARRAY_BUFFER, @buffers[2])
uv = @uvs_b.pack("f*")
glBufferData(GL_ARRAY_BUFFER, uv.size, uv, GL_STATIC_DRAW)
# インデックス群。他と違って GL_ELEMENT_ARRAY_BUFFER を指定してることに注意
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, @buffers[3])
face = @face_b.pack("S*")
glBufferData(GL_ELEMENT_ARRAY_BUFFER, face.size, face, GL_STATIC_DRAW)
- Ruby上では、配列を作って、ソレを pack して渡すことで VBO が作れるらしい。
- 頂点群、法線群、uv群は GL_ARRAY_BUFFER を指定するけど、インデックス群は他と違って GL_ELEMENT_ARRAY_BUFFER を指定する。
- VBOは毎フレーム作らなくてもいいらしい。初期化処理時に一度作って、毎フレーム使い回す模様。
描画は以下のような感じ。
# VBOを使ってcubeを描画
def draw_cube
# バッファのデータと関連付け
glEnableClientState(GL_VERTEX_ARRAY) # 頂点配列を有効化
glEnableClientState(GL_NORMAL_ARRAY) # 法線配列を有効化
glEnableClientState(GL_TEXTURE_COORD_ARRAY) # uv配列を有効化
# 頂点配列を関連付け
glBindBuffer(GL_ARRAY_BUFFER, @buffers[0])
glVertexPointer(3, GL_FLOAT, 0, 0)
# 法線配列を関連付け
glBindBuffer(GL_ARRAY_BUFFER, @buffers[1])
glNormalPointer(GL_FLOAT, 0, 0)
# uv配列を関連付け
glBindBuffer(GL_ARRAY_BUFFER, @buffers[2])
glTexCoordPointer(2, GL_FLOAT, 0, 0)
# インデックス配列を関連付け
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, @buffers[3])
glEnable(GL_TEXTURE_2D) # テクスチャ有効化
glBindTexture(GL_TEXTURE_2D, @texinfo.tex_name) # テクスチャ割り当て
# 描画
glDrawElements(
GL_QUADS, # プリミティブ種類
@face_b.size, # インデックスの個数
GL_UNSIGNED_SHORT, # インデックスの型
0 # バッファオフセット
)
glDisable(GL_TEXTURE_2D) # テクスチャ無効化
# 頂点配列、法線配列、uv配列を無効化
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
# バッファを削除
# glDeleteBuffers(@buffers)
end
「使い終わったらバッファを削除せよ」と、その手の説明ページには書いてあったのだけど。C++での書き方は載ってるものの、Ruby でどう書けばいいのかが分からなくて。こういう書き方でいいのだろうか…?
「使い終わったら」の意味もちょっと不明で。毎フレーム、描画が終わったタイミングで削除しないといかんのかなと試してみたら異常終了するようになってしまって。いや、次回のフレームでも描画に使うんだから当たり前だろうけど。「もう、このデータを使って描画する機会は今後無い」という状態ならバッファを削除せよ、という話なのかも。
◎ 参考ページ。 :
[ ツッコむ ]
#2 [ruby] Rubyでsizeofを得るのってどうやるんだろう
C言語系ではsizeofてのがあって、型や変数のメモリサイズを取得することができるのだけど。
_sizeof演算子
_sizeof演算子について - Qiita
Ruby でそういうことをしたいなら、何を使えばいいのだろう…。OpenGL でオフセット等を指定したい時に、float(C言語で言うところのfloat) はバイトサイズで何バイトか、てなあたりを知りたいわけですよ。バイナリデータにするのは pack を使えばいいらしいけど…。
例えばだけど。
_sizeof演算子
_sizeof演算子について - Qiita
Ruby でそういうことをしたいなら、何を使えばいいのだろう…。OpenGL でオフセット等を指定したい時に、float(C言語で言うところのfloat) はバイトサイズで何バイトか、てなあたりを知りたいわけですよ。バイナリデータにするのは pack を使えばいいらしいけど…。
例えばだけど。
nf = [0.0].pack("f*").size
こういうことをして調べられなくはない。けど、他にもっとスッキリした書き方がありそうな。
[ ツッコむ ]
以上、1 日分です。