2016年7月23日土曜日

VRayのRenderPassを用いたあれやこれや

気付けば今年最初の投稿となるようです。明けましておめでとうございます。
VRayのレンダーパスを用いたテクニックをいくつか。


①worldPositionパスを用いてある座標からの距離でマスクを作成。(右の上から2つめ)

②diffuseパス ÷ ライトセレクトで特定ライトの強度を取得(真ん中の上から2つ目)

③ ① x ②で特定ライトの特定エリアのマスクを作成し特定エリアのライトの強度を調整

-----------------



Shadowパスはプラス(加算)する事で落ち影のない画像を取得出来る。

from(「影なし画像」引く「shadowパス」。引算。加算の逆なので元の影がある状態に戻る)で合成する際に、重ねる強さなどを変える事で影の濃さや色を調整出来る。

HDRI : Logoscope

2015年10月8日木曜日

ベクトル計算の基礎

ベクトルの計算が出来るようになると、CGのシェーダー、パーティクル制御、コンポジットの画像処理など、様々な分野での応用が期待出来る。しかし、ベクトルの計算はなんか難しい。そこで、次元を落として考えてみるのはどうだろうか?

一次元

一次元のものを「ベクトル」と言っていいのかどうか謎ですが・・・。(数学や物理の世界では1つの値を持つものは「スカラー」)と言われている。)

ある点、AとBがあったとする。Aは原点から3の距離であり、Bは5の距離である。

AからB、BからAなどのベクトルを得る場合は、ベクトルの引き算を行う。

B-A=AからBへのベクトル
5 - 3 = 2

2というベクトル(?)が得られた。Aから+2の所にBがあるという意味合いですかね。
実際にはベクトルは「向き」と「長さ」を持っているため、得られたベクトルは「プラス方向」に「長さ2」のベクトルとなるのだろうか。これから分かるように、ベクトルと言った場合、その値はAから見て・・・といったような「相対的なもの」である事に注意したい。

A - B  = BからAへのベクトル


3 - 5 = -2
「マイナス方向」に長さ「2」というベクトルが得られた。

一次元で考えるととても分かりやすいですね。

長さの計算
一次元だと値=長さなので、計算する意味まったくないけど、
2乗してから√するって覚えておく。

sqrt(x^2)
pow( x^2 , 0.5 ) 

意味ねー!!次元が増えると役立つ。

※ルートって1/2乗の計算と同じなんだ。つまり0.5乗でも結果同じ。詳しくは分からないが、プログラムの世界ではこちらの記述の方が計算速度が速いというような話を聞いたことがある。確かによく見かける気がするが。




単位ベクトルの取得(ノーマライズ)

これは、ベクトルの計算などをシンプルに行うため等に用いられるもので、ベクトルの長さを1にしたベクトルの事を指す。

自分の長さで割ってあげる



ベクトル / ベクトルの長さ

上の場合だと 2/2 = 1.0
下の例だと -2/2 = - 1.0 (分母が正の値ですが、「長さ」なので基本的に正の値になります!)

ベクトルの計算 足し算
この得られたベクトルを使ってAを動かす場合、
得られたベクトルをAに足してあげれば、AはBの位置に移動する。

A = A + V

動かす度合を変えたい時は、単位ベクトルが便利で
Bの方に1だけ動かすとか0.5だけ動かすとか

A = A + NV(長さ1のベクトル)
A = A + NV*0.5

二次元


二次元であっても同じである。

B - A = AからBへのベクトル

V(x , y) = B ( x, y) - A ( x , y) 
V(x , y) = B (4 , 1) - A(1 , 3) = V (3 , -2)

x、y それぞれで引き算行えばいいす。

そうすると、AからX +3 、Y -2の方向にBがいると言った意味のベクトルが得られる。が、次元が増えた事によりベクトルの長さがですね・・・・下の図注視するとなんか直角三角形が見えてこないですか?



そして、ベクトルの引き算をした際に、aとbの辺の長さが既に取得出来ていたりしませんか?


ピタゴラスの定理きた----!となる。

ということで V(length) = sqrt( 2^2 + 3^2) = 3.60555

もしくは pow ( (2^2+3^2) , 0.5) ・・・ (2^2+3^2)^0.5 という意味です。


出ました。ベクトルの長さ = 3.60555

今回の場合、ベクトルはV(3,-2)とYの値に負の値を持ってますが、計算式の性質上、累乗の計算があり、かならず求められる長さは正の値となるため、そのまま使って問題ないです。

Length = sqrt (3^2 + -2^2)
Length = sqrt (9 + 4)
Length = sqrt (13)
Length = 3.60555


で、単位ベクトル(長さ1のベクトル)はこの得られた長さを使って自分のx , yの値を割ってあげる事で求める事が出来る。


単位ベクトル NV = V (x / length , y / length)

NV = ( 3/3.60555 , -2/3.60555)
NV = ( 0.832051 , -0.5547)

出ました。単位ベクトル。

このように、次元が増えても、各次元で計算すればよい事が多く、2次元がクリアー出来れば、後は単純に次元が増えていくだけなので、3次元のベクトルの計算は基本的には2次元で使った式の次元を増やすだけとなる。

たとえばピタゴラスの定理もこうすればいいらしい。


ちなみに3dsMAXのTPのDistanceノードはこれらの計算を一発でやってくれる素晴らしい、「神、いわゆるゴッドなノード」だと、エフェクトスペシャリストの米岡さんに教えて頂いた。「Distance」という名前から「距離」をイメージしがちだが、本質的にはベクトルの長さと、単位ベクトルの取得になります。

また、代表的なベクトルの計算はMathノードで可能となっている。
計算式を一々打ち込んだり、ノード組まなくてもいいのでとてもありがたいですね。
神、いわばゴッド。

Maxscriptによるベクトルの操作はこちらを参照 Maxscript その9 「座標とベクトル point3値」


以前、「Nukeで3DCGのノーマルマップを使ったLambert Shaderの表現」の言ったような記事を書いたのですが、ノーマル(法線情報)も面の向きのベクトルなので、これも初歩的なベクトルの計算だけで出来る事になります。基本的なベクトルの計算を知るだけでも非常に沢山の表現が手に入るのではないかと思います。

距離を色に変えてみたりとか。

重力でものを落としたいとか
これも、ニュートンの法則が・・・とか考えずに、毎フレーム、自分のベクトルに重力のベクトルを足すだけでよいとか(重力による加速度といった考え)Maxだと一々スペースワープで重力を作る必要はないわけで・・・。

CGの「Velocity」などもベクトルである。「RealSmartが・・・」などの単語をよく耳にするが、その根本はベクトルであるため、この辺りを理解する事でVelocityチャンネルなどを適切に扱えるようになってくるのではないかと思う。

「ベクトルの内積 / Dot Product」、「ベクトルの外積 / Cross Product」 などが加わるとパーティクル制御などの幅がめちゃくちゃ広がると思います。

ベクトルの計算でよく分からなくなったらx軸だけとか1次元に落として考えてみる。

取り急ぎ失礼します。

2015年5月31日日曜日

カラーマネジメントについて思う事

左:適切に見れていない 右:適切に見れている

素材を確認する環境や設定によって、同じデータでも見え方が大きく異なる事例をいくつも確認出来ている。撮影現場の見え方と作業現場の見え方の違いetc。この事に気づいていない人も多いのではないだろうか?

カラーマネジメントはVFX制作環境の根本に関わる事だと僕は考えている。この部分が土台としてしっかりあって、はじめて見え方や方法論など議論が可能になるのではないのかな?と。

現在、日本の環境の多くは左の環境である事が多いように思う。特にカラーマネジメントをしていない環境。その環境で見え方や方法論を語る事に意味があるのか?「こうすると光り方が綺麗に見えるよ!」などなど。

僕は、まだそれら議論が可能な段階にすらなっていないのでは?と感じている。

カラーマネジメントされていない環境でチャートを撮影する事に何の意味があるのだろうか?

リニアワークフローやモニターキャリブレーション以前の問題だと思う。それは細部の話であって、まずは、全体のカラーマネジメントを行って素材の管理や制作環境が整って初めてVFX制作が行えるのではないのかな?と。今、語られている多くの方法論は「カラーマネジメントされていない環境」「出力デバイスや画像の色に依存した方法論」なのではないかな・・・・と。

カラーマネジメントがあって、はじめて骨太で確かな技術を張り巡らせれるのではないかな?と。ヴィジュアルエフェクト。名前の通り、見た目、非常に重要だと思うんだ。カメラの色や、異なる規格、例えばBT.2020などが来ても揺るがない環境。

そのような環境になるといいな。など、最近考えている次第。

僕自身がまだ、プロジェクト等で色々思考錯誤している段階でズバリな記事を書けない事があれなんだけど・・・。今、個人的に一番ホットなのは、上の画像のような高輝度の表現。エフェクトなどの見え方にとても直結している部分。エフェクトのルックデブ環境について。特にRGBの原色に近い部分に顕著に表れる。

「そもそも、そのレンダリング画像、適切に見れてないんじゃ?」って結構確信に近い思いがある。適切に見る事が出来て、初めてパラメーターとかの話が出来るんじゃないか?

(VFX制作のカラーマネジメントは難しいですよね。「正しい」だけではなく「好ましさ」も考慮しなくてならない。間違っていても、それが「好ましい」と判断されれば、それに柔軟に対応していかなければいけない。それはとても難しい事だと思う。)

これはカラーマネジメントに関わらず、全ての事に言える事だと思う。確かな基礎があって、初めて応用を張り巡らす事が出来るといいますか。僕の中にどこか色んな基礎的な事がすっぽり抜けている感覚が沢山あって・・・。

僕の理想とする環境は、「それは間違っている!」「正しくない!」とかではなくて、必要な環境を構築し、アーティストにはそこで自由に作品を作ってもらって「こうするといいよ!」とか「こうしてみた」みたいな議論がされていったらいいなぁ・・・。となんとなく思い描きつつ・・・。

2015年2月3日火曜日

Maxscript その18 「for式 ループ処理その1(+whileループ)」

ループ処理の基本となるforループ、for式。「for文」などで検索すると、説明しているサイトが沢山出てくると思う。
---------
■for式1

【構文】
for i = 0 to 10 do print i

for <変数> = <初期値> to <終了値> [by <変化値>] do <式>

使える値はinteger値、float値、time値

byで変化値を変える。 byを記述しない場合の変化値は1。

for i = 0 to 10 by 2 do print i
for i = 10 to 0 by -2 do print i

複数の処理を繰り返し実行したい場合は do の後の<式>にブロック式を用いる

for i = 1 to 10 do (
    obj = Sphere()
    obj.radius = 5
    obj.pos = [i*10,0,0]
    c = (255/10)*i
    obj.wirecolor = [c,c,c]
)

上のスクリプトは多分下の書き方でも同じだと思う・・・。
for i = 1 to 10 do (
    obj = Sphere radius:5 pos:[i*10,0,0] wirecolor:(random [0,0,0][255,255,255])
    --色の記述ちょっと変えた
)

【for式で使われる変数のスコープ】

for式で定義される変数(下の場合「i」)は、for式の中だけで有効なローカル変数となるようだ。

(
    for i = 1 to 10 do (
        print i
    )
    print i
)

この場合、最後のprint iの実行結果は「undfined」 となる

【戻り値】
for式も式なので処理が終わると「OK」という答えを返す。
a = for i = 1 to 10 do ()



OK値(値>特殊データ値)

【多重ループ

どのような順番で処理がされるているか注意して確認してみる。

num = 3
for x = 1 to num do (
  for y = 1 to num do (
  for z = 1 to num do (
  format "x = %\ty = %\tz = %\n" x y z
  )
  )
)





























---------
■for式2
for <変数名> = <初期値> to <終了値> [by <変化値>] collect <変数名>

x = for i = 1 to 10 collect i
print x

この場合のfor式の戻り値は「OK」ではなく、iに代入された全ての値の配列となる。

x = for i = 1 to 10 (
    collect i*2
)
print x
---------
tips:強制ループ終了 exit
一般的にループを途中で中断させるための命令が用意されている。maxscriptにもそのためのexitという関数があるが・・・。


どういう事だ?と調べようとしたらリファレンスが「ループ終了のページ→forループのページ→ループ終了のページ」とループしていた!流石ループの説明だ。exitしたい。

exit使うとパフォーマンスが落ちるのでwhileかwhereを使ってね。という事らしい。


【例】 -- 0~100からランダムで値を取得し、50より大きい場合はforループを終了させる。
(※説明用にfor式を使っているが、この場合は下で説明するwhileループの方が適しているかもしれない)

x = 0
for i = 1 to 100 while x <50 do (
    x = random 0 100
    print x
)

while<式>の答えは(True/False)となっており、答えがFalseとなると処理が中断される

意味はないが、処理がされないfor式は以下のようになる。

for i = 1 to 100 while false do ()
---------
tips:for式とfloat値

コンピューターでのfloat値の扱いには色々あるらしい。以下は、maxの側の問題なのかコンピュータで演算する際の問題なのかは不明だが、for式の値にfloatの値を使うと以下のような事が起きる場合がある。(検索「浮動小数点 丸め誤差」)

for i = 0.0 to 10.0 by 0.1 do print i


for式に使う値は出来るだけintegerを使うようにした方が良いかもしれない。

for i = 0 to 100 do (
    print (i/10.0)
)

---------
tips:whileループとDoループ

本来、Tipsではなく、別で扱った方がいいくらい代表的なループ処理の一つ。無限ループとも言われたりしますか?Maxscriptでは2つのループが準備されている。条件の記述を間違うとループ処理が終わらず、永遠とループ処理がされてしまう。「ESC」キーを長押しすることで強制的にループ処理を止める事が出来るようだ。たまに、Maxの強制終了により強制的にループ処理を抜ける事もある。ワイルドだぜ。

do <式> while <条件>
while <条件> do <式>

例:Xに10を代入し、Xを1つづ引いていく。Xが1より小さくなるとループ処理が終わる。
x = 10
while x>0 do print (x-=1)

doとwhileの違いはdoはwhileの前に処理があるため、最低1回は処理が行われる

x =10
do (print (x-=1)) while (x>10)

x = 10
while (x>10) do (print (x-=1))
---------
ここまで来るとスクリプトで出来ることがとても増えてくる。特に大量の情報を扱うCG制作において、このループ処理がもたらす効果は本当に大きい。実際どのように使うか等の具体例は後ほどまとめて掲載したいと思う。

2014年12月31日水曜日

Maxscript その17 「3d変換行列(3D Transform Matrix) matrix3」

ここでは3D変換行列(3d Transform Matrix)の基本的な操作について少しだけ、ザックリと触れたいと思う。

3D変換マトリックスは何かというと、3Dで扱うオブジェクトの変換=移動・回転・スケールの情報らしい。それが、どのようなものなのか理解しようとすると、それはそれは(僕にとって非常に)高度な数学の知識を必要とし、理解するのが難しい。


ここでは3D変換マトリックスをシステムと捉え、それを使った基本的なトランスフォームの操作を見ていってみたいと思う。誰か知らないけど、3Dの変換を管理する素晴らしいシステムを作ってくれた。その基本的な使い方と概念。


maxscriptでは <ノード>.transformとすることで3D変換マトリックスの数値を取得/設定できるようだ。





取得した値はmatrix3という値になっており、ワールド座標基準の4つのベクトル情報を持つ。



matrix3 X軸 Y軸 Z軸 位置(オフセット)



となっている。何も操作していない状態だと以下のようになる。






X軸方向のベクトルの長さを2倍にすると次のようになる。




x,y,z,positionをコントロールするポイントを作って、各位置情報をteapotのトランスフォームに値を渡してあげるような仕組みを作ってみた。変化が分かりやすい。




4つのベクトルを使って、移動回転スケール(スキュー)が表現出来るようだ。凄いっすね・・・。

数値の説明は終わり。水色の数値から直感的に色々イメージするのは非常に難しいため、基本的にはmatrix3の数値は見ない。考えない。



matrix3値を操作するための様々な関数が用意されているが、ここでは割愛。詳しく知りたい人はscriptリファレンスの「matrix3」の項目を見て下さい。



<3D変換行列の演算 変換の継承>

リファレンスによると演算子は



演算子

+

-

*
as


とあるが、ここでは乗算(掛け算)のみ見てみようと思う。3D変換行列の基本的な演算は乗算になるようだ。


原点にあるAの変換(移動回転スケールがデフォルト値)をBに持って行く計算



Bの変換行列を取得してAの変換行列に掛け算してあげる事で、Aの変換がBの変換と等しくなる。

何も変換していないマトリックスは

matrix3 1

(matrix3 [1,0,0] [0,1,0] [0,0,1] [0,0,0])

として取得する事も可能なようだ。

$.transform = matrix3 1

で、変換をリセット出来ると思う。

トランスフォームの継承/親子関係(ペアレント)
3Dのオブジェクトのリンク(ペアレント=親子関係)の計算は基本的にはこの原理になっているようだ。

ザックリな感じであれですが、AとBのトランスフォームを掛け算した値をCに渡すと、CのトランスフォームはAとBのトランスフォームを継承した値になるようだ。いわゆる親子関係(リンク、ペアレント、コンストレイント)


だたし、掛け算する順番を変えると結果が異なるので注意が必要なようだ。

Bのトランスフォーム * Aのトランスフォーム → BがAの子の時のトランスフォーム
Aのトランスフォーム * Bのトランスフォーム → AがBの子の時のトランスフォーム

複数のペアレント構造を計算する場合も、同じ計算手順で。


Bから原点に戻す変換行列の求め方

考え方が非常に良く似ているので、ベクトルの操作の基本を見てみる。
教えて頂いたMark代表取締役 犬童さんに感謝

BからAのベクトルを求めたい時、AのベクトルからBのベクトルを引き算する事で、BからAのベクトルが取得出来る。

V = A.pos - B.posのような感じで。

B.pos = B.pos + V

とすると、当たり前だけど、ティーポットは原点に移動する。

ベクトルの計算(位置)だけだとシンプルだが、回転、スケールも考慮する場合はどうだろうか?そのような際に3D変換マトリックスを使った演算がとても強力だと思う。

原点からBの変換行列は、B.transform の値そのもの


Bから原点の変換行列(逆行列)はinverseを使って求める事が出来るようだ。


そして、自分の変換行列に、自分の逆行列を掛け算してあげると・・・
ティーポットが原点に移動し、回転、スケールもリセットされる。



inverseは反転。つまり「-」と考える事が出来るので

原点の変換行列 = 元の行列 - 元の行列

と考えると覚えやすいか。


相対的な変換行列の取得

AからBへのベクトル = B.pos - A.pos
AからBへの変換行列 = B.transform * (inverse A.transform)


BからAへのベクトル = A.pos - B.pos
BからAへの変換行列 = A.transform * (inverse B.transform)

---
TM = A.transform * (inverse B.transform)
B.transform = TM * B.transform

とすると、Bの変換がAの変換と同じになるのが確認出来るかと思う。

【実用例】動いているオブジェクト(Teapot001)に追従(リンク)するカメラ(Camera001)をオブジェクトを止めて同じように見えるようにする

新しく動いていないTeapot002と動いていないCamera002を作成して
動いていないカメラ、Camera002のTransformにScriptコントローラーを適用して以下のように記述。

トラッキングした情報をカメラに適用するか、オブジェクトに適用するかなども同じ仕組みだと思われる。オブジェクトを止めた状態でカメラの動きをつけて動いているカメラにその変換情報を渡してあげるとか。


<解説>
1.$Camera001.transform * (inverse $Teapot001.transform)とし、Teapot001からCamera001への相対的な変換マトリックスを取得

この変換をワールド座標で表すと以下の感じだと思われるので、ワールド原点を基準にしたい場合はこれだけで終わり(なはず・・・)。

2. 1で得た変換マトリックスをTeapot002の所に持ってくる。(Teapot002の子にする)
この計算をした場合、Teapot002の変換が考慮されているので、Teapot002を動かしたり、アニメーションを付けたとしても、Cameraとの相対関係は維持されたままになる(はず)。


カメラのトランスフォームをワールドX軸で対象にする
$Camera002.transform = $Camera001.transform * (scaleMatrix [-1,1,1])
とするとシンプルなのだが、この場合スケールの値がマイナスとなり、カメラやライトの場合は反転してしまって、上手くいかなかったので、xformMat関数を使い変換を変換した。オブジェクトなどでも、レンダリング時などエラーが起きそうですし。変換を変換。変換を・・・。

tiraokaさんのブログでカメラの変換トランスフォームを使って、素材の座標を変換する方法を説明してますが、考え方は同じになりますか。勉強になります。

ColorMatrix

2014年10月27日月曜日

Maxscript その16「if式 条件分岐」

if式 <if_expr> 「プログラム フロー制御>if式」

これもプログラムでは代表的な処理のようだ。条件によって処理を変える事が出来る。条件分岐。概念などは、これまた色んなサイトで紹介されているので「if 文」などで検索。

構文

if <条件式> then <式>
if <条件式> then <式> else <式>
if <条件式> then <式 > else if <式> then <式>  else if  <・・・・許す限り続く>

もしくは

if <条件式> do <式> ※doの時はelseを使えないらしい

thenとdoの使い分けについて、リファレンスでは「doの場合elseがないと認識するので即座に式が実行される。」とある。処理が若干早くなるんすかね?どれくらい早いのかは不明・・・。

(※以下非常に申し訳ないのですが、チェックをしっかり行っていないため記事の精度が低いかもしれません。特にコードのスペルミス、構文ミスがある可能性が高いです。)
-----------
if式について

if の後の条件式部分は最終的な値がboolean値となれば、どのような式であっても問題ないようだ。もっとも最小構成のif式は以下のようになると思う。

if <boolean値> then <式>
if <boolean値> then <式> else <式>
if <boolean値> then <式> else if <式>

if式である意味はないけど

if true then print "yamagishi" -- 実行される
if false then print "yamagishi" --実行されない
if false then print "yamagishi" else print "false" -- elseの式が実行される

なので、次のような記述が可能のようだ。

--オムニライトのオン/オフによって処理を変える
if $omni001.enabled then print "OmniLightはon" else print "OmniLightはoff"

条件式部分を
$omni001.enabled == trueと記述しなくてもよいらしい。

各式部分にブロック式を使って見やすく書くと以下の感じ。
if ($omni001.enabled) then (print "OmniLightはon") else (print "OmniLightはoff")

もしくは

if ($omni001.enabled) then (
    print "OmniLightはon"
)
else (
    print "OmniLightはoff"
)

複数の式をまとめて処理したい時もブロック式を用いて・・・

obj = $Sphere001
r = 10

if classof obj == Sphere then (
    format "[original radius]\nname:% radius:%\n" obj.name obj.radius
    obj.radius = r
    format "[new radius]\nname:% radius:%\n" obj.name obj.radius
)
else (
    messagebox "オブジェクトが見つからないか球体ではない"
)

この辺りから急にスクリプトっぽくなってきて目がチカチカしてくる・・・。
-----------
例:その1 基本

球体オブジェクトを1つ作って選択して実行。半径がxより大きかったら半径をxにする

obj = $
x = 10

if  obj.radius > x then obj.radius = x

--
if式の使い方は様々だが、代表的な使い方の1にエラーを回避するというのがあると思う。
上のスクリプトは「選択しているオブジェクトが1つで球体」でないとエラーとなってしまう。エラーが出る際は「どの部分がどのように問題なのか?」をみる。

スクリプトの処理の順番を考える。

obj = $で選択しているオブジェクトし、変数xを初期化する。
次にif式が実行される。まず、条件式部分の評価がされるようだが・・・

obj.radius > x

この際、選択しているオブジェクトが「立方体」だった場合、立方体には「radius」というプロパティがないためエラーとなる。

複数選択している場合も同じで、複数選択の場合は$の値は「$Selection(Selection値)」となる。selection値も「radius」というプロパティを持っていないため、やはり同じ部分でエラーとなってしまう。

(ただし、選択しているオブジェクトが全て「radius」というパラメーターを持っていれば「selection.radius = 10」のような処理は出来る)

オブジェクトを何も選択してない場合も、objの中身が「undefined」となるためやはりエラー

そこで、処理をする前に条件を付ける事で条件が一致したときのみ処理が実行されるように(エラー回避)してあげる。
-----------
例:その2 基本

オブジェクトを1つ作って選択し、球体かボックスかで処理を変えるスクリプト

obj = $
x = 10

if classof obj == Sphere then (
    obj.radius = x
)
else if classof obj == Box then (
    obj.width = x
    obj.height = x
)
else messagebox "test"

などすれば各「classof == <クラス名>」の結果が一致した時にしか処理がされないのでエラーが出ないようだ。

$の中身は3通りだと思う。
・選択が1つ
・選択が複数
・何も選択されていない

複数選択している場合の$は$selection。

Classof $selection

はObjectSet値。

選択されていない場合は「obj = undefined」となるため
Classof undefiend
はundefined値。

1つだけ選択は各オブジェクトとなるので上の書き方で問題ないらしい。
-----------
tips:if式を使う際も変数のスコープに気を付ける
(
    obj = $omni001
    s = undefiend

    if obj.enabled then (
        print "オムニライトはオン"
        s = true
    )
    else(
        print "オムニライトはオフ"
        s = false
    )

    print s
)

ifでブロック式を用いた場合も例外なく変数のスコープが存在するので、条件で変数に値を代入し、その後もその変数を使いたい場合は、変数の初期化を適切な場所で行ってあげる必要がある。

この例の場合、s = undefined などの一文がないと、各ブロックの処理が終わった後にローカル変数であるsは破棄されprint sの結果がundefinedとなってしまう。
-----------
例:if式の多重構造

if式内でif式を使う事で色んな条件分岐が可能となるようだ。この辺りの考え方等はとにかく沢山書いて反復あるのみ。

--選択しているオブジェクトを取得
obj = $
x = 10

--選択している数が1つだったらブロック内の式を実行
if  selection.count == 1 then (
  --選択しているオブジェクトが球体だったらブロック内の式を実行
    if classof obj == Sphere then (
        --選択している球体の半径がxより大きかったらブロック内の式を実行
        if obj.radius > x then (
      --球体の半径をrに設定
            obj.radius = x
        )
    )
   --選択しているオブジェクトが球体以外でボックスだったら
   else if classof obj == Box then (
        --ボックスの幅か高さがxより小さかったら
        if obj.width > x or obj.height > x then (
            obj.width = x
            obj.height = x
        )
    )
    --それら以外
    else messagebox "test"
)

このスクリプトの場合は、最初のifで
selection.count == 1 としているので、選択しているオブジェクトの数が1つでなければ処理が実行されないスクリプトとなる。似たような処理として

obj = $Sphere001
if obj != undefined then (
)

というのもある。これはオブジェクトが取得できた場合のみ処理がされる仕組みとなるようだ。
obj != undefined ・・・ オブジェクトが未定義ではない=オブジェクトが存在する場合Trueになりthen以降が実行されるようだ。
----------------
tips:「短絡論理式?」
以下のスクリプトを見てみる

--選択しているオブジェクトを取得
obj = $
x = 10

--選択している数が1つだったらブロック内の式を実行
if  selection.count == 1 then (
  --選択しているオブジェクトが球体だったらブロック内の式を実行
    if classof obj == Sphere then (
        --選択している球体の半径がrより大きかったらブロック内の式を実行
        if obj.radius > x then (
           --処理内容
      --球体の半径をrに設定
            obj.radius = x
        )
    )
)

このスクリプトは「選択しているオブジェクトが1つ」で「球体」で「xより半径が大きいとき」に「球体の半径をxにする」というスクリプトになる。エラーが起きないように段階的に判定を進めていってるのだが・・・。

obj = $
x = 10

if (selection.count == 1) and (classof obj == Sphere) and (obj.radius > x) then obj.radius = x

としても問題なく実行される。また、余分な処理がされないという事もあるらしい。

【短絡論理式】
以下の場合は2番目以降の評価がされないらしい。

・最初のand式の左辺がfalseの場合。
・最初のor式の左辺がtrueの場合。

「false and <値>」「ture or <値>」は何が来ても結果は同じなので後の式の評価をしない(処理しない)という事らしい。賢いっすね。

(selection.count == 1) and (classof obj == Sphere) and (obj.radius > x)

上の論理式は全てandなので、左から順に評価をしていき、1つでもfalseとなれば以降処理はされないっぽい。省エネ設計。
-----------
tips:if式の戻り値
「Maxscriptでは全ての命令は式である」。ifも例外なくmaxscriptではif式と呼ばれ、式としての戻り値があるようだ。なので以下のように記述する事も出来るようだ。

s = if $omni001.enabled then "オムにライトはオン" else "オムニライトはオフ"
print s

この場合のif式の戻り値はthen、else各式の戻り値となる。

s = if $omni001.enabled then true else false
print s

オムニライトがオンだとtrue、オフだとfalseが返される。
また以下の場合は、else式がないため$omni001.enabledがfalseの場合、戻り値はundefinedとなるようだ。

s = if $omni001.enabled then "yamagishi"

ブロック式の場合はブロック式の戻り値がif式の戻り値となるようだ。

obj = $omni001
s = if obj.enabled then (
    print "オムニライトはオン"
    true
)
else(
    else "オムニライトはオフ"
    false
)

ただし、このような使い方はmaxscriptの独自性が際立つ気もするので・・・。

obj = $omni001
s = undefiend

if obj.enabled then (
    print "オムニライトはオン"
    s = true
)
else(
    print "オムニライトはオフ"
    s = false
)

とかの方が見やすいですかね?ブロック内の行数が長い時は下まで見に行かないと戻り値が不明だったりする時もありますし・・・。これはあくまでも個人的な考えですが。

-----------
tips:if式等の書き方の例
これは初心者用のアドバイス。先に仕組みを記述し、後に処理部分を記述するような感じで書いていくとスムースにコードが書ける。また、優先順位が高い順にブロックを構成していく。
また、print等を使って問題点がないかの動作チェックも出来るだけ先にしてしまう。後からだと色んな問題が多発的に出た場合、原因の特定に労力を使う事になってしまうので。

obj = $
x = 10

if  selection.count == 1 then (
    if classof obj == Sphere then (
        if obj.radius > x then (
            obj.radius = x
        )
    )
   else if classof obj == Box then (
        if obj.width > x or obj.height > x or obj.length  > x then (
            obj.width = x
            obj.height = x
            obj.length = x
        )
    )
    else messagebox "test"
)

の場合。
---------------
【ステップ1】おっきな仕組み
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"
)
---------------
【ステップ2】細部、レベル1。多分自動でなるとは思うけどレベル毎にインデント(字下げ=段落)を揃える。
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
          print "球体"
    )
   else if classof obj == Box then (
          print "立方体"
    )
    else messagebox "test"
)
---------------
【ステップ3】細部、レベル2
if  selection.count == 1 then (
    print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
        print "球体"
        if obj.radius > x then (
             format "name:% radius:%\n" obj.name obj.radius
           print x
        )
    )
   else if classof obj == Box then (
        print "立方体"
        print x
        if obj.width > x or obj.height > x then (
             format "name:% width:% width:% length:% height:%\n" obj.name obj.width obj.length obje.height
              print x
        )
    )
    else messagebox "test"
)
---------------
【ステップ4】処理を書き足し動作確認。最後に必要のない処理はコメントアウトか削除して完成。変数のスコープの問題があるので、最終的な動作確認は一度maxを再起動し、初回実行で動作確認をする。俗に言うmaxscript検便検査。朝一が大事。(一度位はふざけず真面目に記事が書けないのだろうか・・・)

if  selection.count == 1 then (
    --print "オブジェクトを1つ選択"

    if classof obj == Sphere then (
        --print "球体"
        --print x
        if obj.radius > x then (
             obj.radius = x
             --format "name:% radius:%\n" obj.name obj.radius
        )
    )
   else if classof obj == Box then (
        --print "立方体"
        --print x
        if obj.width > x or obj.height > x then (
             obj.width = x
             obj.height = x
             obj.length = x
             --format "name:% width:% width:% length:% height:%\n" obj.name obj.width obj.length obje.height
        )
    )
    else messagebox "test"
)

※スクリプトなのであまり処理速度を気にしてもあれなのだが、大量のオブジェクトを捌く場合、printやformatなどはリスナーの更新に時間がかかり、処理速度に大幅な違いが出てくるので必要のないアウトプット処理は削除するかコメントアウトする方がいいようだ。
----------------
tips:条件式の関数化

条件が長くなりそうな場合や後で条件を変えたい時などは、関数にして別処理とか。

fn selectIsSphere o = (
    (selection.count == 1) and (classof o == Sphere)
)

obj = $
r = 10

if  (selectIsSphere obj) then ()
-----------
tips:try式 例外処理

try式というのがある。

try()catch()

エラーによって処理を変えたい。エラー回避を考えたりするのが面倒な時などに。
obj = Plate()
try(obj.radius = 20)catch()

obj = Plate()
try(obj.radius = 20)catch(print "Unknown property [radius]")

2014年10月7日火曜日

Maxscript その15 「変数のスコープ グローバル変数とローカル変数」

「リファレンス>変数>変数のスコープ」

maxscriptでは変数の定義が自動でされるので、好きな時に好きな変数を使える。しかし、好き勝手に変数が使えるかというと、そんな事はなく、多くの言語同様に変数には変数のスコープと呼ばれる変数の有効範囲が存在しているようだ。ここでは変数のスコープについて見ていってみようと思う。

maxscriptの変数のスコープには大きく分けて「グローバル変数」「ローカル変数」の2つの種類があるようだ。

グローバル変数はどこでも呼び出せるが、ローカル変数はある特定の構文内でしか使用する事が出来ないようだ。ブロック式を例に見たとき

(
  a = 10
  b = 30
  c = b - a
)

とした場合、ブロック式内で使用されているa、b、cはローカル変数となり、ブロック式内でしか有効ではない。

x = 20
y = 5.0
z = x/y
(
  a = z
  b = 30
  c = b - a
)
print c

とした場合、ブロック式の外で使われる変数xyz はグローバル変数として定義されるのでブロック式内でも使用出来るようだ。ブロック式内で定義された変数cをブロックの外で呼び出すと、変数cはブロック内でしか有効ではないローカル変数であるため、print cの実行結果は

undefined

となる。(後ほど触れるが、このスクリプトは2回目以降実行した際のprint cの実行結果はundefinedとはならない)

グローバル変数は起動しているmaxが終了するまで値を保持するが、ローカル変数は有効な範囲内でのみ値を保持するようだ。なので、一度グローバル変数として定義されれば、その変数は起動しているmax内に限り、別のスクリプトでも使用する事が出来るようだ。(リスナーで呼び出す事も可能)

一方、ローカル変数の有効範囲は、関数、ループ式、ブロック式の開始から終了までのようだ。

また、ローカル変数には段階的に有効範囲があるようだ。

とした場合、レベル1のブロックで宣言されているローカル変数abcはレベル1のブロック内である、レベル2の中でも有効だが、レベル2で宣言されたローカル変数uvwはレベル2のブロック内でしか有効ではないのでレベル2以外で変数uvwを呼び出すと「undefined」となるようだ。
レベル毎インデントを揃える事は、このような事を明確化するのにもとても役に立つのが分かる。
---------------------------------------------------
maxscriptにおける変数の初期化について


変数のスコープは変数を初めて使用する際に定義される。値を代入しない際、値は「undefined値」となる。
変数の型は変数に値が代入される際に定義される

scriptリファレンスには次のようにある。

暗黙的な変数宣言
変数を明示的に宣言せず、その変数名が上のレベルのスコープに存在しない場合は、その変数を初めて使用するとき MAXScript によって変数が作成され、特別な値 undefined を保持するために変数が初期化されます。変数を使用する前に、変数を明示的に宣言したり値を初期化する必要はありません。前のステートメントで明示的に宣言されていない変数は、暗示宣言変数と呼ばれます。暗示的に宣言された変数のスコープは、変数が最初に使用されたときの MAXScript のスコープ コンテキストになります。

この「暗示的な変数宣言」の仕様は便利な一方で「変数のスコープによるエラー」やバグを起こす理由の1つでもあるように感じる。(pythonなどでは未定義の変数を呼び出すとエラーとなるため、この問題が避けられるがmaxscriptではエラーとはならないため「暗示的な変数宣言」が問題となる事がある)

明示的な変数の宣言(変数の初期化)について
構文 ( local | global ) <decl> { , <decl> }

リファレンスの見方について(これが本当に分かりづらい・・・)
| ・・・orを意味する。どっちからしい。
{}内 ・・・ 必要がある際に記述する事が出来る。実際記述する際に{}は書かない。
<> ・・・ 式や値など様々なものを意味する。
ここで、<decl> は次のように定義されます。
<ver_name> { = <expr>} <ver_name>は変数名、代入式でも可。

maxscriptの変数には「スコープ(有効範囲)」と「型(タイプ)」という2つの属性があるらしい。基本的にどちらも自動的(暗黙的)に定義してくれるようなので、あえて定義する必要は基本的にはないようだが、変数を明示的に定義するための「global」「local」という命令が準備されているようだ。

エディター(もしくはリスナー)で
x = 20

と実行した場合、変数xの属性は「global変数」に定義され、型は「integer」となるようだ。これを明示的に行う場合は

global x = 20

とするようだ。また、

global x

とした場合の変数xの属性は「global変数」となり、型は「undefined」となるようだ。「未定義が定義される」非常にややこしい・・・。
,(コロン)を用いることで複数まとめて宣言が出来るようだ

global x = 10, y = 5
もしくは
global x,y
x = 10
y = 5

ネットなどで落としたスクリプトの出だしが、文字の羅列だけで始まっていたりすることがある。あれは「暗黙的な宣言」を用いて「global」を省略し変数をglobal変数として定義しているようである。
<例>
renderlist
materiallist
objectlist

これは
global renderlist
global materiallist
global objectlist

と同義のようである。全てを明示的に記述すると

global renderlist = undefined
global materiallist = undefined
global objectlist = undefined

となる。

ローカル変数の宣言はlocalを用いる。例えばブロック式内で

(x1 = 10)

とした場合の変数x1はローカル変数となる。これを明示的に行う場合は

(local x1 = 10)

とするようだ。
グローバルな領域でローカル変数を定義しようと

local x = 10

などとすると、おそらくエラーになると思う。あるスクリプト内だけで使われるローカル変数を定義したい場合は
(
   x = 10
   y =  20
   z = x + y
   print z
)
など、スクリプト全体をブロックで囲んであげればよいようだ。

ローカルな領域で
(
    global x = 10
)
と定義した場合、変数xは以降グローバル変数となるようだ。

明示的な変数宣言の必要性について
maxscriptでは変数の定義、初期化が自動で行われるため、必ずしも明示的な変数宣言を行う必要はないように思う。一方で、「暗黙的な変数宣言」の仕様が理解できていないと、バグの原因になったりするなどスクリプトが正しく動作しない理由となってしまったりするようにも思う。明示的な変数宣言のメリットをいくつか考えてみたいと思う。
------------------------------------
1.最初に明示的に変数の宣言を行う事でコードに使用している変数名やスコープが分かりやすくなる。特に長いコードを書く際や他の人が見た際などに有効。readme的な役割にもなる。コメントアウトと組み合わせるとさらに有効。

global x,y,z --変数x,y,zをグローバル変数として定義。オブジェクトの位置情報
x = 10
y = 5
z = x + y
(
    local u,v,w --ブロック内で使用する変数u,v,wをローカル変数として定義
    u = x
    v = y
    w = u * 0.5 + v * 0.5
)
といったように、そのスクリプト内で扱われている変数名と属性が分かりやすくなる。
------------------------------------
2.「暗示的な変数宣言」によるバグの回避
maxscriptでよくある「初回実行時のみエラーとなるスクリプト」もしくは「maxを再起動したらスクリプトが正しく動かなくなった」等のバグの回避になる。

(
    x1 = 20
)
print x1

と初回実行した場合、print x1の実行結果は「undefined」となる。この場合の変数x1がどのように暗示的に定義されているか考えてみると・・・・まず、ブロック式が実行され、ブロック式内で初めて使用されるx1はそのブロック内で有効な「ローカル変数」となる。ブロック式終了時に変数x1は破棄される。
次に、ブロックの外でprint x1とし変数x1を呼び出す。変数x1は破棄されているので、ここで初めて使われる事として、再び変数の定義がされるがx1を呼び出しているのがグローバルな領域なので、変数x1は「global変数」「undefined値」として初期化されるようだ(自信はないが、振る舞い的にそうであると想像する)。

同じスクリプトを再び実行する。今度はx1は既に「グローバル変数」と定義されているので、20という値が代入され、ブロックが終了してもその値が保持される。結果、print x1 とした場合、リスナーには「undefined」ではなく「20」と表示され「初回の実行結果と異なる」という事が起きる。

このように動的な変数のスコープの変化を望まない場合、変数の宣言を明示的に行ってあげればよいようだ。

グローバル変数として使いたければ
global x1
(
    x1 = 20
)
print x1

とすれば変数x1ははじめにglobal変数と定義されるので、print x1の初回時の実行結果も「undefined」とはならずに「20」となる。もしくは

x1
(
    x1 = 20
)
print x1

としても同義。

x1を常にlocal変数として扱いたい場合は
(
    local x1 = 20
)
print x1
とすれば、変数x1は「ローカル変数」として振る舞うため、print x1の実行結果は常に「undefined」となる。ただし、変数の定義を明示的に行ったからと言って、「暗黙的な定義」が行われなくなる訳ではないようなので、この書き方の場合、最終的にはprint x1の部分で変数x1の属性は「グローバル変数」に定義されてスクリプトが終了するようだ。

なので、もし、常にx1をローカル変数として使いたい場合は
(
    local x1 = 20
    print x1
)
とprintもブロックの中に入れる必要があるようだ。明示的な宣言を行わない場合も「暗示的な定義」がグローバルな領域でされないように

(
    x1 = 20
    print x1
)

など、処理がされる場所に気を付けてみたり。この辺り、スクリプトの実行結果は同じもののスクリプトの内部処理が異なってくるので難しい部分でもある。

global x1
(
    local x1 = 20
)
print x1
等とすると、変数の属性を途中で変更する事も出来るようだが、変数の属性を途中で変えるような処理は色々と問題を起こす気がするのであまりお勧めはしない。(また、この場合だと最終的に変数x1はグローバル変数となると思う)

もし、既に定義して使われたglobal変数の値を初期化したい場合は

x1 = undefined

等、当たり障りのない値を代入してやれば初期化する事が出来る(と思う。ただ、本当の意味でglobal変数の初期化を行いたいのであれば、一度maxを終了する必要があると思われる。)

リスナーで変数を使う場合も注意したい。スクリプトが正しく動かず、変数の中身がどうなっているかリスナーで確認しようと
x1
としてエンターして「undefined」と表示され「やっぱり未定義だよなー。」となったとする。この場合、この瞬間に変数x1は「グローバル変数」として定義され、スクリプト内でx1を明示的に宣言していない場合、maxが終了されるまでその属性と値を持ち続ける事になる。もし、スクリプトが正しく動かない理由が変数のスコープによるものだった場合はこのような行為も問題となったりする事もある。
------------------------------------
3.特定の構文内では「暗黙的な変数宣言」が行われない場合がある?
maxscriptは仕様が厳格であるとは言いずらい印象がある。

<例:rollout構文>

rollout r_test "test"(
    w = 100
    button b01 "test" width:w
)

とした場合、ロールアウト構文内で定義される変数wは「暗黙的な変数宣言」が行われないのかエラーとなってしまう。(3dsmax2014現在、※シンタックスエラーになる。ロールアウト構文内では認められない式の書き方のようだ。)

rollout r_test "test"(
    local w = 100
    button b01 "test" width:w
)

と、変数の宣言、初期化をしてあげるとエラーは出さなくなる。(構文エラーにならない)

シンタックスエラー(Syntax error) = 構文エラー = プログラムとして文法が間違っている
 詳細は不明だが、maxscriptでは「評価」を行うと、スクリプトが実行される前に文法に問題ないかの構文チェックを行うようだ。問題がなければコンパイルが行われ処理が開始される。文法に問題があった場合Sytax errorを返し、スクリプトが実行される前に処理が中断されるようだ。文法の間違いなのでコードを目視する事で問題点を見つける事が出来るエラーとなる。
 maxscriptのエラーは、構文チェックのエラー、コンパイル時のエラー、実行時のエラーと段階的なエラーと種類があるようだ。
------------------------------------
と、色々あるようなので、変数の明示的な宣言は面倒でなければ出来るだけ行った方がよいと言えるのではないかと思ったりもする。

global変数とlocal変数の使い分けについて
global変数である必要がないものは出来るだけlocal変数を用い、global変数は出来るだけ使わないように努める。極端な例だとスクリプト全体をブロックで囲んでしまう等。
(
    a = 10
    b = 20
    c = a + b
    print c
)

理由としてグローバル変数は起動しているmaxが終了するまで値を保持する」があげられる。ここでいう「値を保持する」は「物理的にメモリを使用する」という事だと思われる。global変数は起動しているmax内どこでも使えて便利な一方で「maxが終了するまでメモリに情報を保持し続ける」という事だと思う。一方、local変数は特定のスコープ内(ブロック式内など)のみ有効なので、その処理が終わると値が破棄されるようだ。スクリプトなので、そこまでストイックになる必要もないとも思うが、基本的な考え方として、保持する必要がない値は処理が終わったらどんどん破棄されるように、出来るだけlocal変数を使用した方が3dsmaxに優しい気がしないでもない。(その仕組みに悩んで、コードを書く時間がかかってしまうと、スクリプト本来の手軽であるメリットがなくなってしまう事もあるので、ほどほどでいいとも思うが・・・)

また、変数名が被らないように気を付ける際も、ローカル変数はその有効範囲内でのみ考えればよいので、ローカル変数は変数名が衝突する問題の解決方法の1つでもあると思う。(ただし、グローバル変数と衝突しないようにする配慮は常に必要)
--------------------
tips:maxscriptのglobal変数には、global変数をシーンファイルに埋め込む「存続グローバル変数」というものもある。(一見便利そうだがグローバル変数がシーンファイルに保存されるため、扱う際は注意が必要になるようだ。特にmaxscriptの変数は値だけではなく、関数を代入したり色んな物を代入出来てしまうようなので・・・。仕様が不明確な内はあまり使わない方がいいかもしれない。)

「リファレンス>変数>永続グローバル変数」

tips:maxscriptのメモリの仕様については
「メモリの割り当てとガベージコレクション」などで触れているようだ。