2023年12月19日火曜日

BlenderでAmazon Lumberyard Bistro を読み込む

Blender で Amazon Lumberyard Bistro を読み込む

この記事は レイトレ(Raytracing) Advent Calendar 2023 の18日目の記事です。

はじめに

グラフィックス系の論文では実用的なレンダリングシーンの例として Amazon Lumberyard Bistro [1] がしばしば実験に用いられます。 自作のレンダラーでこのシーンを試す方法として Blender [2] でシーンを読み込み glTF などの形式に変換して使用するという方法がありますが、ただ Blender に読み込むだけでは期待した動作にならなかったため、シーンを修正するために行ったことをノートとして残します。但し、シーンを修正するにあたりオブジェクトを手作業で一つ一つ編集するといった時間のかかる作業は行わないようにしています。また、この記事では Blender 4.0.2 で作業を行っています。

Blender でのスクリプト実行について

Blender は Python Interpreter を内蔵しており Python スクリプトを実行してシーンを編集することができます (Blender 4.0.2 時での Python のバージョンは 3.10.13 です) 。 Python スクリプトを実行するにあたって、まずは UI 選択で Text Editor を選択しエディターを表示します。

エディター上で Python スクリプトを記述し実行ボタンを押すことでスクリプトを実行できます。

実行時に標準出力にテキストを出力したり、実行エラーメッセージを確認したい場合は Linux なら標準の端末 (端末からBlenderを実行している場合) 、 Windows なら Blender のメニューの "Windows > Toggle System Console" を選択してコンソールを表示することで確認することがきでます。

スクリプト実行でシーン編集した内容は Blender の Undo 操作で戻すことができるので、 試行錯誤しながら Python スクリプトを記述することもできます。 また、Blender のメニューの "Edit > Preference > Interface" にある Python Tooltips を有効にすることで GUI 上の各種メニューやボタンに相当する Python コマンドを確認できるようになるので、スクリプトを記述する上でとても便利です。

Blender での Python スクリプトについてのより詳しい情報は Python API Documentation を参照して下さい。

シーンの読み込み

読み込み前の準備

本記事ではオブジェクトがまったく無い状態からシーンの読み込みを行います (Blenderで新規シーンを作成してデフォルトで作成されたオブジェクトを全部削除した状態) 。IBL (Background) は無効にしています。また、レンダリングには Cycles レンダラーを用いてデノイザーをオフにして行っています (デバイスバックエンドは HIP を使用しています Cycles GPU Rendering) 。

Amazon Lumberyard Bistro (v5.2)

配布ページに載っている Bistro_Night.png のような画像をレンダリングできることを目標にします。 まずはダウンロードした BistroExterior.fbx を Blender にインポートします。但し、本記事ではアニメーションは考慮しないので fbx をインポート時にアニメーションは無効にします。

シーンをインポートしたら Bistro_Night.png と同じカメラアングルになるようにカメラを下の画像のように設定します。

また、指向性光源 (Sun Light) は使わないので directionalLight1 オブジェクトをシーンから削除します。 この状態でレンダリングを開始すると、

上画像のように光源以外ほとんど真っ暗な画像になります。 光源の Strength が足りないため以下のスクリプト (Emission.py) を実行して全ての光源マテリアルの Strength を上げます、


import bpy # Blenderのデータ構造にアクセスできるようにする

def strengthenEmission(material):
    # マテリアルのノードツリーが無かったり'Principled BSDF'の無いマテリアルは無視する
    if (not material.node_tree) or (material.node_tree.nodes.find("Principled BSDF") == -1):
        return

    # ノードツリーの中から'Principled BSDF'を取り出す
    bsdf = material.node_tree.nodes["Principled BSDF"]

    # Emissionカラーに黒以外が指定されていたりノードが接続されている場合はEmission Strengthを上げる
    emission = bsdf.inputs[26]
    if (len(emission.links) > 0) or ((emission.default_value[0] + emission.default_value[1] + emission.default_value[2]) > 0):
        emission_strength = bsdf.inputs[27]
        emission_strength.default_value *= 512

if __name__ == "__main__":
    # 全てのマテリアルをチェックする
    for material in bpy.data.materials:
        strengthenEmission(material)

このスクリプトでは光源の Strength を 512 倍しています。実行後、マテリアルは以下の画像のように変化しています、

レンダリング結果は、

のようになり、明るさは Bistro_Night.png と比較してそれっぽくなりました。 しかし、床や手前のバイクを見てみるとスペキュラーな反射の具合がおかしく見えます。 Blender上でバイクのマテリアルを見てみると、

スペキュラーテクスチャーが Principled BSDF の Specular のパラメータに接続されています。 一方で、ダウンロードした Bistro ファイル内の README.txt を読むと、

Textures (compressed .DDS) designed for GGX-based metal-rough PBR material system with the following convention:   
- BaseColor  
    RGB channels: BaseColor value  
    Alpha channel: Opacity  
- Specular  
    Red channel: Occlusion  
    Green channel: Roughness  
    Blue channel: Metalness  
- Normal (DirectX)  
- Emissive  
    RGB channels: Emissive color

と記述されており、スペキュラーテクスチャーの緑成分は Roughness 、青成分は Metalness として使われる事が想定されています。 そこで、全てのスペキュラーテクスチャーの接続を修正するために以下のスクリプト (MetallicRoughness.py) を実行します、


import bpy # Blenderのデータ構造にアクセスできるようにする

def linkMetallicRoughnessTexture(material):
    # マテリアルのノードツリーが無かったり'Principled BSDF'の無いマテリアルは無視する
    if (not material.node_tree) or (material.node_tree.nodes.find("Principled BSDF") == -1):
        return

    # ノードツリーの中から'Principled BSDF'を取り出す
    bsdf = material.node_tree.nodes["Principled BSDF"]

    # Specularパラメータにノードが接続されている場合、MetallicとRoughnessパラメータに繋ぎかえる
    specular = bsdf.inputs[12]
    if len(specular.links) > 0:
        specular_texture = specular.links[0].from_node
        metallic = bsdf.inputs[1]
        roughness = bsdf.inputs[2]
        # テクスチャーの緑成分と青成分を取り出すために'SeparateColor'ノードを作成する
        rgb = material.node_tree.nodes.new("ShaderNodeSeparateColor")
        # スペキュラーテクスチャーノードのリンクを一度解除する
        material.node_tree.links.remove(specular.links[0])
        # スペキュラーテクスチャーノードを'SeparateColor'ノードに接続し、緑成分をRoughnessに青成分をMetallicパラメータに接続する
        material.node_tree.links.new(specular_texture.outputs[0], rgb.inputs[0])
        material.node_tree.links.new(rgb.outputs[1], roughness)
        material.node_tree.links.new(rgb.outputs[2], metallic)
        # スペキュラーパラメータはデフォルト値に戻す
        specular.default_value = 0.5

if __name__ == "__main__":
    # 全てのマテリアルをチェックする
    for material in bpy.data.materials:
        linkMetallicRoughnessTexture(material)

実行後は以下のようにスペキュラーテクスチャーが Metallic と Roughness に繋がっている事が確認できます、

この状態でのレンダリング結果は、

となり Bistro_Night.png と似た画像をレンダリングすることができました。

終わりに

本記事では Amazon Lumberyard Bistro を Blender で読み込み Bistro_Night.png と似たレンダリングができるように Python スクリプトを用いてシーンの修正を行いました。 一度 Blender 上で期待したレンダリングが出来るか確認する作業は、その後で glTF 形式などで自作のレンダラーに読み込ませレンダリングする際の基準としても使えるので確認しておくのは良いと思います。

参考

  1. Amazon Lumberyard, Amazon Lumberyard Bistro, Open Research Content Archive (ORCA), url=http://developer.nvidia.com/orca/amazon-lumberyard-bistro
  2. Blender Online Community, Blender - a 3D modelling and rendering package, url=http://www.blender.org