mieki256's diary



2024/11/23() [n年前の日記]

#1 [blender] blenderでobjをエクスポートした際の色を補正

_昨日 の続き。blenderから Wavefront形式(.obj .mtl)でエクスポートした際、全体的に色が暗くなる件について。

blenderの画面ではガンマ補正したRGB値に基づいて色を表示してるけど、objでエクスポートすると、.mtlファイル(マテリアル情報が記述されたファイル)内にはガンマ補正してないRGB値が出力されてしまう。だから、エクスポートした .obj .mtl をそのまま使うと、blender上での見た目より色が暗くなってしまう。

故に、blenderでの見た目に近づけたいなら、.mtl内の Kd (Diffuse color) をガンマ補正してやればいい。

そんなわけで、.mtlファイルを読み込んで、Kdの値をガンマ補正して出力するPythonスクリプトを書いてみた。linearrgb_to_srgb() という関数が肝。

環境は Windows10 x64 22H2 + Python 3.10.10 64bit。

_add_gamma.py
import argparse
import os
import sys
import re

# DBG = True
DBG = False


def linearrgb_to_srgb(c):
    """Gamma correction."""
    r = 0.0
    if c < 0.0031308:
        r = 0.0 if c < 0.0 else c * 12.92
    else:
        r = 1.055 * pow(c, (1.0 / 2.4)) - 0.055
    return round(r, 6)


def main():
    parser = argparse.ArgumentParser(description="Adding gamma correction to mtl file.")
    parser.add_argument("infile", type=str, help=".mtl filename")
    args = parser.parse_args()
    infile = args.infile

    if not os.path.isfile(infile):
        print("Error : Not found %s" % (infile))
        sys.exit()

    bakfile = "%s.orig.bak" % (infile)

    # read file
    with open(infile) as f:
        lines = [s.rstrip() for s in f.readlines()]

    pattern = r"^Kd (\d+\.\d+) (\d+\.\d+) (\d+\.\d+)$"
    newlines = []
    for s in lines:
        result = re.match(pattern, s)
        if result:
            r = linearrgb_to_srgb(float(result.group(1)))
            g = linearrgb_to_srgb(float(result.group(2)))
            b = linearrgb_to_srgb(float(result.group(3)))
            nline = "Kd %f %f %f" % (r, g, b)
            newlines.append(nline)
            if DBG:
                print("%s  ->  %s" % (s, nline))
        else:
            newlines.append(s)

    # backup file. rename original file
    os.rename(infile, bakfile)

    # write file
    with open(infile, "w", newline="\n") as fo:
        for s in newlines:
            fo.write("%s\n" % (s))


if __name__ == "__main__":
    main()

使い方は以下。
python add_gamma.py INPUT.mtl
INPUT.mtl が、入力する.mtlファイル。

実行すると、元の .mtl を .mtl.orig.bak にリネームしてバックアップを残してから、元の .mtlファイル名でガンマ補正後の .mtl を保存する。

二度も三度も実行しないように注意。何度もガンマ補正されて色がどんどんおかしくなってしまうので。

動作確認 :

正しく変換できてそうか、PythonスクリプトでOpenGLを使って描画してみる。以下の環境で動作確認した。
  • Windows10 x64 22H2
  • Python 3.10.10 64bit
  • PyOpenGL 3.1.7
  • PyOpenGL-accelerate 3.1.7
  • glfw 2.7.0
  • PyWavefront 1.3.3

各モジュールは以下でインストールできるのではないかな…。
python -m pip install PyOpenGL PyOpenGL-accelerate glfw PyWavefront

動作確認用のPythonスクリプト。

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

model_kind = 1

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

SCRW, SCRH = 1280, 720
WDWTITLE = "Draw wavefront obj"
FOV = 50.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(scale):
    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 lighting
    light_pos = [1.0, 1.0, 1.0, 0.0]

    # light_ambient = [0.2, 0.2, 0.2, 1.0]
    light_ambient = [0.5, 0.5, 0.5, 1.0]

    light_diffuse = [1.0, 1.0, 1.0, 1.0]
    light_specular = [0.8, 0.8, 0.8, 1.0]

    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)
    glScalef(scale, scale, scale)
    glRotatef(20.0, 1, 0, 0)
    # glRotatef(ang * 0.5, 1, 0, 0)
    glRotatef(ang, 0, 1, 0)

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

    # draw obj
    for mesh in obj.mesh_list:
        for mat in mesh.materials:
            # glMaterialfv(GL_FRONT, GL_AMBIENT, mat.ambient)
            # glMaterialfv(GL_FRONT, GL_DIFFUSE, mat.diffuse)
            # glMaterialfv(GL_FRONT, GL_SPECULAR, mat.specular)
            glColor4fv(mat.diffuse)
            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:
            # ESC or Q key to exit
            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():
    global model_kind

    objname = ""
    scale = 1.0
    parser = argparse.ArgumentParser()
    parser.add_argument("-m", "--model", type=int, help="Model type 0 - 2")
    parser.add_argument("-o", "--obj", type=str, help=".obj filename")
    parser.add_argument("-s", "--scale", type=float, help="Scale")
    args = parser.parse_args()

    objname = modeldata[model_kind]["file"]
    scale = modeldata[model_kind]["scale"]

    if args.model is not None:
        model_kind = args.model
        if model_kind < len(modeldata):
            objname = modeldata[model_kind]["file"]
            scale = modeldata[model_kind]["scale"]
    else:
        if args.obj is not None:
            objname = args.obj
        if args.scale is not None:
            scale = args.scale

    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

    # set OpenGL 1.1
    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)

    init_animation(objname)

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

    glfw.destroy_window(window)
    glfw.terminate()


if __name__ == "__main__":
    main()

使用するモデルデータ(.obj .mtl)は以下。draw_opengl_glfw.py と同階層に models/ というフォルダを作成して、その中に .obj と .mtl を入れておく。

_models.zip

使い方は以下。
Usage:
    python draw_opengl_glfw.py [-m N] [--obj INPUT.obj] [--scale N]

    -m N, --model N     : モデル種類 0 - 2 を指定
    -o FILE, --obj FILE : .objファイル名
    -s N, --scale N     : 拡大縮小率
python draw_opengl_glfw.py -m 0
python draw_opengl_glfw.py -m 1
python draw_opengl_glfw.py -m 2

python draw_opengl_glfw.py --obj models/car.obj --scale 7.0

動作結果。以下の順番でスクリーンショットを並べてある。
  1. blenderでの表示
  2. ガンマ補正無しの .mtl を使用
  3. ガンマ補正有りの .mtl を使用

model_suzanne01_ss01.png
draw_opengl_glfw_ss03.png
draw_opengl_glfw_ss04_gamma.png

model_car_ss01.png
draw_opengl_glfw_ss05.png
draw_opengl_glfw_ss06_gamma.png

ガンマ補正してあるほうが、blender上での見た目に近い。

もっとも、照明の当て方次第で色合いはどうしても変わってしまうけれど…。

余談。PyOpenGLについて :

Python から OpenGL を利用できるようにする PyOpenGL は、pip でネット経由でインストールすることもできるけど。
python -m pip install PyOpenGL

その版にはGLUT関係の dll が入ってないようで、もしかするとGLUTを使ったスクリプトが動かなかったりするかもしれない。

その場合は、非公式版パッケージを入手してインストールして使うのもアリかも。非公式版なら freeglut.dll 等も入ってる。

_Releases - cgohlke/pyopengl-build
_Ultravioletrayss/OpenGLfile
_cgohlke/pyopengl-build: Build PyOpenGL wheels for Windows

Python 3.10.10 64bit の場合、PyOpenGL-3.1.8-cp310-cp310-win32.whl か PyOpenGL-3.1.7-cp310-cp310-win32.whl、PyOpenGL_accelerate-3.1.7-cp310-cp310-win32.whl を入手してインストールすることになる。

まあ、ネット経由でインストールして使えているなら、それに越したことはないけれど…。

#2 [cg_tools] epsのバージョンについて少しだけ調べてた

3DCGソフト Shade は eps ファイルをインポートできるらしいのだけど、手持ちの Shade 12 Standard、Shade 10.5 Standard では、Inkscape その他からエクスポートした eps をインポートすることができなくて、一体どんな eps ならインポートできるのか気になってきた。

_Shade3D 公式 | Illustratorからのデータインポートについて。
Shadeは、Illustratorのデータ(.ai、.eps)をインポートすることができます。
Illustrator 9以上のデータをShadeにインポートするには、Illustratorで保存時のオプションを以下の設定にする必要があります。

* 互換性:illustrator ver.8.0以前
* プレビュー:なし(EPSの場合)

Shade3D 公式 | Illustratorからのデータインポートについて。 より


8.0以前と言うことは、5.5 や 7.0 で保存した eps が対象ということだろうか?

参考ページ :

Creator行について :

epsファイルをテキストエディタで開いてみると、「%%Creator: 〜」と書かれた行がある。例えば Inkscape 1.4 で保存した eps は以下になってる。
%%Creator: cairo 1.18.2 (https://cairographics.org)

前述の解説ページによると、Adobe Illustrator の場合、以下の3種類の行が存在するようで…。
CreatorTool
AI8_CreatorVersion
Creator

eps の場合、以下になってる、とある。
  • CreatorTool は v11以降に存在。
  • AI8_CreatorVersion は v8 以降に存在。
  • Creator は全バージョンにある。

Shade は「v8以前の eps にしろ」と言ってるから、CreatorTool と AI8_CreatorVersion は存在しなくてもいいのだろう…。となると、Creator と書かれた行さえあればいいということになるのかな。

Inkscape で保存した eps の一部を以下に修正してみた。
%%Creator: Adobe Illustrator(R) 5.5

仮に Shade が「どのツールでエクスポートした eps か」を逐一チェックして処理する/しないを分けているなら、この修正で行けそうだけど…。しかし、Shade でインポートしてみても変化はなかった。

まさか改行コードが関係してないか。通常は、eps は LF で保存されるように見えるけど…。試しに CRLF や CR に変更してみたけれど、Shade上では変化無し。

サンプルファイルが欲しい :

Shade で読み込める eps のサンプルファイルとかどこかにないのかな。Adobe Illustrator 5.5 や 7.0 で保存された eps でもいい。Shade は本当に eps をインポートできるのか? そこからして怪しい。サンプルファイルさえあれば検証できるのに。

いやまあ、フォーラム等を眺めると、昔はインポートできていた事例もあったらしいけど…。

もしかして Mac ならインポートできて Windows はインポートできないというオチだったりしないか?

Adobe Illustrator のバージョンが上がるたびに、出力される .ai も .eps も中身が変わっているというのが結構困る…。

バグの有無も気になる :

Illustrator 7.0 - 10.0 は出力した eps にバグがあるという話も気になる。まさか、バグがある状態の eps じゃないと Shade は正常にインポートできない、なんてオチだったりして…?

_Illustrator - TeX Wiki
_Illustrator 7.0/8.0/9.0 BeginData Bug

余談。Metafile to EPS Converterをインストールしてみた :

Metafile to EPS Converter というツールを使うと、wmf を eps に変換できるらしい。Windows10 x64 22H2上でインストールしてみた。

_LyX wiki | Windows / MetafileToEPSConverter
_Metafile to EPS Converter - TeX Wiki

metafile2eps.exe を入手して実行。今回は "D:\Prog\Metafile to EPS Converter" にインストールしてみた。

たしかに wmf から eps に変換できた。ただ、出力された eps の中身を眺めたら、「%%Creator: PScript5.dll Version 5.2.2」と記述されていた。

以下のページで解説されてる、MS Publisher Color Printer で epsを出力した場合も、「%%Creator: PScript5.dll Version 5.2.2 」と同じ記述になるので、仮想プリンタ経由で eps を出力しているのかもしれない。

_PowerPoint から TeX 用の EPS ファイルを生成する方法 - ソフトウェア開発日記

もちろん、Shade ではインポートできない eps になる。

以上、1 日分です。

過去ログ表示

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