概要
Maya で自動で動画を撮影したい。
モーション確認用だからフラットライトでレンダリングしたい。
こんな需要に2パターンの手法でトライしたので、備忘録的に残します。
前提
- Maya 2020.x
- Lambert マテリアルが既に接続されている
- フラットライト設定のビュー上で得たい見た目になっている状態
目次
2つの手法の比較
2つの手法を試してみましたが、カメラを事前に準備できる場合には Playblast を使う方が楽だと思いました。
surfaceShader | Playblast | |
---|---|---|
メリット | ・好きなレンダラーが使える | ・シーンのノード構成を汚さない(カメラには変更が入るのでリストアは必要) ・シーン構成に左右されない |
デメリット | ・シーンのノード構成に変更が入る(リストアは可能) | ・レンダラーはビューで使用できる物に限られる |
surafaceShader を使う方法
マテリアルを SurfaceShader で差し替えてしまう方法です。
シーンに変更が入ってしまいますが、好きなレンダラーで撮影が可能です。
処理概要
- 対象のメッシュから ShadingEngine(シェーディンググループ) ノードを取得
- ShadingEngine から接続されているマテリアルを取得し、さらに接続された Color と Transparency を取得
- surfaceShader ノードを作成し、接続
- マテリアルから取得した color, transparency インプットに接続
- outColor を ShadingEngine に接続(マテリアル差し替え)
あとはお好みのレンダラーで撮影してください。
差し替え前のマテリアルと ShadingEngine ノードを保持しておけばリストアも可能です。
サンプルコード
コード
※ OpenMayaApi 2.0 を使ってますが、 cmds でも問題なく実現できると思います。
from maya.api import OpenMaya as om2
def connect_surface_shader():
"""対象のメッシュを取得し、マテリアルを Surface Shader に変更する。"""
sg_object_list = []
# シーンから Mesh Dag イテレーターを取得
mesh_dag_itr = om2.MItDag(om2.MItDag.kDepthFirst, om2.MFn.kMesh)
while not mesh_dag_itr.isDone():
mesh_path: om2.MDagPath = mesh_dag_itr.getPath()
mesh_dag_itr.next()
dag_fn = om2.MFnDagNode(mesh_path)
mesh_plugs = dag_fn.getConnections()
for mesh_plug in mesh_plugs:
destinations = mesh_plug.destinations()
if not destinations:
continue
# シェーディングエンジンが接続されているかチェック
for dest in destinations:
node = dest.node()
if node.apiType() == om2.MFn.kShadingEngine:
is_has_shading_engine = True
# シェーディングエンジンをリストに追加
if node not in sg_object_list:
sg_object_list.append(node)
for sg_object in sg_object_list:
sg_dnode_fn = om2.MFnDependencyNode(sg_object)
# Shading Group からマテリアルを取得
surface_shader_plug = sg_dnode_fn.findPlug("surfaceShader", True)
# マテリアルが接続されていない場合はスキップ
if not surface_shader_plug.isConnected:
continue
# マテリアルが Surface Shader の場合はスキップ
material_node = surface_shader_plug.source().node()
material_dnode_fn = om2.MFnDependencyNode(material_node)
if material_dnode_fn.typeName == "surfaceShader":
continue
# カラーと透明度を取得
color_plug = material_dnode_fn.findPlug("color", True)
transparency_plug = material_dnode_fn.findPlug("transparency", True)
# Surface Shader ノードを作成
surface_shader_node_name = cmds.shadingNode(
"surfaceShader",
name=f"{material_dnode_fn.name()}_SurfaceShader",
asShader=True, skipSelect=True)
# カラーと透明度を接続
if color_plug.isConnected:
cmds.connectAttr(
color_plug.source().name(),
f"{surface_shader_node_name}.outColor",
force=True)
if transparency_plug.isConnected:
cmds.connectAttr(
transparency_plug.source().name(),
f"{surface_shader_node_name}.outTransparency",
force=True)
# Surface Shader を Shading Group に接続
cmds.connectAttr(
f"{surface_shader_node_name}.outColor",
f"{sg_dnode_fn.name()}.surfaceShader",
force=True)
Playblast を使う方法
Playblast はビューの見た目をそのまま撮影できるため、シーンを汚さなくて済みます。
ただし、ビューの状態はユーザによって様々です。
そのため、撮影用のビューを生成してしまうのが楽です。
処理概要
- 撮影用の ModelPanel を作成
- ビューの設定
- HUD, 各種表示等
- カメラの設定
- フィルムゲート、 MSAA 等
- Playblst 撮影
- カメラ設定のリストア
サンプルコード
コード
import maya.cmds as cmds
def rec_playblast(
window_name: str, panel_name: str, rec_camera: str,
output_path_without_ext: str, width: int, height: int):
"""
Playblast で撮影する
Args:
window_name (str): 撮影用 Window の名前
panel_name (str): 撮影用 Panel の名前
rec_camera (str): 撮影に使用するカメラ
output_path_without_ext (str): 出力ファイルのパス(拡張子なし)
width (int): 横幅
height (int): 縦幅
"""
# 同名の Window や Panel が存在する場合は削除
if cmds.window(window_name, exists=True):
cmds.deleteUI(
window_name, window=True)
if cmds.modelPanel(panel_name, exists=True):
cmds.deleteUI(
panel_name, panel=True)
# Window 作成
model_window = cmds.window(window_name)
# Window のレイアウト作成
layout = cmds.formLayout()
# Model Panel 作成
model_panel = cmds.modelPanel(panel_name)
# Model Panel を Window に配置
cmds.formLayout(
layout, edit=True,
attachForm=(
(model_panel, 'top', 0),
(model_panel, 'left', 0),
(model_panel, 'bottom', 0),
(model_panel, 'right', 0)))
# Window が閉じられた時に ModelPanel も削除する CloseCommand を設定
cmds.window(
model_window, edit=True,
closeCommand=f"cmds.deleteUI('{model_panel}', panel=True)")
# Model Editor を取得
model_editor = cmds.modelPanel(model_panel, query=True, modelEditor=True)
# Window を表示
cmds.showWindow(model_window)
# ビューポートの HUD を非表示
cmds.modelEditor(model_editor, edit=True, headsUpDisplay=False)
# ポリゴンのみ表示
cmds.modelEditor(model_editor, edit=True, allObjects=False)
cmds.modelEditor(model_editor, edit=True, polymeshes=True)
# 各種ビュー設定
cmds.modelEditor(
model_editor, edit=True,
grid=False, # グリッド非表示
displayAppearance="smoothShaded", # スムーズシェーディング
displayLights="flat", # フラットライト
displayTextures=True, # テクスチャ表示
xray=False, # X線表示 OFF
activeComponentsXray=False, # X線アクティブコンポーネント表示 OFF
jointXray=False, # X線ジョイント表示 OFF
selectionHiliteDisplay=False # 選択ハイライト表示 OFF
)
# カメラの表示オプションとアトリビュートのバックアップ
pre_film_gate = cmds.camera(rec_camera, query=True, displayFilmGate=True)
pre_resolution_gate = cmds.camera(rec_camera, query=True, displayResolution=True)
pre_gate_mask = cmds.camera(rec_camera, query=True, displayGateMask=True)
pre_field_chart = cmds.camera(rec_camera, query=True, displayFieldChart=True)
pre_safe_action = cmds.camera(rec_camera, query=True, displaySafeAction=True)
pre_safe_title = cmds.camera(rec_camera, query=True, displaySafeTitle=True)
pre_msaa = cmds.getAttr("hardwareRenderingGlobals.multiSampleEnable")
pre_ssao = cmds.getAttr("hardwareRenderingGlobals.ssaoEnable")
# カメラの表示オプションとアトリビュートの設定
# FilmGate ~ displaySafeTitle までの表示を OFF
cmds.camera(
rec_camera, edit=True,
displayFilmGate=False, # フィルムゲート表示 OFF
displayResolution=False, # 解像度ゲート表示 OFF
displayGateMask=False, # ゲートマスク表示 OFF
displayFieldChart=False, # フィールドチャート表示 OFF
displaySafeAction=False, # セーフアクション表示 OFF
displaySafeTitle=False # セーフタイトル表示 OFF
)
# MASS, SSAO を有効化
cmds.setAttr("hardwareRenderingGlobals.multiSampleEnable", True)
cmds.setAttr("hardwareRenderingGlobals.ssaoEnable", True)
# カメラを割り当て
cmds.modelEditor(model_editor, camera=rec_camera, edit=True)
# 撮影
# MEMO: viewer=True にすると何故か返り値から拡張子が消えるので filename を指定した方が良い
cmds.playblast(
filename=output_path_without_ext,
forceOverwrite=True,
editorPanelName=model_panel,
widthHeight=[width, height],
format="avi",
compression="none",
percent=100,
quality=100,
offScreen=True,
viewer=False)
# カメラの表示オプションとアトリビュートのリストア
cmds.camera(
rec_camera, edit=True,
displayFilmGate=pre_film_gate,
displayResolution=pre_resolution_gate,
displayGateMask=pre_gate_mask,
displayFieldChart=pre_field_chart,
displaySafeAction=pre_safe_action,
displaySafeTitle=pre_safe_title)
cmds.setAttr("hardwareRenderingGlobals.multiSampleEnable", pre_msaa)
cmds.setAttr("hardwareRenderingGlobals.ssaoEnable", pre_ssao)
# 撮影用 Window を削除
cmds.deleteUI(model_window, window=True)
余談(Arnold Renderer)
Arnold Render のシェーダーオーバーライド機能 でシェーダーを SurfaceShader に切り替えたらサクッとできるのでは?と考えたんですが、オーバーライド前のマテリアルに接続されたインプット(テクスチャの接続等)は無視されるので期待する動作になりませんでした。
まぁ、シェーダー間でアトリビュートの名前が統一されてないので無理ですよね。。。