<< TOP



ICE による AmbientOcclusion
ICE AmbientOcclusion






 プロローグ
パソコンのスペック WindowsXP SP3、 Intel Core2Duo 3G、 RAM; 3G、NVIDIA Quadro FX1700



NodeLocation
 頂点色設定の鍵
     ココでは、ポリゴンの頂点の色を変化させるという目的の為には、
    『 各頂点に 0~1の値を得て、そのデータを使って色を設定させる 』 ってところがポイントです。
    つまり、色々な方法を使って 0~1 の値を得られれば、それに応じて様々な色表現が得られる、ということなんです。

     まず最初に、ポリゴンオブジェクトには 頂点カラーマップのデータだけは付けておきます。
    そして、そのポリゴンの色頂点データを取得する方法として
    NodeLocation を使うのが、 ミソ です・・・。
    Softimage ユーザ ガイド から その説明を見ると こう書いてあります。

      NodeLocation
        ポリゴン メッシュ上のポリゴン ノード (または「ポリノード」や「テクスチャ サンプル ポイント」とも呼ばれる) のロケーション。
        これは、頂点カラー プロパティの値を取得するときに役立ちます。


     ということで、まずはそのあたりまでを作ってみましょう。
    凹凸のあるポリゴンを作成します。(机です)



    取得 > プロパティー > 頂点カラーマップ で 頂点色の設定(UV_Cluster_AUTOの Vertex_Color) を付けておきます。
     


     ポリゴンに ICE_Tree ノード を設定し、SetData で self.color と self.cls.UV_Cluster_AUTO.Vertex_Color.Colors 項目を設定します。
    次に、GetData で self.NodeLocation を設定して 己の頂点カラー プロパティの値を取得して、
    GetDataで color を記入して、上で設定した色をここで拾い、
    それを SetData の self.cls.UV_Cluster_AUTO.Vertex_Color.Colors 項目 に接続します。
     これでもう、SetData ノードの self.color 項目にて色を変化させると、その色が頂点色として反映されます。

      更に、黒>白 のGradient ノードを取り出し、self.color につなぎます。すると全体が黒になります。
    それは、今、各頂点に入っている位置データが  で、その色が、 だからです。

    各頂点に 0~1 になるような値を設定することが出来れば、各頂点の色が 黒から白の間の色 になるはずです。

     一旦ここまでをシーン保存して、次に Raycast の話に移ります。



Raycast
  基本設定
     まずは Raycast ノードの使い方を身に付けちゃいましょう。



     ポリゴンの球を取り出し、0.5くらいのスケールにしてフレーズさせ、スケール値は 1 であることを確認します。
    実は大きさは関係ないのですが、スケール値は厄介なことにならないように  にしておいてください。
    これを PolyLight と名前を変更しておいてここからレイが発せされる 仮のライト として扱います。
     Raycast ノードを取り出し、Geometry として Get Desk1 を接続します。
    Position として self.PointPosition
    Direction として PolyLightの位置データから 己のPointPosition を Subtract(減算) した値を接続します。
    その結果の Location を SetDataself.tmp と仮のデータに差し込んで ICETree に接続します。
    これが最も基本的な接続例です。

    Raycast の 左側 Position と Direction を視覚化して、何やってるのか見て理解を深めましょう。



    self.PointPositionベクトルを表示させると そのオブジェクトの中心からの直線上のポリゴン頂点からベクトルが発せられます。



    今度は、PolyLight のグローバル位置を表示させると、ワールドの中心点から 今ある位置まで、一本のベクトルが見れます。



     すると、Direction に Subtract で接続しているベクトルとは、
    机の頂点の位置から発して ポリゴンライト位置の方向に到達している っていうベクトルが得られます。
    いつもこうして検証して見れるのがICEの有効利用ポイントですね。

     さて、この真ん中の GetPointPosition なのですが、まさにポリゴンの頂点と同じ位置なので、
    影の影響を出すには、頂点位置から法線方向に少し浮かした位置から発した方が良い結果を得られます。
    それを以下のように設定して実現します。



     Get PointNormal を取り出して法線方向を導き出し、そこに Multiply by Scalor 0.01 をかけて、
    その値を PoinPosition に Add(加算) してあげれば ほんの少し浮かすことが出来ます。
    これで己のポリゴン表面にレイが当たりやすくなり、影が出やすいようになります。



     今度は Raycastノードの右側を見ます。
    Location と Hit しかありません。
    前述では、Location を 仮のtmp値 に接続したのですが、
    これでは個々のレイが当たった位置情報を表示してしまいます。(0,0.0 とは当たらなかったという意味)
    そうではなくて、Hit の方を 仮のtmp値 につなげてあげると、レイが何かに当たると をかえしてきます。
    つまり、この  になった部分が影になれば、陰影が作成できます。



    それを実現させるため、If ノード を取り出し、
    もし当たったら 0 を そうじゃなたら 1 をという Result(結果) 最初に作った Gradiant に接続すれば、
    おう!! 頂点色 が PolygonLight の位置によって変化するが実現できました。



  値の改善
     仕組みはもう大体見えて来ました。後はより良い結果を得る工夫次第です。
    ヒントは、たくさんの Raycast事例 がありますので、そこから面白そうな仕組みを取り入れてみましょう。



     レイの方向と法線の角度差から、陰影をもたらすことを考えてみます。
    Subtract(減算) から出ている値は Raycast の Direction に接続しているベクトルでした。
    その値と 法線データ Get PointNormal を GetAngleBetween につないで角度を求め、
    Rescale ノードを使って 0 ~ 1 の間の値に範囲を変換しています。
    さらに FCurve を使って値変化に強弱を増し、If につながる  を求める部分につなぎ変えます。
    この結果に違いは以下図 ↓ のようになります。



     結果として、平行投影っぽい色合いから、ポイントライトっぽい距離による減衰効果が得られています。



      更に、近隣(4頂点)の平均値を取って来るという方法があったので、それはココではこのようにつなげます。
     If から出る結果を一旦 tmp という値に集め、Get Neighbors から 各頂点の近隣の値を tmp から取得し、
    Get Array Average で平均値を取って Gradient につなげると、上図の絵上と絵下の違いになりました。

     更に、レイ角度にランダムのノードを差し入れて、それを何回がリピートした平均値を作り出す、ってのも試したはみたものの、
    あまり綺麗な結果を得られなかったので、それは見送りました。



  コンパウンド化
     では、一旦ここまでのものをコンパウンド化して、簡単に使えるようにしておきます。



     左に外部入力出来るポイントを作成しておきます。
    頂点色を設定する ポリゴンのジオメトリー と ライトの位置 のポイントを作成します。
    一番下の Gradient はReferanceとしてつないでおいて、
    コンパウンドのノードをダブルクリックした時の表示画面から頂点色が変更できるようにしておきます。
    右側はExecuteを1つにまとめておくと見た目良くつなげられるようになります。



     コンパウンドのプロパティーに、 名前 と カテゴリ や ノード色バージョン を設定してOKを押します。
    コンパウンドの書き出し をすれば 記入したカテゴリに書き出されます。


  インフィニット・ライトを使う
     光の方向 と  を、通常良く使っているインフィニット(平行投影)のライトの設定画面 から変更できるようする、
    という風に改造してみます。



     普段、何気なく使っているライトは、回転値が 0,0,0 の時、-Z方向に向いている ↑、ということを再認識すれば、
    ライトの回転 に従って ベクトルも回転する ようにするには、Rotate Vector を使って、
    3DVector に Z値-1 のベクトルを用意して、ライトの回転値をRotationにつなげ れば、
    同じ方向を追従する、と解ります。 便利!!
    Raycast に使う時は ライトの方向に向くベクトル が欲しいので、3DVector の Z値は逆方向の  になります。



     このようなつながりになりました。
    色は、各 R・G・B色 に分けてから Multiply で掛け合わせます。
    これで、ライトの回転で明るさの方向が変わり、ライトの色で頂点色も変化します。

    コンパウンドにまとめると、こんな風になりました。





Ambient Occlusion
 3DVectorのArray対応
     さて、ここからは、少し今までの方法と違う事を考えます。

    前述の通り、少しランダムな値を入れたベクトルを作り出して、それをリピート処理して平均値を求める方法では綺麗な結果を得られませんでした。
    そこで、複数のベクトルをArray(配列)処理できる仕組みに変えてみたいと思います。
    Gradientにつながる所は大幅に変更が必要ですが、その仕組みを一旦理解できたら、それを利用すれば良いだけです。

     まずは、複数ベクトルの対応からで、つないだ結果を先に見せます。
    一番左下は コンパウンドになっていて Fill Interpolated Array って書かれています。
    ここに今、 という カウント数(レイ数になる) を入れていると、次のノードが Pop From Attay なので、つArray(配列)が削除されて となり、
    その数のベクトルが各頂点位置から出ている、っていう図になっています。(ここのカウント数は最終的には10とか16とかなります)
    これを、もう少し詳しく説明します。




  Fill Interpolated Array
     最初のコンパウンドが何やってるの?ですね。



     仮に数字 4 が設定されていて 入って来たとします。
    黄色の接続点、上(ValueA) が Y=390 、下(ValueB) が Y=30、つまり、360度で1回転に30度足した数字にあえて入っています。
    つまり、目的としては Y軸360度を、入って来た数字で割った回転値分のArray(配列)を作り出している代物です。
      の次の Maxmum には 3 が入っていて、3より下の数値にならないようになってます。
     Build Array from Constant には Size も Value にも 最大値が入るので 4 で、4つの 4のArray(配列)を作ります。
     Get Array Sub Indices は Array(配列)の順番数を列に入れるので、0,1,2,3 となります。
     下に行って、Subtract(減算) は  が入っていて 最大値 から 1 減った数 3 になります。
     上も下も Integer to Scalar は スカラー数字に変換し、そのままの数字が移行して、
     Divide by Scalar は 0,1,2,3 を  で 割った数のArray(配列)、0、0.333、0.667、1 となります。
     ・・・つまり、どんな数字が入って来ても、その数字から  引いた数で割った 0 ~ 1 の数値のArray(配列)を作り出します。
     最後の Linear Interpolate は その割った数に相当する角度を出すので、 Y=30、150、270、390 という Array(配列)を作り出します。

      この部分のコンパウンドが作製できたら、 Fill Interpolated Array という名前で コンパウンドを書き出し ておいてください。

      で、次も説明してしまうと、30 と 390 って同じベクトルのことなので、Pop From Attay で一番下の列が削除され、
     Y= 150、270、390 という Array(配列)を出します。



     数字を増やしていっても、きれいに360度を入って来た数値から1引いた数で割った数分のベクトルを作り出す、となっています。



  法線とインフィニットライト情報の追加
     次は、Rotate Vector ノードを使い、各頂点の法線情報で 求めたArray(配列)ベクトルを配置します。



     最後、インフィニットのライトですが、あ、と気が付きますね、1つ前の RotateVector が Z=1でした。



     さーこれで、と思いきゃ、今度は HIT 側の工夫が必要です。


  Array(配列)を0~1値にする
     Array(配列)を使った数値(マルチのデータ)が入って来るので、このようにしないとつながりません。
    意味が解ってくれれば、後は使うだけなので・・・・。



    では、Raycast の HIT 出力から出る値の意味を説明します。

     GetArraySize は必ず 0以上 になるはずなので、=0 に絶対ならないので、
     Rescale の前の If はいつも 下の if False の値が使われます。
     GetArraySum はHitした総数を計算しているので、直前の If が Hit したら 1 を足していって 0~2の値を作り出します。
     そのArrayを Dived by scalar で いつも1少ない数値(ここでは 2 )で割るので、
     かならず 0 ~ 1 の値を作り出します。
     Gradient につながる Rescale は グラデーションの0が黒なので、
     逆になるようにしていて、Clamp にチェックを入れて負数にならないようにしています。

     ここも前のところと同じで、ある値に1少ない数値で割ると 0 ~ 1 の範囲の値を得られる
    っていうのを使うのがミソのようです。



      前のポリコンのライトの時の設定と同じく、GetNeighbors を使うため、
    一旦 tmp データとして SetData して Get Average で平均した値を Gradient につなぎます。



  コンパウンド化
     そしたら、ここまでのノードのつながりを、たった 1つのコンパウンドに まとめてしまいます。
    Rayの数、表面からの距離、減衰を考慮する距離、
    インフィニットのライトを使用するかどうか(色も含む)グラデーションの色、ライトの選択
    は コンパウンドをダブルクリックして表示される設定画面で見えるように設定します。



     その中身はこんな感じになっています。
    インフィニットのライトを使うかどうかは If  によって選べるようになっています。
    Gradient や GetLight には Reference で接続して変更できるようにしておきます。



     Ambient Occlusion with Light という名前で カテコリ Color として書き出せば、皆で使えるコンパウンドになります。



     改良は、まだまだ出来そう・・・なんですね。





  グループ対応のコンパウンド化
     さて、背景とかに使用するとしたら、複数オブジェクト対応も必要なんだろうなと考えました。
    まずは、複数のポリゴンオブジェクトをマージした場合は簡単ですよね。



     グループ対応のコンパウンドの作成に伴い、もう少しコンパウンドに機能を追加しました。
    Geometryとして左側の紫色の接続点がつなぐと増えるタイプにしました。
    Geometry を複数接続する時は、Group Geometry ノードを使用します。
    グループノードを追加したり複数接続したりすることができるようにです。



     それは、コンパウンドで左側の外部入力の名前から、
    プロパティー にて [マルチ-新規のポートを既存のノードに接続] とすると
    接続すると、どんどん増えるタイプになりまうす。Geometry 接続点がどんどん増やすことが出来ます↓。



     明るい部分と暗い部分を Rescale ノードの Targetのスタート値 と エンド値で変更できるので
    それがコンパウンドから操作出来るようにしました。



     Multiply Color by Scalar を self.Color へつながる途中に挟み、右側と接続しておけば、コンパウンドから操作できるようになります。



  グループ対応のスクリプト
     最後は、1つのコンパウンドにまとまったら、
    スクリプトを使って、Group登録されているオブジェクトに
    ICETreeノードの設定、コンパウンドノードの接続まで、一気にしてしまいます。

    Groupに入れた複数のポリゴンオブジェクトに、まずは頂点カラーの設定だけはしておきます。
    そして、Group > メンバの選択 をしておいて、以下のスクリプトを走らせます。

        sub BindAOICE2OBJ( oObj )
          set compound = Dictionary.GetObject( oObj &".ICETree.Ambient_Occlusion_with_Light", false )
          if typename(compound) = "Nothing" then
            set iceOp = ApplyOp("ICETree", oObj, siNode, null, null, 0)
            AddICECompoundNode "Ambient Occlusion with Light", iceOp
            ConnectICENodes iceOp & ".port1", iceOp &".Ambient_Occlusion_with_Light.Execute"
          end if
        end sub

        sub BindGroupToAOICE2OBJ( obj )
          if typename( obj ) <> "Group" then
            logmessage "You must select Group."
          exit sub
          end if

          set oGrp = obj
          for i=0 to oGrp.Members.Count - 1
            set oObj = oGrp.Members.Item( i )
            BindAOICE2OBJ oObj
          next
        end sub

        sub BindSelectionAOICE2OBJ( )
          for i=0 to Selection.Count - 1
            set oObj = Selection.Item( i )
            BindAOICE2OBJ oObj
          next
        end sub

        ' BindGroupToAOICE2OBJ( Selection(0) )
        BindSelectionAOICE2OBJ

            このスクリプトのダウンロード >> ApplyAO.txt (本当の拡張子は vbs で)

     良く使うスクリプトなどは このようにボタン形式にすることもできますね。↓



     すると、全部のオブジェクトに同じICEノードが接続された状態になります。
    このスクリプトも あらかじめ接続するノードが同じである範囲を考慮してスクリプトを改良し、
    一気に設定してしまえば作業がどんどん楽になることでしょう。

     PS, 
       Groupを使った複数オブジェクトの状態での頂点色設定は、1つのオブジェクトにマージしたオブジェクトに設定した場合に比べて
      結果がはっきりしない、というかあまいというか、差がある場合がありました。特に真平らな面など。
      マージしたポリゴンに一旦頂点色を設定して、個別へはGATORなどを使って色を移し変える、
      なんてことも考えられると思います。






     という訳で、次も ICE ですかね。

      乞う、ご期待!!