C4DScriptいくつか

こんにちは.C4Dのスクリプトは毎回データ構造のリファレンスがどこにあるのかググっても見つからないのでリバースしてます.
リファレンスが増えるかもとの目的で作ったものを置いておきます

1.Blender→C4D想定 この前のマテリアル割り当てのFaceSetから割り当てるバージョン

import c4d
from c4d import storage
def main():
    if not op:return
    path = storage.LoadDialog(title = 'File')
    if not path:return
    path = unicode(path, 'UTF-8')
    data_file = open(path, 'r')
    data = data_file.read()
    data_file.close()
    data = data.split('\n')
    for c in range(len(data)/3):
        Mat = doc.SearchMaterial(unicode(data[3*c+1], 'shift-jis'))
        tag = c4d.BaseTag(c4d.Ttexture)
        tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW
        tag[c4d.TEXTURETAG_MATERIAL] = Mat
        name = unicode(data[3*c+2], 'shift-jis')
        t = op.GetFirstTag()
        selection = None
        cname = name.encode('utf-8')
        tnameList = []
        tnameFindexList = []
        while t:
            tname = t.GetName()
            findIndex = tname.rfind(cname)
            if findIndex!=-1:
                tnameList.append(t)
                tnameFindexList.append(findIndex)
            t = t.GetNext()
        if len(tnameFindexList)>0:
            if len(tnameFindexList)>1:
                targetindex = tnameFindexList.index(min(tnameFindexList))
                selection = tnameFindexList[targetindex]
            else:
                selection = tnameFindexList[0]
        tag[c4d.TEXTURETAG_RESTRICTION] = selection.GetName()
        op.InsertTag(tag, t)
if __name__=='__main__':
    main()

2.C4D→Houdini想定 FaceSet用にポリゴン選択範囲をリネームする

import c4d
from c4d import storage
def main():
    if not op:return
    path = storage.LoadDialog(title = 'File')
    if not path:return
    path = unicode(path, 'UTF-8')
    data_file = open(path, 'r')
    data = data_file.read()
    data_file.close()
    data = data.split('\n')
    for c in range(len(data)/3):
        print c
        name = unicode(data[3*c], 'shift-jis')
        newname = unicode(data[3*c+1], 'shift-jis')
        t = op.GetFirstTag()
        selection = None
        cname = name.encode('utf-8')
        tList=[]
        tnameFindexList=[]
        print cname
        while t:
            tname = t.GetName()
            findIndex = tname.rfind(cname)
            if findIndex!=-1:
                tList.append(t)
                tnameFindexList.append(findIndex)
            t = t.GetNext()
        if len(tList)>0:
            if len(tList)>1:
                targetindex = tnameFindexList.index(min(tnameFindexList))
                selection = tList[targetindex]
            else:
                selection = tList[0]
            selection.SetName(newname)
if __name__=='__main__':
    main()

3.C4D→Houdini_RedShift想定 マテリアル情報をCsv形式で出す

import c4d
from c4d import storage
import csv

def main():
    path = storage.SaveDialog(title = 'File')
    if not path:return
    path = unicode(path, 'UTF-8')
    f = open(path + '.csv', 'ab')
    csvWriter = csv.writer(f)
    val = 0
    rowList = []
    mats = doc.GetActiveMaterials()
    for a in xrange(len(mats)):
        rowList.append([])
        matname = mats[a].GetName()
        
        df_tex = mats[a][c4d.MATERIAL_COLOR_SHADER]
        df_tex_path = df_tex[c4d.BITMAPSHADER_FILENAME] if df_tex else "none"
        
        spc_tex = mats[a][c4d.MATERIAL_SPECULAR_SHADER]
        spc_tex_path = spc_tex[c4d.BITMAPSHADER_FILENAME] if spc_tex else "none"
        
        bmp_tex = mats[a][c4d.MATERIAL_BUMP_SHADER]
        bmp_tex_path = bmp_tex[c4d.BITMAPSHADER_FILENAME] if bmp_tex else "none"
        
        nrm_tex = mats[a][c4d.OCT_MATERIAL_NORMAL_LINK]
        nrm_tex_path = nrm_tex[c4d.IMAGETEXTURE_FILE] if nrm_tex else "none"
        
        dsp_tex = mats[a][c4d.MATERIAL_DISPLACEMENT_SHADER]
        dsp_tex_path = dsp_tex[c4d.BITMAPSHADER_FILENAME] if dsp_tex else "none"
        
        opc_tex = mats[a][c4d.MATERIAL_ALPHA_SHADER]
        opc_tex_path = opc_tex[c4d.BITMAPSHADER_FILENAME] if opc_tex else "none"
        
        ObjLink =  mats[a][c4d.ID_MATERIALASSIGNMENTS]
        myCount = ObjLink.GetObjectCount()
        for b in xrange(myCount):
            tag = ObjLink.ObjectFromIndex(doc,b)      #Gets the tag that's on the object
            obj = tag.GetObject().GetName()
            sel = tag[c4d.TEXTURETAG_RESTRICTION]
            rowList[a] = [matname,obj,sel,df_tex_path,spc_tex_path,bmp_tex_path,nrm_tex_path,dsp_tex_path,opc_tex_path]
    csvWriter.writerows(rowList)
    f.close()
# Execute main()
if __name__=='__main__':
    main()

最近はHoudiniメインなので、次の動画が出せたら何か役立ちそうな構成とか紹介しようと思います
後は虎の子の2dレイヤー後合成スクリプト(AE)も...

pmxEditerのpluginについて

3DCGソフトにpmxのデータを手動で移行するのがめんどくさくなってきたのでスクリプトを作ろうと思ったのですが、リファレンスが見つからなくて苦労したのでネット上のサンプルを増やすためにここにggるのに数時間使った結果を書いときます.
こちら TransferWeights(PMX) - BowlRoll のソースを参考にさせていただいてます.
では、地味に手作業ではめんどくさい、全材質名とテクスチャパスをテキストファイルに保存するものを作ります.
中身に興味ない人はここから.dllを\_plugin\Userに入れてください.
ExportMaterialName.zip - Google ドライブ

編集(E)>プラグイン(P)>CSScript>C#スクリプト(S) で開くウィンドウから作成・実行します.簡易形式と一般形式がありますが、一般形式で書いたほうが後々楽だと思います.
で、

try {
            // ここへ処理を追加してください.
            

        }

のとこに本体を書きます.
まず基本的にリファレンスは表示(V)の中のやつを見ればよいですが、ファイル書き出し周りの記述が書いてないので既存の配布されているソースコードを参考にします.
さて、まず次のおまじないを書いときます

// ホスト配下
IPEPluginHost host = args.Host;
IPEConnector connect = host.Connector;

			
// PMX関連			
IPXPmx pmx = connect.Pmx.GetCurrentState();     // PMX取得
IList<IPXMaterial> material = pmx.Material;     // material :材質   | リスト

pmxediterで扱える各データへアクセスするためのオブジェクトは元から用意されてないらしく、これを書いて適当に格納しておく必要があります.とりあえずこうしてIPXPmxを取得さえしていれば、リファレンスに書いてあるPmx以下のオブジェクトにアクセスできるようです.
今回は材質リストが欲しいのでpmx.Materialを取得します.頭についてる宣言はIPXMaterialのリストを示しています.
材質リストさえ手に入ればforeachで名前をとってこれば目的は達成できそうです.これをテキストファイルに出したいというときは、StringBuilderを使うことで簡単にできるようです.

string f = pmx.FilePath;
StringBuilder sb = new StringBuilder();
var i = 0;
foreach (IPXMaterial m in material){
  i++;
  string line = string.Format(i.ToString());
  sb.AppendLine(line);
  line = string.Format(m.Name);
  sb.AppendLine(line);
  line = string.Format(f + @"\" + m.Tex);
  sb.AppendLine(line);
}

これは3行を一セットとして、材質番号(ループ内部のi++でカウントしていく,ここではi>0)、材質名(IPXMaterial.Nameから取得)、テクスチャパス(内部で相対パスで指定されていることを前提に、Pmx.FilePathから読み込んでいるpmxファイルの絶対パスをとってきて、そのあとにIPXMaterial.Texから取得した相対パスをくっつける)記入するようにしています.
.AppendLine()は改行して記入してくれるようで楽です.また、@"\"と書くことで\を文字列とできるようです.
保存先をファイルダイアログで指定できるようにします.参考ソースまんまですが

// ファイルダイアログ表示
using (SaveFileDialog dlg = new SaveFileDialog()) {
  dlg.Filter = "txt files (*.txt *.csv)|*.txt;*.csv|All files (*.*)|*.*";
  if (dlg.ShowDialog() == DialogResult.OK) {
    // CSVファイルとして保存
    File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.GetEncoding("shift_jis"));
    }

こんな感じでいけるようです.SaveFileDialogを作って、.ShowDialog() で開いて、OKが押されたらFile.WriteAllText()で保存する、という感じですね.
pmxは基本日本語なので3つ目の引数は大事です.
さて最後にこれをdllにしておくと起動が楽です.名前をつけたいところですが、それは // ここへ処理を追加してください.の上に書いてある

// コンストラクタ
    public CSScriptClass() : base()
    {
        // 起動オプション
        // boot時実行(true/false), プラグインメニューへの登録(true/false), メニュー登録名("")
        m_option = new PEPluginOption(false, true, "CSScript生成プラグイン");
    }

のPEPluginOption()を使うとできるようです.
まとめると以下になります

// 起動オプションのところに

m_option = new PEPluginOption(false, true, "全材質名保存");

// ここへ処理を追加してください.のところに

// ホスト配下
IPEPluginHost host = args.Host;
IPEConnector connect = host.Connector;
		
// PMX関連			
IPXPmx pmx = connect.Pmx.GetCurrentState();     // PMX取得
IList<IPXMaterial> material = pmx.Material;     // material :材質   | リスト

string f = pmx.FilePath;
StringBuilder sb = new StringBuilder();
var i = 0;
foreach (IPXMaterial m in material){
  i++;
  string line = string.Format(i.ToString());
  sb.AppendLine(line);
  line = string.Format(m.Name);
  sb.AppendLine(line);
  line = string.Format(f + @"\" + m.Tex);
  sb.AppendLine(line);
}
// ファイルダイアログ表示
using (SaveFileDialog dlg = new SaveFileDialog()) {
  dlg.Filter = "txt files (*.txt *.csv)|*.txt;*.csv|All files (*.*)|*.*";
  if (dlg.ShowDialog() == DialogResult.OK) {
    // CSVファイルとして保存
    File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.GetEncoding("shift_jis"));
    }
}

言語からじゃなくてソフトからコーディングをやり始めるとこういうときに大変ですね...

よく使うスクリプト内の処理【AfterEffects】

よく使うものその1 備忘録的に

・選択レイヤーのリストを作成
・プロパティリンク複製
・親子関係を探索
・親をヌルとして複製して親子付け
・親付きプリコンポーズ
・プロパティリンク用の重複名称解消

//選択レイヤーのリストを作成
//引数
//activeComp:作業場所コンポ
//戻り値
//selectedLayers:選択したレイヤーが選択順で入った配列
function gSLs(activeComp){
    var Length = activeComp.selectedLayers.length;
    var selectedLayers = new Array(); 
    for(j=0; j< Length; j++){
            selectedLayers[j] = activeComp.selectedLayers[j];
            }
        return selectedLayers;
        }

//プロパティリンク複製(ライト非対応)
//引数
//activeComp:作業場所コンポ
//Root:複製もとのレイヤー カメラは判定
//戻り値
//Child:複製されたレイヤー
function PropertyLinkDup(Root,activeComp){
    var Child = Root.duplicate();
    Child.name = Root.name + "PropertyLink"
    Child.position.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Root.name + "\").transform.position;";
    if(Child.threeDLayer){
        Child.transform.orientation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").transform.orientation;";
        Child.transform.xRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").transform.xRotation;";
        Child.transform.yRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").transform.yRotation;";
        Child.transform.zRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").transform.zRotation;";
        }else{Child.rotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").transform.rotation;";
            }
        if(Child.cameraOption){
            Child.cameraOption.zoom.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Root.name + "\").cameraOption.zoom;";
            }else{
                Child.scale.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Root.name + "\").transform.scale;";
                Child.anchorPoint.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Root.name + "\").transform.anchorPoint;";
                }
            return Child;
            }

//親子関係を探索
//引数
//activeComp:作業場所コンポ
//Root:探索元の子
//戻り値
//Parent:親が若い方から順に入った配列
function SearchParent(Root,activeComp){
        var Parent = new Array();
        Parent[0] = Root.parent;
        var i=0;
        while(Parent[i]){
            i++;
            Parent[i] = Parent[i-1].parent;
            }
        Parent.pop();
        return Parent
        }

//親をヌルとして複製して親子付け
//引数
//activeComp:作業場所コンポ
//Layer:子
//戻り値
//Null:親にリンクしたヌルが若い方から順に入った配列
//SearchParent使用
function ParentConvertToNulls(Layer,activeComp){
    var Parent = SearchParent(Layer,activeComp)
        //親をヌルとして複製
        var Null = new Array();
        var Inpoint = Layer.inPoint
        for(j=0;j<Parent.length; j++){
            Null[j] = activeComp.layers.addNull();
            Null[j].startTime = Inpoint;
            Null[j].name = Parent[j].name + "null";
            Null[j].position.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Parent[j].name + "\").transform.position;";
            Null[j].scale.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Parent[j].name + "\").transform.scale;";
            Null[j].anchorPoint.expression = "comp(\"" + activeComp.name + "\").layer(\"" + Parent[j].name + "\").transform.anchorPoint;";
            if(Parent[j].threeDLayer){
                Null[j].threeDLayer = true;
                Null[j].transform.orientation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Parent[j].name + "\").transform.orientation;";
                Null[j].transform.xRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Parent[j].name + "\").transform.xRotation;";
                Null[j].transform.yRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Parent[j].name + "\").transform.yRotation;";
                Null[j].transform.zRotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Parent[j].name + "\").transform.zRotation;";
                }else{Null[j].rotation.expression = "comp(\"" + activeComp.name + "\").layer(\"" +Parent[j].name + "\").transform.rotation;";
                    }
            }
        //ヌルと親子付け
        Layer.setParentWithJump(Null[0])
        for(j=0;j<Parent.length-1; j++){
            Null[j].setParentWithJump(Null[j+1]);
            }
        return Null
        }   

//親付きプリコンポーズ
//引数
//activeComp:作業場所コンポ
//Layer:プリコン対象の子
//戻り値
//obj オブジェクト
//obj.MakedComp:作られたコンポへのアクセス
//obj.MakedCompLayer作られたコンポへのactiveCompでのレイヤーとしてのアクセス
//ParentConvertToNulls,PropertyLinkDup使用
function PPC(Layer,HeadName,activeComp){
     var DupParents = ParentConvertToNulls(Layer,activeComp);
     //3dの場合を処理
     var activeCam = activeComp.activeCamera;
     if(Layer.threeDLayer){
         var DupCam = PropertyLinkDup(activeCam,activeComp);
         var CamParents =  ParentConvertToNulls(DupCam,activeComp);
         }
     //precomposeメソッド用インデックス生成
     var PPCIndex = new Array(); 
     PPCIndex[0] = Layer.index;
     for(k=0;k<DupParents.length;k++){
         PPCIndex[k+1] = DupParents [k].index;
         }
     if(Layer.threeDLayer){
        var CamAroundIndex = new Array(); 
        CamAroundIndex[0] = DupCam.index;
        for(k=0;k<CamParents.length;k++){
            CamAroundIndex[k+1] = CamParents[k].index;
            }
        PPCIndex = PPCIndex.concat(CamAroundIndex);
        }
     //precompose
     var minIndex = Math.min.apply(null,PPCIndex)
     var makedComp = activeComp.layers.precompose(PPCIndex, HeadName + Layer.name, true);
     var makedCompLayer = activeComp.layer(minIndex);
     //戻り値を整える
     var obj = new Object();
     obj.MakedComp = makedComp;
     obj.MakedCompLayer = makedCompLayer;
     return obj;
     }

// プロパティリンク用の重複名称解消
//引数
//Layers:重複名称をチェックしたいレイヤーの配列
//戻り値
//なし
function AvoidSameName(Layers){
    var storage= {};
    var i,value;
    for (i=0;i<Layers.length;i++){
        value = Layers[i];
        if (!(value in storage)) {
            storage[value]= '';
            }else{
                Layers[i].name = Layers[i].name + "_" + String(i);
                }
            }
        }

で例えばディスプレイメントマップやベクトルブラーを使った歪みのマットを自動でつけるとか結構便利かもしれない

var activeComp= app.project.activeItem;
if(activeComp){
    var Select = gSLs(activeComp);
    if(Select.length>0){
        app.beginUndoGroup("gG_AutoDisplacement");
        //重複名称チェック
        var Check = new Array();
        for (i=0;i<Select.length;i++){
            Check.concat(SearchParent(Select[i],activeComp))
            }
        AvoidSameName(Check);
        for (i=0;i<Select.length;i++){
            //複製してトランスフォームリンクを書いてPPC
            var DupLay = PropertyLinkDup(Select[i],activeComp);
            var Precomp = PPC(DupLay,"Mat_",activeComp);
            var MatComp = Precomp.MakedComp;
            var MatLayer = Precomp.MakedCompLayer;
            MatLayer.moveBefore(Select[i]);
            MatLayer.enabled = false;
            //調整レイヤー作成
            var AdjLayer = activeComp.layers.addSolid([0,0,0],
                                                "Disp_by_" + MatLayer.name, 
                                                activeComp.width,
                                                activeComp.height,             
                                                activeComp.pixelAspect );
            AdjLayer.adjustmentLayer =  true ;
            AdjLayer.moveAfter(Select[i]);
            AdjLayer.name = "Disp_" + Select[i].name
            //モーションタイル
            var Tile = AdjLayer.property("ADBE Effect Parade").addProperty("ADBE Tile");
            Tile.property("ADBE Tile-0004").setValue(500);
            Tile.property("ADBE Tile-0005").setValue(500);
            Tile.property("ADBE Tile-0006").setValue(true);
            //ベクトルブラー
            var VectorB = AdjLayer.property("ADBE Effect Parade").addProperty("CC Vector Blur");
            VectorB.property("CC Vector Blur-0005").setValue(MatLayer.index);
            var Disp = AdjLayer.property("ADBE Effect Parade").addProperty("ADBE Displacement Map");
            Disp.property("ADBE Displacement Map-0001").setValue(MatLayer.index);
            //上の階層から調整可能なマットぼかし
            var Slider = AdjLayer.property("ADBE Effect Parade").addProperty("ADBE Slider Control");
            var MatAdjLayer = MatComp.layers.addSolid([0,0,0],
                                                "マットをぼかす", 
                                                MatComp.width,
                                                MatComp.height,             
                                                MatComp.pixelAspect );
           MatAdjLayer.adjustmentLayer =  true ;
           var Bluer = MatAdjLayer.property("ADBE Effect Parade").addProperty("ADBE Camera Lens Blur");
           Bluer.property("ADBE Camera Lens Blur-0001").expression = " comp(\"" + activeComp.name + "\").layer(\"" + AdjLayer.name + "\").effect(\"スライダー制御\")(\"スライダー\") "
           }
       
        }else{alert("レイヤーが選択されていません")
            }
        
}else{alert("アクティブなコンポジションがありません")
    }

MMDBridgeでエクスポートしたAlembicにマテリアルを自動で割り当てる【Cinema4d】

MMDBridgeから出力したAlembicには折角なのでC4dで作ったマテリアルを割り当てたいところですが、毎回毎回シーンごとに数十個のマテリアルを割り当てるとつらいので、一回テキストファイルに対応をまとめておくことで自動適用できるスクリプトを書きました。
AEのExtendScriptよりも色々関数があって楽ですがリファレンスが無慈悲だったりして目的のものを探すのには一苦労しますね...


読み込んだAlembicをグループ化して選択し、

【番号】 改行

【マテリアル名】 改行

【名前】改行

という風に書いたテキストファイルを読み込めばOKです

マテリアルは事前に並べといてください

import c4d
from c4d import storage
def main():
    if not op:return
    #オープンダイアログを使ってファイルパスを取得
    path = storage.LoadDialog(title = 'File')
    if not path:return
    path = unicode(path, 'UTF-8')

    #ファイルを開いてデータを読み込む
    data_file = open(path, 'r')
    data = data_file.read()
    data_file.close()
    #改行で区切ったリストに
    data = data.split('\n')
    #選択中のオブジェクトの子をすべて取得してリストに
    children = op.GetDown()
    all_children = []
    while children:
        all_children.append(children)
        children = children.GetNext()
 #Alembicと.GetDownによる取得順が逆なので
    all_children.reverse()
   #選択中のオブジェクトの子を順にリネーム、マテリアル適用
 #子をファイルの三行目を参照してリネーム
    for c in xrange(len(all_children)):
        all_children[c].SetName(unicode(data[3*c+2], 'shift-jis'))
  #子のマテリアルをファイルの二行目から参照して探す
        Mat = doc.SearchMaterial(unicode(data[3*c+1], 'shift-jis'))
  #テクスチャタグを作って整えてマテリアルを乗せて適用
        tag = c4d.BaseTag(c4d.Ttexture)
        tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW
        tag[c4d.TEXTURETAG_MATERIAL] = Mat
  #タグを最後尾に置くメソッドがないのか...(困惑)
        last = all_children[c].GetFirstTag()
        while True:
            if not last.GetNext():
                break
            last = last.GetNext()
        all_children[c].InsertTag(tag, last)
if __name__=='__main__':
    main()

BB劇場におけるMMD,3DCGとの合成について【AfterEffects,Element3D,Cinema4D】

 数か月ほどずっと苦心していた作業の流れがようやく上手くまとまったので、同じ道を通ろうとするホモ達のために辿った道のりをここに載せておこうと思います.

 近年のBB劇場の要となりつつある3d表現ですが、基本的に編集ソフトのカメラ制御は表現に限界がある上にクソ重く、やはり適宜3dcgソフトを使えるとずいぶん表現の幅が広がってきます.特にMMDを使うことができれば、MMDを原画とする声優達に関しては自由自在に扱うことができ、さらに2.5MMDを用いれば男優たちですら自由自在に動かすことが可能です.

 しかし、MMDなどから出力した動画でいざ編集をしようとすると面倒なことに気づきます.当然ながら、出力した動画は2dであるため、編集ソフト側で構成した3d要素との前後関係について破綻が発生します.1モデル程度なら1fずつレイヤー順序を調整しても良いですが、多数のモデルや、複雑なカメラワーク、さらには被写界深度やパーティクルシステムによるエフェクトとの前後関係を解決するのは苦業です.特にBB劇場においては様々な2dのBB素材やエフェクト素材を用いることになるので、全てMMD上で作るというわけにもいきません.

 というわけで、この前後関係を機械的に解決する方法を自分が辿って行った順に述べます.基本的にMMDとAfterEffects、最後にCinema4dを用いますが、AviutlやBlenderでも同様の考え方で処理ができると思いますので、ご参考までに.

基本的な考え方

 3dcgから出力した動画が失っているのはZ軸の情報ですので、これを何とかしてレイヤーに渡すことができれば良いわけです.動画にZ軸の情報を持たせる方法として一番単純なのはデプスマップ(Zdepth,Zbufferh,深度マップ)です.RGBやグレースケールにカメラから見たZ軸の情報を載せるものです.

f:id:ggradesticg:20181222010949p:plain

Z軸情報をグレースケールで含んだ画像

 これによってZ軸を白黒上に載せることができているので、あとは3dレイヤーを置きたい位置に対応した白または黒の値で画像を二分し(レベル補正系のエフェクト)、

f:id:ggradesticg:20181222011423p:plain

手前から二番目の草辺りの白レベルで二分した画像

 これをルミナンスキーで抜いたもの(ワールドマット)のアルファチャンネルを3dレイヤーに移せばよいわけです(AEならルミナンスキーマットでOK、Aviutlだと上のオブジェクトでクリッピング)

f:id:ggradesticg:20181222011840p:plain

手前から二番目の草辺りに挿入された2dレイヤー

f:id:ggradesticg:20181222012335p:plain

ルミナンスキーマット C4dレンダラーでは使えないので注意

 この状態で元の3dcg上のカメラと編集ソフトのカメラの動きがあっていれば、他のフレームでしっかりと前後関係が成立します.ただし、デプスマップはあくまでカメラから見た深度を表しているので、絶対座標とグレースケールの関係はカメラが動くと変わります.そこはキーフレームで対応するか、自分で計算のエクスプレッションを書かなければいけません.

 また、アンチエイリアスやモーションブラーを使うとエッジの部分は少し雑くなりがちです.これは割とどうしようもない話ですが、ワールドマットにアルファブラーをかけたりすると違和感は減ると思われます。

Aviutl+MMD

 自分はモデルの複製をやりたかったのであきらめたのですが、MMEにより描画されたものがなければ一応できるはずです.(なので特に困る点としてCloneが使えません)

 Z情報をこの辺りでとってきて

seiga.nicovideo.jp

seiga.nicovideo.jp

カメラを持ってきて

www.nicovideo.jp

デプスマップのレベル補正にキーフレーム(中間点)を打てばそこそこ正しい前後関係にはなると思います.ただしキーフレームを打つのは手間なので、スクリプト制御でそのあたりを計算してあげると良いのかもしれません(Aviutlのスクリプト制御については全く知らないので想像ですが)

 

AfterEffects+MMD+Blenderのみ

 前述の方法でZバッファを取ってきた後,レベル補正(個々の制御)でマットを作ります.

 さて、最大の問題はMMD2AEの配布が停止していることです.ということでMMDBridgeによってAlembicを出力し、Blenderで読み込み、BlenderのアドオンでAEに移します.スクリプトファイルの形で出力されるのでAEで実行すればカメラを取り出せます.

f:id:ggradesticg:20181222015016p:plain

ユーザー設定からAdobe After Effects(.jsx)をONにし、エクスポートで選択する

 この方法には実は深刻な問題があり、各ソフトのワールド座標系の違いから、最終的にカメラがいるワールド座標がクソデカくなるようで、.出力した動画とAEのワールド座標を合わせるのがかなり至難の業です.AEの座標系では原点が画面の中央ではなく左上にくる上、だいたい40倍ぐらいスケールが変わるっぽいですが、正確な値がちょっとよくわからないので、ヌルやエクスプレッションで微調整するしかないです.

Element3dを経由する

 ということでMMD2AEがない現状、カメラをぴったり合わせるのが難しいため、破綻を避けるには先にMMDの動画を出力することをあきらめて、AE上で描写させるという手を使うことになります.

 AEプラグインのElement3dを使うとObjシーケンスが読み込めるので、多少前述の方法で移行したカメラが元々のMMDのものと異なっても描写は破綻しません.またElement3dにはワールドマットを作成する機能も付いているため、その点での破綻も避けることができます.さらにElement3dはマテリアル設定などの普通の3dcgソフトの機能を有しているので、MMDよりかなりクオリティの高いレンダリングができます.クローナー、デフォーム、環境マップといった機能もあるので、Cloneが使えない問題も解決することができます.しかしながら、Element3dでMMDのObjシーケンスを読み込む際には二点問題があるようです.

 まず、MMDBridgeについているObjシーケンスエクスポーターはMMDの外親登録とモデル読み込み順序の影響を受け、メッシュが大きく破綻することがあります(外親登録の子が親より後に読み込まれていると破綻するっぽい)さらに、Element3dのBakedAnimationの不具合としてワールド座標が破綻することがあります(原点がフレームによって変わったりする)

 この問題については、MMD→MMDBridgeAlembic→Blender→アニメーション付きでObjエクスポート→Element3dと経由することで解決できるようです.

f:id:ggradesticg:20181222022446p:plain

出力先を指定する画面の左下にあるアニメーションにチェックをいれる

 二点目の問題として、Element3dのOBJシーケンス描画はメチャクチャ重いです.正直なところデプスマップをプロキシ出力しておき、それからワールドマットを作るようにしないと重すぎてエフェクトを付けたりするのが厳しくなります.(レイヤーの数だけElement3dが同じ計算を繰り返すことになるので、相当重い)

Cinema4dを経由する

 ということで、MMD2AEに代わるスクリプトを自分で書く技術がない限りは3DCGソフトに頼るしかなさそうです.マテリアル設定やレンダリング設定を覚え、かなりのレンダリング時間に耐えなければなりませんが、これまでの問題は解決しました.

 特にCinema4dはAEとの連携が強いといわれるだけあって、色々できます.MMDBridgeによってAlembicでMMDをC4Dに移すことができるので、アニメーションをMMDで制作した後C4dに写し、マテリアルを整え、マルチパスを付けてレンダリングすればデプスマップもワールドマットも手に入ります.

cubelic3.jp

c4d.motiondesign81.com

 後からワールドマットを作成したくなった時にはデプスマップから作成すれば良いです.

 さらに、AEでC4dファイルを読み込めばそこからカメラとライトの情報も抽出できるのでそのあたりも解決します.

 さらにC4dにおいて、外部コンポジットタグを使うことでオブジェクトの座標をAEで読み込むことすらできます.ただし外部コンポジットタグはキーフレーム座標しか取り出せないため、ヌルにXpressoでMMDAlembicポリゴンの中心座標を渡して、キーフレームにベイクするなどする必要があります.

 Xpressoはこんな感じで組んで

f:id:ggradesticg:20181222025015p:plain

XpressoでMMDオブジェクトの座標をヌルに移す

アニメーションをベイクして外部コンポジットタグを付けてAEでC4dを読み込めばOKです

cubelic3.jp

おそらくこの一連の作業ならCinema 4D Liteでもできる気がします.

 

 

まだまだ各ソフトについて良くわかんないですが、何かあれば@GGradestまでお気軽にどうぞ.