mieki256's diary



2024/04/22(月) [n年前の日記]

#1 [python] PyOpenGLでモデルデータを描画したい

Python + PyOpenGL + glfw で、モデルデータを描画したい。とりあえず、wavefront obj形式のモデルデータを読み込んで描画できるだけでも助かる。

環境は Windows10 x64 22H2 + Python 3.10.10 64bit。PyOpenGL 3.1.6 + PyOpenGL-accelerate 3.1.6 + glfw 2.7.0。

ライブラリを探す :

wavefront obj形式を解析する処理を自分で書いてもいいのだけど、大人気のプログラミング言語、Python のことだから、絶対に誰かが既にそういう処理を書いてライブラリにしているはず。PyPI で検索してみた。

_PyPI - The Python Package Index

色々見かけたけど、PyWavefront とやらはどうだろう。

_PyWavefront - PyPI
_pywavefront/PyWavefront: Python library for importing Wavefront .obj files
_【Pythonライブラリ】「pywavefront」のサンプルコード | YuNi-Wiki

pip でインストール。
pip install pywavefront

PyWavefront 1.3.3 がインストールされた。

PyWavefrontの使い方 :

とりあえず、以下のように書けば、.obj と .mtl を読み込んでくれるらしい。
import pywavefront

obj = pywavefront.Wavefront("hoge.obj")

ただ、変数objに入った、このクラスが、どんな情報を持っているのかが分からない…。とりあえず、それらしい情報を print() で出力してみよう…。

テストに使ったモデルデータは以下。

_cube01.obj
_cube01.mtl
_suzanne01.obj
_suzanne01.mtl
_car.obj
_car.mtl

car.obj、car.mtl は、以下のデータを利用させてもらった。ライセンスがCC0なので、改変して利用してもOK。自由に使える。ありがたや。ほんの少しだけ修正して、ポリゴン数を微妙に減らして使ってみた。

_Car Kit | OpenGameArt.org
_Car Kit - Kenney

テストに使った Pythonスクリプトは以下。

_dump_obj.py
import pywavefront

infile = "cube01.obj"
# infile = "suzanne01.obj"


def dump_material(mat):
    print()
    print(f".name = {mat.name}")
    print(f".vertex_format = {mat.vertex_format}")
    print(f".vertices = {mat.vertices}")
    print(f".diffuse = {mat.diffuse}")
    print(f".ambient = {mat.ambient}")
    print(f".specular = {mat.specular}")
    print(f".texture = {mat.texture}")


def main():
    obj = pywavefront.Wavefront(infile)
    # obj.parse()

    print(f"file_name = {obj.file_name}")
    print(f"mrllibs = {obj.mtllibs}")
    print(f"vertices = {obj.vertices}")

    print(f"\nmaterials = {obj.materials}")
    # for matname, mat in obj.materials.items():
    #     dump_material(mat)

    print(f"\nmeshes = {obj.meshes}")
    # for meshname, mesh in obj.meshes.items():
    #     print()
    #     print(f"Mesh name = {meshname}")
    #     print(f"name = {mesh.name}")
    #     print(f"faces = {mesh.faces}")
    #     print(f"materials = {mesh.materials}")
    #     print("\n# mesh.materials")
    #     for mat in mesh.materials:
    #         dump_material(mat)

    print(f"\nmesh_List = {obj.mesh_list}")
    for mesh in obj.mesh_list:
        print(f".name = {mesh.name}")
        print(f".faces = {mesh.faces}")
        print(f".materials = {mesh.materials}")
        for mat in mesh.materials:
            dump_material(mat)


if __name__ == "__main__":
    main()

python dump_obj.py

出力結果は以下。

_dump_obj_output.txt

どうやら、.mesh_list が持ってる情報を使えば、描画ができそうだなと…。
  • .mesh_list という配列の中に、各mesh が入っている。
  • 各meshは、マテリアル(.materials) を持っている。
  • そのマテリアルの中に、頂点配列(.vertices)も含まれている。
  • 頂点配列は、テクスチャUV情報、法線情報、頂点座標値が、インターリーブされた形で入ってる。
  • どんなインターリーブになっているかは、.vertex_format に入っている。

インターリーブについて :

OpenGLのインターリーブについては、以下のページが参考になるかなと…。

_GPU本来の性能を引き出すWebGL頂点データ作成法 #WebGL - Qiita
_wgld.org | WebGL: インターリーブ配列 VBO |

要は、頂点座標、法線情報、テクスチャUV情報などを、頂点1つ毎にチマチマと並べている頂点配列の格納形式、ということでいいのだろうか。ただ、上記のページの解説によると、昔のGPUではインターリーブをすると高速化できたけど、今のGPUでは遅くなるそうで…。

そのインターリーブとやらは OpenGL 1.1 でも使えるのだろうかと気になったけど、Microsoftのページでは、「OpenGL 1.1以降で使える」と書いてあるようだなと…。

_glInterleavedArrays 関数 (Gl.h) - Win32 apps | Microsoft Learn
注意 : glInterleavedArrays 関数は、OpenGL バージョン 1.1 以降でのみ使用できます。

glInterleavedArrays 関数 (Gl.h) - Win32 apps | Microsoft Learn より

PyWavefrontを利用して描画してみる :

PyOpenGL 3.1.6 + glfw 2.7.0 + PyWavefront 1.3.3 を利用して、wavefront obj形式のモデルデータを読み込んで描画してみる。

以下のような見た目になった。




_draw_opengl_glfw.py
import glfw
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import pywavefront

model_kind = 2

modeldata = [
    {"file": "./cube01.obj", "scale": 2.0},
    {"file": "./suzanne01.obj", "scale": 5.0},
    {"file": "./car.obj", "scale": 7.0},
]

SCRW, SCRH = 1280, 720
WDWTITLE = "Draw wavefront obj"
FOV = 50.0

light_pos = [1.0, 1.0, 1.0, 0.0]
light_ambient = [0.2, 0.2, 0.2, 1.0]
light_diffuse = [0.9, 0.9, 0.9, 1.0]
light_specular = [0.5, 0.5, 0.5, 1.0]

winw, winh = SCRW, SCRH
ang = 0.0
obj = None


def init_animation(infile):
    global ang, obj
    ang = 0.0
    obj = pywavefront.Wavefront(infile)


def render():
    global ang, obj
    ang += 45.0 / 60.0

    # init OpenGL
    glViewport(0, 0, winw, winh)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(FOV, float(winw) / float(winh), 1.0, 1000.0)

    # clear screen
    glClearDepth(1.0)
    glClearColor(0, 0, 0, 1)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    glDepthFunc(GL_LESS)
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_BLEND)
    glEnable(GL_NORMALIZE)

    glEnable(GL_CULL_FACE)
    glFrontFace(GL_CCW)
    # glCullFace(GL_FRONT)
    glCullFace(GL_BACK)

    # set material
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE)
    glEnable(GL_COLOR_MATERIAL)

    # set lighting
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos)
    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient)
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)

    # obj move and rotate
    glTranslatef(0.0, 0.0, -20.0)
    scale = modeldata[model_kind]["scale"]
    glScalef(scale, scale, scale)
    glRotatef(20.0, 1, 0, 0)
    # glRotatef(ang * 0.5, 1, 0, 0)
    glRotatef(ang, 0, 1, 0)

    # draw obj
    for mesh in obj.mesh_list:
        for mat in mesh.materials:
            r, g, b, a = mat.diffuse
            glColor4f(r, g, b, a)
            gl_floats = (GLfloat * len(mat.vertices))(*mat.vertices)
            count = len(mat.vertices) / mat.vertex_size
            glInterleavedArrays(GL_T2F_N3F_V3F, 0, gl_floats)
            glDrawArrays(GL_TRIANGLES, 0, int(count))


def key_callback(window, key, scancode, action, mods):
    if action == glfw.PRESS:
        if key == glfw.KEY_ESCAPE or key == glfw.KEY_Q:
            glfw.set_window_should_close(window, True)


def resize(window, w, h):
    if h == 0:
        return
    set_view(w, h)


def set_view(w, h):
    global winw, winh
    winw, winh = w, h
    glViewport(0, 0, w, h)


def main():
    if not glfw.init():
        raise RuntimeError("Could not initialize GLFW3")
        return

    window = glfw.create_window(SCRW, SCRH, WDWTITLE, None, None)
    if not window:
        glfw.terminate()
        raise RuntimeError("Could not create an window")
        return

    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 1)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 1)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    glfw.window_hint(glfw.DEPTH_BITS, 24)

    glfw.set_key_callback(window, key_callback)
    glfw.set_window_size_callback(window, resize)
    glfw.make_context_current(window)
    glfw.swap_interval(1)

    set_view(SCRW, SCRH)

    # get csv filepath
    infile = modeldata[model_kind]["file"] if len(sys.argv) != 2 else sys.argv[1]
    init_animation(infile)

    # main loop
    while not glfw.window_should_close(window):
        render()
        glfw.swap_buffers(window)
        glfw.poll_events()

    glfw.destroy_window(window)
    glfw.terminate()


if __name__ == "__main__":
    main()


render() の中で OpenGL関係の描画をしているから、そこを見れば大体分かりそうだけど。肝は以下だろうか…。
import pywavefront

# ...

    obj = pywavefront.Wavefront(infile)

# ...

    # draw obj
    for mesh in obj.mesh_list:
        for mat in mesh.materials:
            r, g, b, a = mat.diffuse
            glColor4f(r, g, b, a)
            gl_floats = (GLfloat * len(mat.vertices))(*mat.vertices)
            count = len(mat.vertices) / mat.vertex_size
            glInterleavedArrays(GL_T2F_N3F_V3F, 0, gl_floats)
            glDrawArrays(GL_TRIANGLES, 0, int(count))

PyWavefront を使って読み込むと、マテリアル毎に、そのマテリアル情報を使って描画したい頂点配列をまとめてくれるので…。
  • マテリアル単位でループ処理する。
  • マテリアル情報を元にしながら色の設定を行う。
  • glInterleavedArrays() でインターリーブのフォーマットを指定。
  • glDrawArrays() で頂点配列の描画を指示。

インターリーブのフォーマットについては、今回のテストモデルデータを使う限り、GL_T2F_N3F_V3F を固定で指定しちゃって問題無さそうだったけど。本来は、.vertex_format から指定すべき値を決めないといけない気がする。

ちなみに、GL_T2F_N3F_V3F は、テクスチャUVがfloatで2個分(=T2F)、法線情報がfloatで3個分(=N3F)、頂点座標がfloatで3個分(=V3F)、の順番で入っていると示す値、だと思う。

さておき。描画されたモデルの色が、なんだかおかしい気がする…。blender上で表示されたソレとは随分違う色になってしまっていて…。OpenGL側でマテリアルの指定をするあたりで何か間違えてしまっている気がする。

#2 [prog] 平方根を使ったほうが速いらしい

思考メモ。

とあるPythonスクリプトを書いてる際、途中で平方根を求める部分が出てきて、特に何も考えずに sqrt() を使って書いてしまったのだけど。ふと、「平方根を求めるところで遅くなったりしないのかなあ?」と不安になった。

気になったので少しググってみたら…。今時のCPUは、平方根を求めるための専用のCPU命令が用意されていたりするから、実は素直に平方根を使って計算しちゃったほうが一番速かったりする、と知った。えー。そんなことになってたのか…。今頃になって結構ビックリ。いやまあ、使ってるCPUにもよるだろうし、コンパイラによっても違ってくるだろうし、各プログラミング言語の sqrt() がどういう実装をしているかも絡んでくるのだろうけど。

OpenGLのインターリーブの件もそうだろうけど、ハードウェアの仕組みが変わったことで、昔の高速化手法を使うとかえって遅くなってしまう場面があちこちにあるのかもしれないなと…。

以上、1 日分です。

過去ログ表示

Prev - 2024/04 - 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

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project