株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • Arnoldのちょいといい話!? part.3 -node_loader&vectorOp shader-

Arnoldのちょいといい話!? part.3 -node_loader&vectorOp shader-

2016/6/27

Tag: ,,,,,

ご無沙汰しております。
開発室ゴトー[@jackybian]です。
前回、前々回の投稿を見返してみたら、なんと今回で3回連続の梅雨時期投稿!
DF Talkの順番は毎回シャッフルして決めてるんですけど、よっぽど梅雨と縁でもあるんだろうか?
いよいよ本格的に梅雨到来?ジメジメ-ムシムシしてきましたね(lll-ω-) 季節の変わり目、皆さんもどうぞ体調には気をつけください。

さて前々回の「Arnoldのちょっといい話!?」で
【MtoAでSItoAシェーダーを使っちゃう方法】をご紹介しましたが一部のshaderでnodeIDがコンフリクトすることがあります。
実際にはあまり問題にならなかったりしますがあまり気分のよいものではないですよね。それとSItoAのシェーダーはmayaの
テンプレート(.py)がないのでUIが分かりにくかったりもします。

最近はHair周りのことをしているのですが、その過程でちょっとしたshaderが必要になったのでそのお話をすることにします。

Hairをレンダリングする際、AOVとしてsurface_n(毛の発生元meshのnormal), surface_p(毛の発生元meshのposition), surface_uv(毛の発生元meshのuv)などを出力したりします。
(Hairにmeshなどの情報を付与する方法は今回は割愛させていただきます。次回にでもまた)
多くの場合、comp用の素材ですがlighterがこれらの情報を使ってshaderネットワークで調整したい場合もあるかと思います。
例えば、surface_nとlightベクトルの内積を使ってlookを調整するとか。
MtoaではmayaのvectorProductノードをサポートしていない様で今まではSItoAのシェーダーを使っていたのですが、先程お話したnodeIDやUIの問題から簡単なvector演算のshaderを書きました。その過程で見つけたちょっとニッチな内容ですが最後までお付き合い頂けたらと思います。

node_loader

おかげさまで弊社も今年で16年目を迎えておりますが、最近ではshotワークだけでなくassetワークなども外注させて頂くことが増えてきました。その際、社内pluginにはDF_licenseという有効期限設定を埋め込んだ形で提供しています。有効期限が切れている場合にはそのpluginがloadされない仕組みです。
shaderの場合、loadさせなくすることが難しかったようでピクセル計算の中で期限切れの場合は黒で塗りつぶすなどの対処を行ってきました。本来なら計算させる必要ないのにな~なんか釈然としないな~と。
そんな思いを抱きながら、arnoldのshaderを書いているとnode_loaderという処理でshaderの登録がされていることがわかり、この処理をすっ飛ばしちゃえば???
という訳で試してみました。
node_loaderではメソッド、出力タイプ、名前、ノードタイプ、バージョンを設定することでロードされるようになります。

node->methods = *******;
node->output_type = AI_TYPE_RGB;
node->name = "****";
node->node_type = AI_NODE_SHADER;
strcpy(node->version, AI_VERSION);

詳細に関しては、Arnold Shaders Tutorial – Solid Angle を参照してみてください。

これらの処理をスルーすることでloadしなくすることが出来ます。(まぁ、ある意味裏技的な方法かもですが…)
Mtoaはmtoa.mllがロードされる時にARNOLD_PLUGIN_PATHが通っている場所にあるshaderが順次loadされる仕組みになっています。実際に期限切れの状態でnode_loader処理をスルーしてみるとOutput Windowに

[DF: dfArVectorOp] License was Expired !! (Expire: 2016.1.31)
00:00:00 WARNING |  dfArVectorOp.dll was compiled against non-compatible Arnold UNKNOWN VERSION

期限切れメッセージを表示した上でloadできない状態になります。
これで他のpluginと同じくload自体を回避できます。あ~スッキリ!

vectorOp shader

前述のSItoA利用によるnodeIDのコンフリクトやUIの分かりにくさを回避するために作ったものです。SItoAには多くのUtility系のshaderがあるのですが、その中にsib_vector_math_scalarやsib_vector_math_vectorと呼ばれるものがあり、ベクトルの
加算, 減算, dot, cross, length, distance, negate, normalize, min, max, multiply by scalar, divide into scalarといった演算ができます。 これらの演算を1packにしたshaderを書きました。
mtoaでのarnold用shaderは基本的に.dll, .mtd(metadata), .py(AETemplate)の3つファイルから構成されています。
簡単なものなのでcodeを載せておきます。(※先程のnode_loaderのスルー処理は入れてません)

dfArVectorOp.cpp

#include <ai.h>
#include <cstring>

AtColor Vec3ToRGB(AtVector InVector3)
{
	return AiColor(InVector3.x, InVector3.y, InVector3.z);
}

AtColor FloatToRGB(float InFloat)
{
	return AiColor(InFloat, InFloat, InFloat);
}

AI_SHADER_NODE_EXPORT_METHODS(dfArVectorOpMTD);

enum dfArVectorOpParams
{
	p_mode,
	p_vector1,
	p_vector1invert,
	p_vector1normalize,
	p_vector2,
	p_vector2invert,
	p_vector2normalize,
	p_scalar1
};

enum Ops
{
	V1_PLUS_V2 = 0,
	V1_MINUS_V2,
	DOT_V1_V2,
	V1_CROSS_V2,
	DISTANCE_V1_V2,
	MINIMUM_V1_V2,
	MAXIMUM_V1_V2,
	LENGTH_V1,
	V1_MULT_SCALAR,
	V1_DIV_SCALAR
};

static const char* OpNames[] = {
	"Vector1 + Vector2",
	"Vector1 - Vector2",
	"Dot(Vector1,Vector2)",
	"Cross(Vector1,Vector2)",
	"Distance(Vector1,Vector2)",
	"Minimum(Vector1,Vector2)",
	"Maximum(Vector1,Vector2)",
	"Length(Vector1)",
	"Vector1 x Scalar1",
	"Vector1 / Scalar1",
	NULL
};

node_parameters
{
	AiParameterEnum( "mode", DOT_V1_V2, OpNames);
	AiParameterVec ( "vector1", 0.0f, 0.0f, 0.0f );
	AiParameterBool( "vector1invert",false);
	AiParameterBool( "vector1normalize",false);
	AiParameterVec ( "vector2", 0.0f, 0.0f, 0.0f );
	AiParameterBool( "vector2invert",false);
	AiParameterBool( "vector2normalize",false);
	AiParameterFlt ( "scalar1", 0.0f);
}

node_initialize 
{
}

node_update 
{
}

node_finish 
{
}

shader_evaluate
{
	AtVector vector3;
	// Get Parameter
	int mode =				AiShaderEvalParamInt(p_mode);
	AtVector vector1 =		AiShaderEvalParamVec(p_vector1);
	bool vector1invert =	AiShaderEvalParamBool(p_vector1invert);
	bool vector1normalize =	AiShaderEvalParamBool(p_vector1normalize);
	AtVector vector2 =		AiShaderEvalParamVec(p_vector2);
	bool vector2invert =	AiShaderEvalParamBool(p_vector2invert);
	bool vector2normalize =	AiShaderEvalParamBool(p_vector2normalize);

	// Invert, normalize
	if(vector1invert)
		vector1 = -vector1;
	if(vector2invert)
		vector2 = -vector2;
	if(vector1normalize)
		vector1 = AiV3Normalize(vector1);
	if(vector2normalize)
		vector2 = AiV3Normalize(vector2);

	// Operation - Scalar
	if (mode == LENGTH_V1){
		sg->out.RGB = FloatToRGB(AiV3Length(vector1));
	}
	else if (mode == DOT_V1_V2){
		sg->out.RGB = FloatToRGB(AiV3Dot(vector1, vector2));
	}
	else if (mode == DISTANCE_V1_V2){
		sg->out.RGB = FloatToRGB(AiV3Dist(vector1, vector2));
	// Operation - Vector
	}else if (mode == V1_PLUS_V2){
		vector3 = vector1 + vector2;
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == V1_MINUS_V2){
		vector3 = vector1 - vector2;
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == V1_CROSS_V2){
		vector3 = AiV3Cross(vector1, vector2);
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == MINIMUM_V1_V2){
		vector3 = AiV3Min(vector1, vector2);
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == MAXIMUM_V1_V2){
		vector3 = AiV3Max(vector1, vector2);
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == V1_MULT_SCALAR){
		vector3 = vector1 * AiShaderEvalParamFlt(p_scalar1);
		sg->out.RGB = Vec3ToRGB(vector3);
	}else if (mode == V1_DIV_SCALAR)
	{
		float scalar_input = AiShaderEvalParamFlt(p_scalar1);
		scalar_input = scalar_input <= AI_EPSILON ? 1.0f : 1.0f / scalar_input;
		vector3 = vector1 * scalar_input;
		sg->out.RGB = Vec3ToRGB(vector3);
	}
}

node_loader
{
	if (i > 0)
		return false;

	node->methods     = dfArVectorOpMTD;
	node->output_type = AI_TYPE_RGBA;
	node->name        = "dfArVectorOp";
	node->node_type   = AI_NODE_SHADER;
	strcpy_s(node->version, AI_VERSION);
	return true;
}

dfArVectorOp.mtd

[node dfArVectorOp]
    maya.name               STRING "dfArVectorOp"
    maya.id                 INT 0x00118326
    maya.classification     STRING "shader/utility"
    maya.output_name        STRING "outColor"
    maya.output_shortname   STRING "out"

    [attr mode]
        desc                STRING "Operation of Input Vector1 and Input Vector2."
        maya.keyable        BOOL    True
    [attr vector1]
        desc                STRING "Vector1."
        maya.keyable        BOOL    True
    [attr vector1invert]
        desc                STRING "Invert Vector1."
        maya.keyable        BOOL    True
    [attr vector1normalize]
        desc                STRING "Normalize Vector1."
        maya.keyable        BOOL    True
    [attr vector2]
        desc                STRING "Input Vector2."
        maya.keyable        BOOL    True
    [attr vector2invert]
        desc                STRING "Invert Vector2."
        maya.keyable        BOOL    True
    [attr vector2normalize]
        desc                STRING "Normalize Vector2."
        maya.keyable        BOOL    True
    [attr scalar1]
        desc                STRING "Operation of Input Vector1 and Input Scalar1."
        maya.keyable        BOOL    True

dfArVectorOpTemplate.py

import pymel.core as pm
import mtoa.utils as utils
import mtoa.ui.ae.utils as aeUtils
from mtoa.ui.ae.shaderTemplate import ShaderAETemplate

class AEdfArVectorOpTemplate(ShaderAETemplate):

    def modeDim(self, nodeName):
        modeAttr = self.nodeAttr('mode')
        modeValue = pm.getAttr(modeAttr)
        if(modeValue > 6):
            pm.editorTemplate(dimControl=(nodeName, "vector2", True))
            pm.editorTemplate(dimControl=(nodeName, "vector2invert", True))
            pm.editorTemplate(dimControl=(nodeName, "vector2normalize", True))
            if(modeValue == 7):
                pm.editorTemplate(dimControl=(nodeName, "scalar1", True))
            else:
                pm.editorTemplate(dimControl=(nodeName, "scalar1", False))
        else:
            pm.editorTemplate(dimControl=(nodeName, "scalar1", True))
            pm.editorTemplate(dimControl=(nodeName, "vector2", False))
            pm.editorTemplate(dimControl=(nodeName, "vector2invert", False))
            pm.editorTemplate(dimControl=(nodeName, "vector2normalize", False))

    def setup(self):
        #self.addSwatch()
        self.beginScrollLayout()

        #self.addCustom('message', 'AEshaderTypeNew', 'AEshaderTypeReplace')

        # Begins a "Main Attributes"
        self.beginLayout("Main Attributes", collapse=False)
        self.addControl("mode", label="Operation", changeCommand=self.modeDim, annotation="Operation between Vector1 & Vector2 & Scalar1")
        self.addControl("vector1", label="Vector1", annotation="Input Vector1")
        self.addControl("vector1invert", label="Invert", annotation="Invert Vector1")
        self.addControl("vector1normalize", label="Normalize", annotation="Normalize Vector1")
        self.addControl("vector2", label="Vector2", annotation="Input Vector2")
        self.addControl("vector2invert", label="Invert", annotation="Invert Vector1")
        self.addControl("vector2normalize", label="Normalize", annotation="Normalize Vector1")
        self.addControl("scalar1", label="Scalar1", annotation="Input Scalar1")
        self.endLayout()

        pm.mel.AEdependNodeTemplate(self.nodeName)

        self.addExtraControls()

        self.endScrollLayout()

このshaderを使ってsurface_nとvector2使ってHairをレンダリングした例です。


この他にもcurve tangentとvector演算したりすると面白い表現ができるかもしれません。

という訳で今回は簡単なshaderを書きつつ、shaderのloadをコントロールする方法についてでした。
shaderの方はmatrixのinputを追加して座標変換もできるようにすると良いかもですね。
loadコントロールは、ちょっとニッチだったかもしれませんが会社によっては社内ツールを期限付きで渡さないといけない状況とかあったりしませんかね?興味のある方はぜひ利用してみてください。

では今回はこのあたりで。(´∀`*)ノ マタ~

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。
Pocket

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

*