読者です 読者をやめる 読者になる 読者になる

graphics.hatenablog.com

テクニカルアーティストの技術を書き殴るためのメモ帳

プログラマのための Maya 入門 (2) 「ノードとアトリビュート」

 前回 は最初ってことで、とりあえずシーンの真ん中に球を置いてみた。とはいえこれじゃあ、結局 Maya を起動しないと結果が確認できないし、スクリプトのありがたみがよくわからない。
今回はちょっとだけツールっぽくしてみよう。

 CG 制作の流れをここで説明するのは少しむずかしいのだけど、ざっくり言ってこんなかんじになる。

  1. 映像の脚本をつくる
  2. キャラクタをつくる
  3. テクスチャをかく
  4. アニメをつける
  5. レンダリング(撮影)する
  6. レンダリング結果を編集する

今回はひとまず、レンダリングをしてみよう。


というわけで今日のスクリプトはこちら。

# renderer.py
# -*- coding: Shift_Jis -*-
import pymel.core as pm

# 前回つくったシーンを読み込む
pm.openFile("hello.ma", force = True)

# レンダリングの設定
defaultRenderGlobals = pm.ls('defaultRenderGlobals')[0]
defaultRenderGlobals.imageFilePrefix.set('test') # ファイル名は 'test'
defaultRenderGlobals.imageFormat.set(8)          # 拡張子は 'jpg'、設定番号は 8

# レンダリングに使うカメラを取得
cameraList = pm.ls(cameras = True)
camera = cameraList[0]
camera.renderable.set(True)

# レンダリング
pm.render(camera)

実行コマンドは前回とほぼ同じ。

> mayabatch.exe -command "python(\"import rendering\")"

実行結果
f:id:hal1932:20130526124302j:plain

このプログラムには、Maya を理解するためのポイントがいくつかある。ひとつずつ取り上げていこう。


まずは 9 行目。

defaultRenderGlobals = pm.ls('defaultRenderGlobals')[0]

 pm.ls()、もとい pymel.core.ls()、ls ってのはあれだ、linux とかの ls コマンドと同じ。そのシーン内に存在するものを列挙するための API 関数。使用頻度はものすごく高い。

 じゃあ、この test.ma というシーンには何が存在してるんだろう? もしできれば、このスクリプトで openFile した直後に print pm.ls() してほしい。このシーン内に存在するものが全部表示される。長くなるからここには書かないけれど、思ったよりたくさんの項目が出力されたはずだ、defaultRenderGlobals というインスタンスもどこかにあると思う。前回作成した polyShpere や、このあとでつかう Camera クラスのインスタンスもいくつか見つけることができる。

 覚えておいて欲しいのは、単純にみえるシーンでも実はたくさんのオブジェクトで構成されている、ということ。そして、それは ls() で取得できる、ということ。
ついでに、Maya ではこれらのオブジェクトをノードと呼ぶ こと。

 つまりここでは、レンダリングの設定をカプセル化した defaultRenderGlobals というノードを取得した。このノードは、シーンを作成したときに Maya が自動で生成している。

 ちなみに、ls() の返り値は常にリストで返ってくる。[0] でリストの先頭だけ取得してるのはそういう理由だ。defaultRenderGlobals はシーン内にひとつしか存在しないノードなので、返り値のリストも必ず長さが 1 になる。


次にいこう、10 行目。

defaultRenderGlobals.imageFilePrefix.set('test') # ファイル名は 'test'

 さっき、defaultRenderGlobals はインスタンスだ、と言った。正確には、RenderGlobals クラスのインスタンスだ。当然、いくつかのメンバを持ってる。そのひとつが imageFilePrefix、ということになる。

print type(defaultRenderGlobals)
print type(defaultRenderGlobals.imageFilePrefix)
# 出力は以下の通り
# <class 'pymel.core.nodetypes.RenderGlobals'>
# <class 'pymel.core.general.Attribute'>

 RenderGlobals に限らず、Maya で使われるすべてのノードは、アトリビュートというメンバを持っている。ここでは、「ファイル名のプレフィクス」がひとつのアトリビュートとしてカプセル化されている。11 行目の imageFormat も同じく RenderGlobals のアトリビュートだ。

 Maya のシーンを扱うためにはたくさんの設定項目が必要なのだけど、それぞれは適当にカテゴライズ化されてひとつのノードの中にまとまっていることが多い。その設定を書き換えるときは、Attribute クラスの set() メソッドを使う。だいたい想像がつくかもしれないが、設定を取得するときは get() だ。

defaultRenderGlobals.imageFilePrefix.set('test')
print defaultRenderGlobals.imageFilePrefix.get()
# 出力は以下の通り
# test


で、11 行目。

defaultRenderGlobals.imageFormat.set(8)          # 拡張子は 'jpg'、設定番号は 8

 きたこれ、8 ってなんだよ……。
 プログラマとして思うところはたくさんあるとは思うけど、ひとまずは我慢してほしい。Maya にはよくあることなのだ。。
 ちなみに、gif は 0 で、png は 32 だ。tga は 19。確認方法については追々。ここで悩むと紙幅がいくらあっても足りないので、理不尽かもしれないが、とりあえず「そういうものだ」ってことにして次へいこう。


14~16 行目。

cameraList = pm.ls(cameras = True)
camera = cameraList[0]
camera.renderable.set(True)

 defaultRenderGlobals と同じく、シーン内にデフォルトで生成されてるカメラも ls() で取得できる。
 さて、手許で Maya を起動できる人は、とりあえず起動してスペースキーをゆっくり何度か押してみてほしい。1  画面のビューと 4 画面のビューが切り替わると思う。この 4 画面ビューというのはいわゆる三面図とパースペクティブで、ようするに 4 つの視点からみた絵が同時に表示される、つまり、front、perspective、side、top の 4 つのカメラがシーン内に存在する、ということになる。

つまり、こういうことだ。

print cameraList
# 出力は以下の通り
# [nt.Camera(u'frontShape'), nt.Camera(u'perspShape'), nt.Camera(u'sideShape'), nt.Camera(u'topShape')]

もしパースペクティブビューのカメラが欲しければ以下のようにすればいい。

camera = pm.ls('perspShape')[0]

 また、Maya にはレンダリング可能なオブジェクト」という概念 があって、カメラの場合、レンダリング可能なカメラでないとシーンをレンダリングすることができない。Maya のデフォルトではパースペクティブビューのカメラ以外はレンダリング不可能になっている。今回は、cameraList の中でどれがパースペクティブビューのカメラなのかを予め確認しておくのが面倒だったので、とりあえず renderable を True にしてしまった。


さて、19 行目、いよいよレンダリングだ。

pm.render(camera)

といっても、ここで説明することは特にない。撮影したいカメラを指定して render() 関数を呼ぶだけだ。それで test.jpg が生成される。


 test.jpg が生成される……、どこに?


 さて、Maya にはプロジェクトという概念がある。とりあえず Maya を起動して、[ファイル] - [プロジェクト ウィンドウ] を選んでほしい。そこに「現在のプロジェクト」と「場所」が表示されているはずだ。
f:id:hal1932:20130526142017j:plain

 Maya で作業する場合は、必ず毎回「プロジェクトの設定」をしなければいけない。たとえば映像制作の場合は、ひとつのシーンやカット、ひとつのモデルあたりが、1 つのプロジェクトに相当していたりする。プログラムを書く場合でも SetProject という API があるから、それを使ってプロジェクトの場所を設定してやればいい。特に設定をしていなければ、Maya が起動時に default というプロジェクトを勝手に設定する。
 今回の場合は特に何もしていないので、default/images/front/test.jpg というパスに画像ファイルが保存されることになる。なお、images/ というのは出力画像を保存するためのディレクトリ、front はカメラの名前、両方とも Maya のお作法だから覚えておいてほしい。


 プログラムの解説はここで終わりなんだけど、今回はひとつだけ落とし穴を掘ってある。感のいい人は気付いてくれたかもしれないけれど、ここ。

# 前回つくったシーンを読み込む
pm.openFile("hello.ma")

Maya のプロジェクトでは シーンファイルは scenes/ 以下から読み書きされる んだけど、今回はカレントディレクトリから読み込まれてる。これは C/C++ とかの #include と同じ理屈で、まずはプロジェクトのシーンディレクトリ(defualt/scenes/)を検索して、見つからなかったらカレントディレクトリから検索される。

 わりと嵌りどころなのがここで、ディレクトリをちゃんと意識してないと、自分がデフォルトディレクトリを使ってるのかカレントディレクトリを使ってるのかわからなくなったりする。いま読み込んでるのが scenes\test.ma なのか %CD%\test.ma なのか、いま書きだしたのが images\test.jpg なのか %CD%\test.jpg なのか。考え方次第ではあるけど、個人的にはいつもプロジェクトディレクトリを使う(プログラム側から SetProject() する)のがいいと思う。


 あ、そうそう、冒頭で「ちょっとだけツールっぽく」って言ったけど、これのどこがツールっぽいのか。
 Maya の普通の機能で似たようなはできるんだけど、こっちは純然たるプログラムだ、柔軟性と拡張性が違う。連番ファイルで出力した画像を圧縮して、編集素材と一緒にサムネイルムービーを書き出してデータベースに登録しておく、なんてこともわりと簡単にできる。もっとも、実際にそういうことをやるときは大抵 backburner を使うんだろうけど、そのための最初の一歩になるのが今回のプログラム、ってこと。


つづく。