3D coat ペイントの勉強をしております

2012_10_24Image_01
どういった経緯でこうなったのだろうか?
いつものことながら、自分でも不思議に思います。

 

突然ですが、3Dcoatでのペイントを勉強しております。
うーん、経緯を考えても良く分からない。
それまで何をやっていて、こうなったのだろうか?
今日記事を書くにあたり、色々と考えてみましたが、途中でめんどくさくなったので、考えるのをやめました。
とにかく3Dcoatをやっております。

 

今までリトポにしか使用しておりませんでしたが、せっかくなので色々と機能を知ろう。
あれ、それが経緯かな。まぁ、いいや。
で、ボクセルなどにも挑戦してみたのですが、どうも手になじまない、、
最近の3Dcoatでのモデリングは、おおまかな形をボクセルで作って、割と早い段階で
ペイントスカルプトに移行する。というのもあるようです。
ZBrushだとDynameshであらかた作る。という感じでしょうか?
また、ZBrushではシャドウボックスなどを使って結構進めてゆく手法があったりするので、
それらも3Dcoatでいう、ボクセルモデリングに近いように思います。

 

それはともかく、モデリングに対して敷居を感じてしまったので、ペイントに移行してみました。
で、感想は、すごくいい。です。
modoとの感覚の違いを試してみましたが、modoは基本的にペイントが重いです。
速いストロークでハッチングすると基本的にダメです。
そして、それ以上に3Dcoatが使いやすい理由はフォトショップのレイヤー構造を編集しながらペイントできる。
ということだと思います。
BodyPaintでもそういった機能がありましたが、いかんせん操作に問題がありました。
もっとも自分の知っているBodyPaintのバージョンは相当古いので、現在のものがどうであるかは知りません。
でも、この値段でこの機能はどちらにせよ、凄い。
今のところストレスはありません。むしろ自分の画力の無さがストレスです。
あ、一つだけ思いついたのは、フォトショップの「d」キーに登録されている、
描画色と背景色のリセット、これがほしい。有りそうな気もするし、設定できそうな気もします。

 

もっとちゃんとしたものを作ったら3Dcoatのペイントについて、記事を書いてみたいと思います。

modo ToyCameraとHybridLights

modomodeのTakumiさんが作られたスクリプトを紹介します。
Luxologyのこちらページからダウンロードできます。

 

いやー、凄い。感激しました。
因みにレンダリングは、
2012_10_20Image_01
このような感じです。
OSの再インストールでZBrushが使えなくなったので古いモデルで失礼します。

 

いい具合のエリアライトを作ってくれるスクリプトとカメラのレンズを変えてくれるスクリプトのようです。
ライトの方はエリアライトだけでなく、蛍光灯も作ることができるようです。

 

カメラのエフェクトが非常に面白いです。
選択したカメラを変更してくれる機能と、すべてのカメラを下に戻す機能があるので、気軽に使えます。
ただ、難点はプレビューが遅いことでしょうか、、自分のパソコンの遅さにげんなりします。
でも、レンダリングは早いです。プレビューするよりも小さいサイズでレンダリングしたほうが早そうです。
デフォルト設定でレンダリングしていますが、それでも十分に味のある絵ができてしまう。
しばらくこれで楽しめそうです。
モデルさえあれば、、

Maya Script Pymel ウィンドウの表示

import pymel.core as pm

def test_attr_slider():
	obj = pm.ls(hl=1)
	if not obj:
		obj = pm.ls(os=1,o=1)
	if obj:
		pm.attrFieldSliderGrp("test",e=1,at="{0:}.tx".format(*obj))

def ui():
	name = "test"
	if pm.window(name,q=1,ex=1):
		pm.deleteUI(name)
	win = pm.window(name,t=name)
	win.setWidthHeight([250,50])
	
	with win:
		with pm.columnLayout():
			pm.attrFieldSliderGrp("test",cw=[1,1],min=-100,max=100)
	pm.scriptJob(p="test",e=["SelectionChanged","test_attr_slider()"])
ui()

ウィンドウの表示について、色々と学べたので、備忘録的に書いておきます。
「__exit__」のメソッドを持ったオブジェクトであれば、「with」文に入れると簡単に書けるようです。
マヤコマンドではUIの区切りで「cmds.setParent(‘..’)」と各必要がありますが、
それが要らなくなります。
なおかつPythonではインデントでブロックを見ているのでUIを記述すると
どうしても読みづらくなる傾向がありますが、それもこれで一発解決です。素晴らしい。
因みにマヤコマンドで同じ事をしてもダメでした。dir()で調べてみるとやはりメソッドが少ない。
 

上記のスクリプトでは、選択されたオブジェクトのトランスレートXをスライダーや数値入力で
変更できるスクリプトです。
ウェイトエディタを応用すれば複数のオブジェクトを一変に操れるだろう、と言うことはわかるのですが、
必要性を感じないので書きませんでした。

 

それと、変なところで可変長のリストを受け取るようにしています。
obj = obj[0]
というような書き方で受け取っても同じ事だと思いますが、こんな使い方もあるんだなぁ、
と、いうことで。

Maya API 2.0

API2.0というものを知りました。
2012のオンラインマニュアルには書いてありませんが、2013のマニュアルに書いてありました。

 

Pymelが遅いのはどうしようもないようです。
すみません、詳しくはわかりません。
で、APIを使って自分で必要にあわせてオブジェクトを作る。
というのが、良いようですが、PythonでやるならAPI2.0を使うのがベター。
と、言った感じなのでしょうか。

 

しかし中々情報がありません。
色々と探してみると、

import maya.api.OpenMaya as om2

print om2.MGlobal.getActiveSelectionList()

のようにすると、選択されているオブジェクトの情報がプリントできるようです。
ただ、これをfor文に入れると、イテレータではない、と怒られます。

import maya.api.OpenMaya as om2

depFn = om2.MFnDependencyNode()
sel = om2.MGlobal.getActiveSelectionList()

for x in range(sel.length()):
	mObj = sel.getDependNode(x)
	depNode = depFn.setObject(mObj)
	print depNode.name()

このようにすると、名前を出力することができます。
API1.0に比べるとちょっと記述が少ないのでしょうか?
参考にしたコードがそのように書いていたのか、どっちなのか分かりません。
ともかく、

import maya.api.OpenMaya as om2

とインポートするとAPI2.0が使えるようです。
2012でも使用すことができるようです。

 

API関連は英語ばかりで良く分からない、、

PS Script JavaScript ZBrushのBPRレンダリングのコンポジット その2

otherDoc.sort(function(a){return(a.name.indexOf("Light")!=-1) ? 1 :0;})
otherDoc.sort(function(a){return(a.name.indexOf("Shadow")!=-1) ? -1 :1;})
otherDoc.sort(function(a){return(a.name.indexOf("AO")!=-1) ? -1 :1;})

JavaScriptのソートについて調べていると、色々と見つかりました。
で、一応コードだけ書いておきます。
30行目あたりに挿入すると使えるかと思います。

 

何をしているかというと、
ファイル名に「Light」があればレイヤーが上の方になります。
「Shadow」が含まれていれば下の方になります。
「AO」があれば一番下になります。(「Render」に元々入っている背景レイヤーの上)
ただ、これだけだと数字が書いてある場合にその数字に従ったソートはしてくれません。
最後にもう一つ関数を作って処理すればいいかと思いますが、めんどくさかったのでやめておきました。

 

なので、数字の並び替えは手で行なってください。
どっちにしろ、手でやるのであれば、こんなソートも必要無いように思います。
大した労ではないので、とりあえず良いだろう。というくらいの気持ちです。

 

なるほどねぇ、無名関数って、こんな時に使うのですね。
まだまだ勉強が足りないなぁ。

PS Script JavaScript ZBrushのBPRレンダリングのコンポジット

function bpr_comp()
{
	otherDoc = [];
	var BprDoc;
	BprFlag = 0;
	if (app.documents.length > 1){
		for( i=0 ; i<app.documents.length ; i++ )
		{
			activeDocument=documents[i]
			if (app.documents[i].name.indexOf("_Render")!=-1)
			{
				BprDoc = app.documents[i];
				BprFlag ++;
			}else{
				otherDoc.push(app.documents[i]);
			}
		}
	}else{
		alert("2枚以上の画像を開いてください");
		return;
	}
	if (BprFlag == 0){
		alert("「_Render」 の名前が付いた画像を開いてください");
		return;
	}
	if (BprFlag>1){
		alert("「_Render」 の名前が付いた画像が2枚以上存在します");
		return;
	}
	
	activeDocument = BprDoc;
	docObj = activeDocument.artLayers;
	docObj[docObj.length-1].opacity = 100;
	
	for(i=0;i<otherDoc.length;i++)
	{
		activeDocument = otherDoc[i];
		if (activeDocument.name.split("_")[0] == BprDoc.name.split("_")[0]){
			activeDocument.selection.selectAll();
			activeDocument.selection.copy();
			
			LayerName = activeDocument.name.split("_")[1].split(".")[0];
			activeDocument.close(SaveOptions.DONOTSAVECHANGES);
			
			activeDocument = BprDoc;
			if (LayerName.indexOf("Depth")!=-1 || LayerName.indexOf("Mask")!=-1){
				activeDocument.channels.add();
				paste_inplace();
				select_rgb_channel();
				activeDocument.selection.deselect();
			}else{
				paste_inplace();
				newLayer = activeDocument.activeLayer;
				newLayer.name = LayerName;
				if (LayerName.indexOf("AO")!=-1 || LayerName.indexOf("Shadow")!=-1){
					newLayer.blendMode = BlendMode.MULTIPLY;
				}
				if (LayerName.indexOf("Spec")!=-1 || LayerName.indexOf("Light")!=-1 || LayerName.indexOf("Ref")!=-1){
					newLayer.blendMode = BlendMode.SCREEN;
					if (LayerName.indexOf("Ref")!=-1){
						newLayer.opacity = 20;
					}
				}
			}
		}else{
			activeDocument.close(SaveOptions.DONOTSAVECHANGES);
		}
	}
}
function paste_inplace()
{
	var idpast = charIDToTypeID( "past" );
	var desc3 = new ActionDescriptor();
	var idinPlace = stringIDToTypeID( "inPlace" );
	desc3.putBoolean( idinPlace, true );
	var idAntA = charIDToTypeID( "AntA" );
	var idAnnt = charIDToTypeID( "Annt" );
	var idAnno = charIDToTypeID( "Anno" );
	desc3.putEnumerated( idAntA, idAnnt, idAnno );
	executeAction( idpast, desc3, DialogModes.NO );
}
function select_rgb_channel()
{
	var idslct = charIDToTypeID( "slct" );
	var desc6 = new ActionDescriptor();
	var idnull = charIDToTypeID( "null" );
	var ref1 = new ActionReference();
	var idChnl = charIDToTypeID( "Chnl" );
	var idChnl = charIDToTypeID( "Chnl" );
	var idRGB = charIDToTypeID( "RGB " );
	ref1.putEnumerated( idChnl, idChnl, idRGB );
	desc6.putReference( idnull, ref1 );
	executeAction( idslct, desc6, DialogModes.NO );
}
bpr_comp()

相変わらず、唐突ですが、ZBrush用のフォトショップスクリプトです。
ファイルはBPRComp

 

ZBrushでBPRレンダリングすると、デフォルトで「BPR_Render.PSD」などのファイルが作られるかと思います。
それらのファイルに大して一括してコンポジット処理を行うスクリプトです。
処理するファイルをフォトショップで全て開いてからスクリプトを実行してください。
名前を見て判断しているので、命名規則があります。
拡張子は見ておりません。

 

まずメインファイルとなるファイル名は「○○○_Render.PSD」※○の文字数や文字の制限はありませんが、「_」(アンダーバー)は使わないでください。
重要なのは、アンダーバーのあとに「Render」と付くことです。
それ以外のファイルは○○○の名前とあわせてください。合わせていないと処理をしません。

 

それ以外のファイルでは、名前により特殊な処理を施します。
「○○○_Mask」や「○○○_Depth」であれば、それらの画像は「○○○_Render.PSD」のアルファチャンネルに追加されます。

 

「○○○_AO」、「○○○_Shadow」であれば、レイヤーとして追加され、ブレンドモードを「乗算」に変更し、名前をリネームします。
名前の最後に数字がついている場合、それもレイヤー名として設定されます。

 

「○○○_Spec」、「○○○_Light」、「○○○_Ref」の場合はブレンドモードを「スクリーン」にしてレイヤーを追加します。
「○○○_Ref」の場合は「不透明度」を「20%」に設定します。

 

処理が終わると、「○○○_Render.PSD」以外のファイルは勝手に閉じられます。
「○○○_Render.PSD」を開いた時に存在した「背景」レイヤーは「レイヤー0」に変更されます。

 

ちょっとだけ合成が楽になるかと思いますが、あくまでも土台作りの手助けなので後の合成は手でやってください。
因みにレイヤーの順番は手を付けておりません。
なので、それもちゃんとしないと意図したとおりになりません。
あくまでも最初の手助け程度です。
使いやすいように改造してください。

Maya Script Pymel 選択範囲の記憶 複数版

またまたスクリプトにはまっておりました。
以前書いた選択範囲の記憶、複数版もできるのではないか。
という思いを抱いてしまったので、作ってみました。

 

インスタンスを使う、とかカッコいいこと書いておりましたが、使いませんでした。
辞書を使いました。
中々苦戦しましたが、なんとかできたようです。多分。バグがあったらごめんなさい。
しかし、またまた作っておいてなんですが、使わないなぁ、、
なんでMayaはこうも選択範囲の作成が大変なんだろうか?
modoに慣れているとその使いにくさに絶句してしまう。
スクリプトを作ったはいいが、使わないなぁ、、もやは趣味の領域です。
スクリプトなので、もちろん遅いかと思います。
シーンを新たに開くとリセットされます。
記憶できる選択範囲は、頂点、エッジ、フェース、UVです。

 

テストです。
まずはひとつのオブジェクトを選択し、selection(“vertex”)で選択範囲を作ります。
2012_10_06Image_01
次にselection(“object”)を使ってすべてのオブジェクトを選択します。
2012_10_06Image_02
そのままselection(“vertex”)で頂点選択に戻ります。
2012_10_06Image_03
ちゃんと覚えています。
で、selection(“edge”)を使ってエッジ選択をします。
2012_10_06Image_04
再びselection(“vertex”)で頂点選択に戻ってみます。
2012_10_06Image_05
もちろん、まだ覚えております。
で、selection(“edge”)で再びエッジ選択にします。
2012_10_06Image_06
うん、今のところ大丈夫。多分。

 

で、ソースコードです。改造は自由に行なってください。

class MemSel(object):
	vsel = {}
	esel = {}
	fsel = {}
	uvsel = {}

mem = MemSel()

def mem_clear():
	mem.vsel = {}
	mem.esel = {}
	mem.fsel = {}
	mem.uvsel = {}

def mem_clear_sjob():
	sjob = pm.scriptJob(e=["SceneOpened","mem_clear()"])
mem_clear_sjob()

def selection(mode):
	if pm.selectMode(q=1,o=1):
		obj = pm.ls(sl=1,o=1)
	else:
		obj = pm.ls(hl=1)
	sel = pm.selected()
	newSelect = []
	if obj:
		names = []
		for o in obj:
			list = []
			if type(o) == pm.nodetypes.Transform:
				name = o.getShape()
				names.append(name)
				if sel:
					for s in sel:
						shapeName = s.split(".")[0]
						if shapeName == name:
							list.append(s)
					if type(s) == pm.MeshVertex:
						mem.vsel[name] = list
					if type(s) == pm.MeshEdge:
						mem.esel[name] = list
					if type(s) == pm.MeshFace:
						mem.fsel[name] = list
					if type(s) == pm.MeshUV:
						mem.uvsel[name] = list
		if mode == "object":
			pm.selectMode(o=1)
		else:
			pm.selectMode(co=1)
			pm.select(cl=1)
		if mode == "vertex":
			pm.selectType(smp=1,sme=0,smf=0,smu=0,pv=1,pe=0,pf=0,puv=0)
			for name in names:
				if name in mem.vsel and mem.vsel[name]:
					newSelect.append(mem.vsel[name])
		if mode == "edge":
			pm.selectType(smp=0,sme=1,smf=0,smu=0,pv=0,pe=1,pf=0,puv=0)
			for name in names:
				if name in mem.esel and mem.esel[name]:
					newSelect.append(mem.esel[name])
		if mode == "face":
			pm.selectType(smp=0,sme=0,smf=1,smu=0,pv=0,pe=0,pf=1,puv=0)
			for name in names:
				if name in mem.fsel and mem.fsel[name]:
					newSelect.append(mem.fsel[name])
		if mode == "uv":
			pm.selectType(pv=0,pe=0,pf=0,puv=1,smp=0,sme=0,smf=0,smu=1)
			for name in names:
				if name in mem.uvsel and mem.uvsel[name]:
					newSelect.append(mem.uvsel[name])
		if newSelect:
			pm.select(newSelect)

modo Script Python モデルの中心線をXゼロに設定

# python
import lx

fg = lx.eval("query layerservice layers ? fg")

if (lx.eval("select.typeFrom {edge;polygon;item;vertex} ?") and lx.eval("select.count edge ?") or 
	lx.eval("select.typeFrom {polygon;item;vertex;edge} ?") and lx.eval("select.count polygon ?") or
	lx.eval("select.typeFrom {vertex;edge;polygon;item} ?") and lx.eval("select.count vertex ?")):
	lx.eval("select.convert vertex")
	lx.eval("select.connect")
else:
	lx.eval("select.typeFrom vertex true")
	lx.eval("select.drop vertex")
	lx.eval("select.invert")

vtx = lx.eval("query layerservice verts ? selected")
lx.eval("select.drop vertex")

[lx.eval("select.element {0} vertex add index:{1}".format(fg,v)) for v in vtx if lx.eval("query layerservice vert.pos ? {0}".format(v))[0] == 0]
if lx.eval("query layerservice verts ? selected"):
	lx.eval("select.convert edge")
	lx.eval("select.edgeLoop base false m3d")
	lx.eval("select.convert vertex")
	vtx = lx.eval("query layerservice verts ? selected")
	lx.eval("select.drop vertex")
	[lx.eval("select.element {0} vertex add index:{1}".format(fg,v)) for v in vtx if not lx.eval("query layerservice vert.symmetric ? {0}".format(v))]
	lx.eval("vert.set x 0.0")

前回のスクリプト、半分選択にバグがありました。修正版を上げておきました。すみません。
modoでは取得するものが複数あれば配列、そうでなければ文字列として返ってくる。ということを忘れておりました。
「lx.eval」では頂点を一つしか選択していないときは配列として返って来ません。なので「lx.evalN」でそれがどのようなものでも配列で返すようにしました。

 

さて、今回のスクリプトは中心線がXゼロから少しずれているものをXゼロに設定するものです。
動作としては、
2012_10_04Image_01
このように中心線がずれているモデルを用意し、スクリプトを実行します。
選択があれば、そのメッシュだけ、なければそのレイヤーすべてのメッシュに適用されます。
2012_10_04Image_02
こんな感じです。
交差しているモデル、穴の開いたモデルでも対応できます。
「query layerservice vert.symmetric ?」便利ですねぇ。Mayaにも同じ物が欲しいです。
「?」マークの後ろに頂点インデックスをつけるとそれに対応したシンメトリーの頂点のインデックスを返してくれます。

 

しかし、作っておいてなんですが、エッジ選択のダブルクリックでループを選択し「vert.set x 0.0」をショートカットに割り当てた方が圧倒的に早いです。
試しにZBrushで随分前に作ったモデルでテストします。
2012_10_04Image_03
恐らくこのモデルの中心線はループ選択されている場所だと思います。
2012_10_04Image_04
スクリプトを適用するとこんな感じです。
このスクリプトの欠点は、ゼロにかかる頂点が幾つか存在しなければなりません。
そうでない場合は、反応なしです。
うーん、使えるのかなぁ、、

modo Script Python 半分選択

# python
import lx

xflag = 0
fg = lx.eval("query layerservice layers ? fg")

if (lx.eval("select.typeFrom {edge;polygon;item;vertex} ?") and lx.eval("select.count edge ?") or 
	lx.eval("select.typeFrom {polygon;edge;item;vertex} ?") and lx.eval("select.count polygon ?") or
	lx.eval("select.typeFrom {vertex;edge;polygon;item} ?") and lx.eval("select.count vertex ?")):
	lx.eval("select.convert vertex")
	vt = lx.evalN("query layerservice verts ? selected")[0]
	p = lx.eval("query layerservice vert.pos ? {0}".format(vt))[0]
	if p <= 0.00000001:
		xflag = 1
	lx.eval("select.connect")
else:
	lx.eval("select.typeFrom vertex true")
	lx.eval("select.invert")
vtx = lx.eval("query layerservice verts ? selected")
if vtx:
	lx.eval("select.drop vertex")
	for v in vtx:
		pos = lx.eval("query layerservice vert.pos ? {0}".format(v))
		if not xflag:
			if pos[0] >= -0.00000001:
				lx.eval("select.element {0} vertex add index:{1}".format(fg,v))
		else:
			if pos[0] <= 0.00000001:
				lx.eval("select.element {0} vertex add index:{1}".format(fg,v))
	lx.eval("select.convert polygon")

更新が滞ってしまいました。
スクリプトにはまっておりました、modoでもっと効率のいい書き方はないのだろうか。
そんなことを実験しているうちに、あれよあれよと、時間が過ぎました。
今のところの結論は、難しい。といった感じでしょうか。

 

今回のスクリプトは、パールですでに存在する、deleteHalfの改造版です。
そのスクリプトでは、+xのポリゴンを削除してくれます。
ついでなら、反対側をミラーコピーして、更にUVもミラーする。というようなスクリプトを書いておりましたが、
中心線がずれているモデルや、軸をまたがって交差しているモデルだと問題がある。
ということで、選択までに留めておきました。
で、元のパールのものとの一番の違いは、頂点やエッジやポリゴンですでに選択されている範囲があれば、
その範囲が存在する方を選択します。
要するにマイナスの方を選択したければ、マイナスにあるポリゴンを選択する。といった感じです。
2012_10_02Image_01
こんな感じに、選択すると、
2012_10_02Image_02
こんな感じに選択されます。

 

うーん、ちょっとだけ便利?
そういえば、「modo実践チュートリアルビデオ デザイナーズハウス編」、購入しました。
まだきちんと見たわけではないので、レビューは書けません。
ただ、すごいボリュームです。残念なことにマテリアルやレンダリングについてはほぼ触れられていはいないようです。
第二弾が出るのでしょうか?
しかし、これだけのボリューム、準備に相当時間がかかったことだろうと思います。本当にご苦労様でした。
なるほど、パイメニューって使い方次第では意外と使えるものなのですね。
ショートカットが埋まってしまって困っておりましたが、参考にして使ってみようかと思います。
あっ、そういえば、CADを起こしている途中だった、、すっかり忘れておりました。

 

※追記2012/10/3
バグフィックスです。すみません。
11行目「lx.eval」を「lx.evalN」に変更しただけです。

アニメーションが親切に解説されております

レンダリング、ライティングの基本が分かります

図版が見やすい美術解剖書です