mieki256's diary



2017/03/16(木) [n年前の日記]

#1 [ruby] 3Dモデルデータファイルを読み込んでRubyから使いやすい形に整形するRubyスクリプトを書いた

Ruby + gosu + opengl で実験する際、一々モデルデータを紙に描いて座標を決めて配列にしていくのが面倒臭かったので、Wavefront形式(.obj)の3Dモデルデータファイルを読み込んで、Rubyから使いやすい形に整形して標準出力に出力する Rubyスクリプトを書いてみたり。

_mieki256/tinywavefrontobj

tinywavefrontobj.rb というファイルをダウンロードして実行すれば使える、はず。

Wavefront形式の全ての属性に対応してるわけではないけれど、実験用の頂点配列、法線配列、uv配列、面配列(頂点インデックス配列)をちょこっと作りたい時にはひとまず使える、のではないかなと。

使用例その1。 :

例えば、blender でこういうモデルデータを作成して、Wavefront(.obj)形式でエクスポート。

tinywavefrontobj_ss01.png

ちなみに、Wavefront形式は、三角形ポリゴンと四角形ポリゴンを混在できるのだけど…。後になってからRubyスクリプト側で、面配列(頂点インデックス配列)のみを眺めてそれぞれ三角ポリゴンか四角ポリゴンかを判別するのはおそらく不可能。故に、エクスポート時に「三角面化」にチェックを入れて、全てのポリゴンを三角ポリゴンにしちゃったほうがいいと思う。

tinywavefrontobj_ss02.png

tinywavefrontobj.rb を使って整形出力。
ruby tinywavefrontobj.rb sample.obj

こういう結果が得られる。
@vertexes = [
  1.0, -1.0, 0.0,  # 0
  -1.0, 1.0, 0.0,  # 1
  -1.0, -1.0, 0.0,  # 2
  1.0, 1.0, 0.0,  # 3
]

@normals = [
  0.0, 0.0, 1.0,  # 0
  0.0, 0.0, 1.0,  # 1
  0.0, 0.0, 1.0,  # 2
  0.0, 0.0, 1.0,  # 3
]

@faces = [
  0, 1, 2,  # 0
  0, 3, 1,  # 1
]
これをRubyソースに貼り付ければ実験に使えるだろうと。

jsonやyamlで出力。 :

json や yaml でも出力できる。

ruby tinywavefrontobj.rb sample.obj --json > sample.json
{"vertex":[1.0,-1.0,0.0,-1.0,1.0,0.0,-1.0,-1.0,0.0,1.0,1.0,0.0],"normal":[0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0],"face":[0,1,2,0,3,1]}

ruby tinywavefrontobj.rb sample.obj --yaml > sample.yml
---
vertex:
- 1.0
- -1.0
- 0.0
- -1.0
- 1.0
- 0.0
- -1.0
- -1.0
- 0.0
- 1.0
- 1.0
- 0.0
normal:
- 0.0
- 0.0
- 1.0
- 0.0
- 0.0
- 1.0
- 0.0
- 0.0
- 1.0
- 0.0
- 0.0
- 1.0
face:
- 0
- 1
- 2
- 0
- 3
- 1

Rubyスクリプト側で、例えば以下のような感じで書けば、jsonファイルを読み込んで、頂点配列、法線配列、uv配列、面配列(頂点インデックス配列)を得られる。
    require 'json'
    ...
    vertex_array = nil
    normal_array = nil
    uv_array     = nil
    face_array   = nil
    File.open("sample.json") { |file|
      hash = JSON.load(file)
      vertex_array = hash["vertex"]
      normal_array = hash["normal"] if hash.key?("normal")
      uv_array     = hash["uv"] if hash.key?("uv")
      face_array   = hash["face"]
    }
    ...
    if uv_array
      puts "use texture"
    end
    if normal_array
      puts "use normal"
    end

RUbyソース内から呼び出して使う。 :

以下のように書けば、Rubyソース内から呼び出して使うこともできる。
    require_relative 'tinywavefrontobj'
    ...
    o = TinyWaveFrontObj.new("sample.obj", true, true)
    vertex_array = o.get_vertex_array
    normal_array = o.get_normal_array
    uv_array     = o.get_uv_array
    face_array   = o.get_face_array
    ...
    if o.use_uv
      puts "use texture"
    end
    
    if o.use_normal
      puts "use normal"
    end

#2 [ruby] Ruby + gosu + opengl でプログラマブルシェーダを利用してみる

_算譜記録帳: OpenGLでの頂点データの扱いの変化 を参考にして、OpenGL 2.0風の書き方を ―― プログラマブルシェーダ(頂点シェーダ、フラグメントシェーダ)を用意して3D描画するRubyスクリプトを書いてみたり。

正直、自分の書いたソースはグチャグチャでアレなのだけど…。Web上で紹介されてるOpenGL関係のソースは、そのほとんどがおそらくC++で書かれてるっぽいので、「Rubyで書くとこうなるよ」てな例としてアップロードしておくだけでも多少は意味があるのかなと。

まずは三角形を描画。 :

まずは、三角形のみを描画してみる。

_gosu_opengl_20a.rb

こんな感じの結果画面に。
gosu_opengl20_ss01.png


ポイントになる部分だけをメモ。
  # プログラマブルシェーダの初期化
  def init_shader
    # 頂点シェーダ(Vertex Shader)のソース
    # gl_position に (x, y, 0.0, 1.0) を渡してる
    vert_shader_src_a =<<EOS
#version 120
attribute vec2 coord2d;
void main(void) {
  gl_Position = vec4(coord2d, 0.0, 1.0);
}
EOS

    # フラグメントシェーダ(Fragment Shader)のソース
    # gl_FragColor に (r,g,b,a)=(0, 0, 1, 1) を渡してるので、青一色になる
    frag_shader_src =<<EOS
# #version 120
void main(void) {
  gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
EOS

    # (r,g,b,a) の r,g をx,y座標で変化させるのでグラデーションになる
#     frag_shader_src =<<EOS
# #version 120
# void main(void) {
#   gl_FragColor[0] = gl_FragCoord.x / 640.0;
#   gl_FragColor[1] = gl_FragCoord.y / 480.0;
#   gl_FragColor[2] = 0.5;
#   gl_FragColor[3] = 1.0;
# }
# EOS

    # 頂点シェーダを設定
    # ----------------------------------------

    # 1. シェーダオブジェクトを作成
    vert_shader = glCreateShader(GL_VERTEX_SHADER)

    # 2. シェーダのソースを渡す
    glShaderSource(vert_shader, vert_shader_src_a)

    # 3. シェーダをコンパイル
    glCompileShader(vert_shader)

    # 4. 正しくコンパイルできたか確認
    compiled = glGetShaderiv(vert_shader, GL_COMPILE_STATUS)
    abort "Error : Compile error in vertex shader" if compiled == GL_FALSE

    # フラグメントシェーダを設定
    # ----------------------------------------

    # 1. シェーダオブジェクトを作成
    frag_shader = glCreateShader(GL_FRAGMENT_SHADER)

    # 2. シェーダのソースを渡す
    glShaderSource(frag_shader, frag_shader_src)

    # 3. シェーダをコンパイル
    glCompileShader(frag_shader)

    # 4. 正しくコンパイルできたか確認
    compiled = glGetShaderiv(frag_shader, GL_COMPILE_STATUS)
    abort "Error : Compile error in fragment shader" if compiled == GL_FALSE

    # 5. プログラムオブジェクトを作成
    @shader = glCreateProgram

    # 6. プログラムオブジェクトに対してシェーダオブジェクトを登録
    glAttachShader(@shader, vert_shader)
    glAttachShader(@shader, frag_shader)

    # 7. シェーダプログラムをリンク
    glLinkProgram(@shader)

    # 8. 正しくリンクできたか確認
    linked = glGetProgramiv(@shader, GL_LINK_STATUS)
    abort "Error : Linke error" if linked == GL_FALSE

    # 9. シェーダプログラムを適用
    glUseProgram(@shader)

    # 設定が終わったので後始末
    glDeleteShader(vert_shader)
    glDeleteShader(frag_shader)

    # シェーダに渡す属性のインデックス値(0,1,2,3等)を得る
    attr_name = "coord2d"
    @coord2d = glGetAttribLocation(@shader, attr_name)
    abort "Error : Could not bind attribute #{attr_name}" if @coord2d == -1
  end
OpenGL 2.0 でシェーダを用意する手順としては…。
  1. シェーダオブジェクトを作成
  2. シェーダのソースを渡す
  3. シェーダをコンパイル
  4. 正しくコンパイルできたか確認
  5. プログラムオブジェクトを作成
  6. プログラムオブジェクトに対してシェーダオブジェクトを登録
  7. シェーダプログラムをリンク
  8. 正しくリンクできたか確認
  9. シェーダプログラムを適用
更に、1から4までは、頂点シェーダとフラグメントシェーダの2回分、処理を行うようで。

_DXRubyのShader と同様なのだけど、Ruby + OpenGL を使って実験する際、Rubyスクリプトのソース内に、シェーダプログラム(GLSL)のソースを直接ずらずらと書けるあたりはありがたいなと。気軽に実験できるというか。

もう少し複雑な描画をしてみる。 :

正直なところ、シェーダをどんな風に書けばいいのかまだ全然分かってない状態なのだけど。以下の記事で参考として紹介されてるシェーダを使わせてもらって、もう少し複雑な描画を試してみたり。

_床井研究室 - 第1回 シェーダプログラムの読み込み
_床井研究室 - 第2回 Gouraud シェーディングと Phong シェーディング
_床井研究室 - 第3回 テクスチャの参照

Rubyスクリプトは、こんな感じに。

_gosu_opengl_20g_read_json.rb

  • jsonファイルになってるモデルデータは、tinywavefrontobj.rb を使って用意した。
  • 頂点シェーダのソースファイル(.vert)と、フラグメントシェーダのソースファイル(.frag)は、前述の参考記事内で紹介されてるものを利用させてもらって実験。

結果画面は、こんな感じに。



ということで、Ruby + gosu + opengl を使っても、OpenGL 2.0風の書き方はできるみたいだなと。

課題。 :

今回、マテリアル(材質設定)を一種類に決め打ちして描画したけれど。複数のマテリアルを使ったモデルデータを描画したい場合は、どういう書き方をすればいいのかなと…。

_glBegin と glEnd を使って描画した場合 は、処理が遅い代わりに、ポリゴン一枚ごとに材質設定をし直すこともできたので、複数のマテリアルを使っていても描画ができたけど。頂点インデックス配列を渡して一気に描画する場合は…。

もしかしてそのあたり、マテリアル毎に分割した頂点インデックス配列を用意して対応することになるのだろうか。それとも、「この面にはこのマテリアルを使う」てな指定も可能になる書き方があったりするのだろうか。マテリアル情報をVRAMに転送しておいて、マテリアルインデックス配列を渡して、みたいな。…そんなことできるのかな。

テクスチャを使ってる時と使ってない時で、違うシェーダを使って処理してるあたりも気になる。1つのシェーダでどちらにも対応できないものか…。

参考ページ。 :

#3 [prog] オンボードビデオ上でのVBOって意味あるのかな

ここ数日、OpenGL関係の勉強をしてるのだけど、なんとなく疑問が。

メインメモリ上に頂点配列その他を置いておくとアクセスが遅くなるから、GPUが管理してるメモリにその手のデータをあらかじめ転送しといてそっち使うと速くなるよ、ソレをVBOって呼ぶよ、みたいな話があるわけだけど。

今時のPCってオンボードビデオが多かったりするし、そのオンボードビデオはVRAMとしてメインメモリを使ってるわけで。そんな状況下で、GPUが管理するメモリ上にデータを転送してみても、速度の違いが出るのかなと。結局どっちもメインメモリを使ってるやん、と。VBOが有効だったのは、高速なメモリを入手するのがコスト的にちと厳しくてビデオカード上にしか載せられなかった時代、3D描画したかったらGPUが載ったビデオカードを増設するのが当たり前だった時代、そんな時代の話だったりしないのだろうかと。

もちろん今でもゴイスなビデオカードはフツーに売られてるし、複雑な3D描画をしてるゲームを遊びたいなんて時はデスクトップPCを買ってビデオカード増設して、てのが当たり前だったりするから、VBO的なソレがは有効な場面がほとんどやで、とも言えそうだけど。

でも、CPUの中にGPUまで入っていて、ソレを使って画面表示してるPC上では、どの程度意味があるんだろうと。意味があるとしたら、それは何故なんだろうと。CPUもGPUも同じ速度のメモリを使ってるはずなのに、どのあたりの仕組みで速度差が出てくるのだろう…。

いやまあ、「それなりのスペックのPCではそれなりの速度で描かれるけど、ゴイスなビデオカードを積んでるPCではゴイスな速度で描かれるのだから、後者を意識してソースを書くべき」という方針もアリなのかな。逆に、「今時皆が持ってるのはえてしてそれなりレベルのPCなんだから、それなりの速度で動いて、その代わりソースが書きやすければそれで十分じゃん」てのもアリかもしれないけど。

まあ、そんな疑問を持ちました。とメモ。

#4 [nitijyou] 親父さんが退院

手術も無事に済んで今日の午後退院。

もう1回手術しないといけないはず、なのだけど。何故か、次回対処すべき患部が、今回の検査中に見つからなかったそうで。造影剤が少なかったのか、それとも前回の手術で状態が変わったのか分からないとのことだけど、映像は撮れているので探してみるとお医者さんは言ってたらしい。どのみちしばらくしたら診察しないといかんので、その時には状況が分かるのだろう…。

以上、1 日分です。

過去ログ表示

Prev - 2017/03 - Next
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project