はじめに
Qiitaに、異色のCGノウハウを投稿してみる。興味持つ人いるのかしかし。まぁいいやw
さてさて、こんにちは。トゥーンシェーディング、皆さんやってますか?
Mayaで色々方法はあるのですが、困るのが、細かい箇所の制御ですよね。
「この鼻筋に陰がほしいよ」
「逆にここ陰いらなくね?」
「ここの線を細くしたい。あるいは太くしたい」
「あ゛〜〜、でもこのパラメーターいじると他の場所がおかしく(以下略」
ということは往々にしてあります。
で、CGの世界ではかなり以前からそのソリューションもありまして、例えばOLMさんの「ペイントブラシによる演出可能なトゥーンシェーダ」というMayaベースのブラシ塗り塗り調整ソリューションがあったりします(論文も参考になるよ!)。
ただ、このOLMさんのMayaプラグインは残念ながら公開されていないんですよね。
そこで、似たようなものをオイラ作りました(この例だと、ミクさんモデルのテクスチャがちょっとグラデーション入ってるんで、トゥーンっぽく見えないですけど)。
以下の様な特徴を持ちます。
- Mayaの標準機能だけで実現できるようにしました。
- OLMさんのアルゴリズムを実装したわけではないです。大幅な劣化コピー、それ以下です。
- でも、そこそこ使えるんじゃね?
- 基本的にMayaのランバートやフォンのマテリアルをサポートするレンダラーに対応しているけど、一番相性が良いのはMayaソフトウェアレンダラー。
- (Expressionなどで工夫すれば)、設定した塗り調整の値をキー間で補間もできるんじゃね?
- 今回提案するのは、あくまで基本アイデアなので、皆さんノード構築を改良してもっと良いもの作れますきっと。
- モデルに、あらかじめきちんとUV(値は0~1の間にすること)が設定されている必要があります。
特徴と書いておきながら全然特徴の説明になってない気が…とりあえず行ってみましょう。
手順
トゥーンシェーディングのやり方は、今回こちらのサイト様(中央区アリゾナ町様)の方法を採用させていただきます。
まず、上記サイト様の方法を試してみてね。やり方マスターしたら、
早速始めましょう。
さて、今回はモデルとして、モデモデサイト様が配布されているミクさんモデルを使わせていただきたいと思います。カワエエ……ホンマカワエエわ……。
1. モデルを読み込んで、新規Lambertマテリアルを割り当てる。
モデルを読み込みましょう。読み込んでから、そのモデルに新規Lambertマテリアルを割り当てます。
2. LambertマテリアルのColorを黒にして、Incandescenced(白熱光)にRampシェーダーを接続する
Lambertマテリアルに名前をつけましょう。今回は「finalLambert」とします。
次に、finalLambertのColorを黒に設定します。そしてIncandescenced(白熱光)の右にあるチェックアイコンをクリックして、Rampシェーダーを接続します。なぜRampシェーダーをColorに設定しないのかというと、Colorだと、Lambertシェーディングによるなだらかな陰影がついてしまうためです。Incandescenced(白熱光)なら一様の明るさになるので、トゥーンシェーディングに向いています。
Rampシェーダーで、InterpolationをNoneに設定します。そして、以下の画像のように、ハイライトの白と、明るいテクスチャと暗いテクスチャを設定してください。
(本来は、テクスチャもグラデーションがついていない方がいいんですが、今回は横着してモデモデ様のテクスチャをそのまま使わせていただきます^^;)
3. Lambertマテリアルをもう一つ作り、Rampシェーダーに接続する
さて、Rampシェーダーには何らかの値を接続しなければなりません。Rampシェーダーの各しきい値は何を基準にしたらいいと思いますか? そう、モデルが反射する光の輝度がよいでしょう。その為には、Lambert反射モデルが必要なので、ハイパーシェードで、新たにLambertマテリアルを一つ新規作成します。
作成したら、作成したLambertマテリアルの名前を「simpleLambart」とでもしておきましょう。あ、Rampシェーダーも分かりやすいように、名前を「toonRamp」とでもしておきましょうか。さて、次に接続です。
ノードエディターかハイパーグラフ(接続)を開きます(私はハイパーグラフが慣れているので、以後のキャプチャはハイパーグラフで)。
そして、simpleLambertとtoonRampが表示されている状態にします。
そして、simpleLambertのoutColorRアトリビュートをtoonRampのvCoordアトリビュートに接続します。
そして、simpleLambertのcolorがデフォルトだと若干暗いので、明るめに設定します。
さて、ここでレンダリングしてみましょう。
ちゃんとテクスチャが映りましたね。テクスチャ自体にグラデーションがかかっているのでアレですが、明るい方のテクスチャと暗い方のテクスチャが、ちゃんとsimpleLambertの反射輝度とtoonRampのしきい値に応じて、うまく切り替わっているのがわかるかと思います。
4. 3D Paint Toolを利用する
さて、ここまでが準備でした。いよいよ、ブラシによる調整に入ってきます。まず、モデルを選択して、メニューセットRendering
、Texturing
メニューの3D Paint Tool
のオプションを選択します。
すると、左側に3D Paint Tool
のTool Setting画面が出てきます。ここで、File Textures
のAttribute to paint:
のプルダウンをクリックし、Translucence
アトリビュートを選択します。なぜこのアトリビュートにしたかというと、今回の場合、このアトリビュートをテクスチャペイントでいじっても、finalLambertのレンダリング結果には影響を与えないアトリビュートだからです。
次に、Assgin / Edit Textures
ボタンを押します。Translucence
アトリビュートに接続するテクスチャのここで作成するのですが、テクスチャのサイズは、縦横それぞれ2の累乗にする必要があります。フォーマットは、今回はMaya以外からも確認しやすいようにPNGにしておきましょう。Assign/Edit Textures
ボタンを押して、ウィンドウを閉じます。
これで、テクスチャにペイントができる状態になっています。
モデルに対して、ブラシで色々塗ってみましょう。塗ったらSave Textures
ボタンを押してテクスチャを保存しましょう。あ、ちなみに塗っている様子を確認するには、ビューポートの種類を「Legacy Default Viewport」にしないといけないようです。
さて、こうして塗っても、レンダリングにはまだなんの影響もありません。ちゃんと影響をあたえるようにするために、次のステップに移ります。
5. 計算用ノードの作成、接続
さて、ここでハイパーグラフ(接続)またはノードエディターを開いてください(私はハイパーグラフが慣れているので、キャプチャはハイパーグラフです)。
早速ノードの作成と接続を……と行きたいところなのですが、まずはこのブラシによる陰影の調整の考え方から説明したいと思います。
まず、ベースとしてsimpleLambertによるランバートシェーディングによる反射輝度がありますね(現在はそれがtoonRampシェーダーのvCoordにつながっています)。この輝度に、3D Paint Toolでテクスチャに描いたピクセル値を調整値として足せばいいのです。
そうすれば、例えば、モデルのレンダリングで、明るいテクスチャの方が適用されて欲しいのに暗いテクスチャが適用されているような場所に、3D Paint toolで白い色を塗れば、白は値としては1なので、simpleLambertによる輝度値に+1され、非常に明るい輝度になります。この値をtoonRampシェーダーのvCoordに繋げばいいのです(実際は、+1なんてしてしまうとvCoordの0~1という正規範囲を超えてしまい正しくない結果になってしまうので対策が必要なのですが、それは後述します)。
ただ、単純にテクスチャーのピクセル値を足してしまうと、暗い部分を明るくすることはできますが、明るい部分を暗くすることができません。
そこで、計算ノードを上手く使って、以下のようにするのです。
- テクスチャの値を-0.5する。すると…
- テクスチャの値がグレー(0.5)の場合は、0になり、simpleLambertの輝度値には0が足されるだけで、結果に影響を与えない。
- テクスチャの値が白い(0.5より大きい)場合は、0より大きい値がsimpleLambertの輝度値に足され、暗い場所を明るく調整できる。
- テクスチャの値が暗い(0.5より小さい)場合は、0より小さい(つまり、マイナスの)値がsimpleLambertの輝度値に足され、明るい場所を暗く調整できる。
さて、ここまで理解できたら、あとはノードの作成・接続に入りましょう。
まず、ハイパーシェードを開いて、UtilitiesのカテゴリからVector Product
ノード(日本語モードではベクトル積
という表記です)を作成します。
そして、3D Paint Toolで作成したFileノード(私の例ではfile3です)のoutColorRと、Vector Productノードのinput1Xを接続します。
次に、Vector Productノードを選択して、アトリビュートエディタ―で、Vector Product Attributes
のところを、以下の画像のように設定します。
皆さん、内積はご存知ですよね? ベクトルはご存知ですよね? え、知らない? え、えーとですね(汗)
ベクトルとは、いくつかの値の集まりです(Mayaの場合は、x, y, zの3成分の集まりの場合が多いです)
で、内積はですね。2つのベクトルInput1、Input2があったとすると、
内積結果 = Input1.x * Input2.x + Input1.y * Input2.y + Input1.z * Input2.z
という結果になります。
で、また下の画像に注目していただくと、Input1のx成分が黄色になっていますが、これが先ほどFileノードのoutColorRアトリビュートをここに接続したためです。「0.000」となっていますが、接続した場合ここの値は参考にならないので無視してください。
で、x,y,z成分ごとに見てみると
-
Input2のx成分が1になっていますね。これは、Input1のx成分(FileノードのoutColorRアトリビュート)の値を1倍しますという意味です。つまり、1倍だからなにもしないわけです。
-
Input1のy成分は0.5になっています。Input2のy成分は-1ですね。この2つは掛け算されるので、-0.5になります。
-
Input1およびInput2のz成分はどちらも0なので、無視していいです。
で、1,2,3で説明した各値を足します。これが内積計算です。
難しかったですか? つまり、結局なんなのかというと、FileノードのoutColorRアトリビュートの値を問答無用で-0.5しているだけなのです。内積コワクナイヨー。
なんでこんな内積ノードを使っているかというと、Mayaは数学関連のユーティリティノードが貧弱で、足し算・引き算に内積を使わざるをえないからです。
(+|- Average
ノードが使えるんじゃない? という方もいらっしゃるかと思いますが、逆に私はこのノードに何故か馴染めませんでした。できるかもしれませんね。内積がどうも受け付けない、という人はトライしてみてください)
さて、これで、テクスチャペイントの値がグレーの場合が0という、グレーが基準の値になりました。で、テクスチャペイントの値が真っ白(1)の場合は、0.5引くので、0.5がsimpleLambertの値に足されることになります。もしかしたら、もっと大きく調整したい(0.5と言わず、白のときは0.75くらい足したい)という要望があるかもしれません。そこで、ここまでの値をスケールするノードを作ります。
ハイパーシェードで、UtilitiesのカテゴリからMultiply Divide
ノード(日本語モードでは乗算除算
という表記です)を作成します。
そして、vectorProduct1ノードのoutputXとmultiplyDivide1ノードのinput1Xを接続します。
そして、multiplyDivede1ノードを選択して、アトリビュートエディタでMultiply-Devide Attributs
のところを以下の画像のように設定します。
Input1のx成分が黄色になっているのは、vectorProduct1ノード(テクスチャペイントのピクセル値を-0.5するノード)のoutputXがここに接続されているためです。
で、大事なのはInput2のx成分です。この値が、vectorProduct1ノードのoutputXの値に掛け算されて、その結果が出て行くわけです。
ここでは、1.5としたので、例えばテクスチャペイントの値が真っ白(ピクセル値:1)だとすると、-0.5されたあとに1.5倍されるので、0.75となり、simpleLambertの輝度値に0.75が足されることになります。まぁ、この1.5という値は、お好みで変えて構いません。1.0でも0.7でも1.75でも2.0でもいいでしょう(2.0以上はおすすめしません。計算してみれば、わかりますね?)
さて、次は、ここまでノードをつないできた値と、simpleLambertの輝度値をいよいよ足し合わせます。
ハイパーシェードから、Vector Productノードを作成してください。
そして、multiplyDivide1のoutputXとvectorProduct2のinput1Yを接続し、
次に、simpleLambertのoutColorRをvectorProduct2のinput1Xに接続します。
vectorProduct2のVector Product Attributes
のところを、以下の画像のように設定します。内積の計算の方法を思い出していただきたいのですが、つまり、こうすることで、simpleLambertのoutColorR(simpleLambertの反射輝度値のR成分)とmultiplyDivide1のoutputX(テクスチャペイント値を-0.5して1.5倍した値)を単純に足し合わせることになります。
さて、いよいよこのvectorProduct2の出力をRampシェーダーのvCoordに接続……ちょっと待ってください。今のままだと、例えばsimplerLambertの輝度がすでに明るい(たとえば0.8)場所に、テクスチャペイント値で明るい色(たとえば0.9)を塗った場合、合計が1.7となり、Rampシェーダーのv座標の最大値である1.0を超えてしまい、変な結果になってしまいます。
そのため、vectorProduct2の出力をクランプ(1.0より大きければ1.0にし、0.0未満なら0.0にする)する必要があります。
ハイパーシェードでUtilitiesのカテゴリからSet Range
ノード(日本語モードでは範囲設定
という表記です)を作成します。
そして、vectorProduct2のoutputXをsetRange1のvalueXに接続します。
そして、setRange1のSet Range Attributes
のところを、以下の画像のように設定します。これで、Valueのx成分が1.0より大きければ1.0になり、0.0未満なら0.0になります。
さて、今度こそ、setRange1の出力(outValueX)をtoonRampのvCoordに接続します(すると、自動的にそれまでのsimpleLambertのoutColorRとtoonRampのvCoord間の接続は外れます)。
6. さっそくブラシで調整してレンダリングしてみよう!
さて、では早速試してみます。その前に、まず3D Paint Toolで、Flood Paint
を使って、テクスチャ全体をグレー(0.5)で塗りつぶしましょう。グレー(0.5)が基準ですからね。
この状態(未調整)でのレンダリング画像が以下です。
さて、ここからブラシ調整していきます。まず、向かって右側の顔の中途半端に明るい部分を、暗くしたいとします。
その場合は、Color
で暗い色を選んで、該当箇所を塗ります。
Save Texture
ボタンを押して、再びレンダリングすると……。
見事に調整できました。では、向かって左側の髪の毛の先端が暗いですね。ではここを明るめの色で塗ると……。
見事に、髪の毛の先端が明るくなりました!
ちなみに、真っ白になっている部分は、Rampシェーダーで設定した真っ白のハイライトの部分ですね。まぁ、適当にやってるんで見てくれ悪いですが^^;
7. Maya Toonでラインを出す
さて、今度はラインの太さの制御をやってみましょう。まずは、ラインを出さないといけません。Maya Toon機能のラインを使いましょう。
モデルを選択した状態で、Renderingメニューセット、Toon
メニューのAssign Outline
-> Add New Toon Outline
を選択します。すると、pfxToonノードが作成され、モデルに黒いラインが出てきたのが分かるかと思います。
pfxToonノードを選択して、アトリビュートエディタで設定を行います。ここではただ、ラインの色が黒だと目立たないので、Profile Lines
、Crease Lines
、Boarder Lines
それぞれの色をマゼンタ色にしているだけです。
8. 3D Paint ToolのテクスチャとMaya Toon Lineを結ぶ
さて、ここからはもう想像つきますね?
モデルを選択して、メニューセットRendering
、Texturing
メニューの3D Paint Tool
のオプションを選択します。
すると、左側に3D Paint Tool
のTool Setting画面が出てきます。ここで、File Textures
のAttribute to paint:
のプルダウンをクリックし、今度はDiffuse
アトリビュートを選択します。なぜこのアトリビュートにしたかというと、今回の場合、このアトリビュートをテクスチャペイントでいじっても、finalLambertのレンダリング結果には影響を与えないアトリビュートだからです。
次に、Assgin / Edit Textures
ボタンを押します。Diffuse
アトリビュートに接続するテクスチャのここで作成するのですが、テクスチャのサイズは、縦横それぞれ2の累乗にする必要があります。フォーマットは、今回はMaya以外からも確認しやすいようにPNGにしておきましょう。Assign/Edit Textures
ボタンを押して、ウィンドウを閉じます。
そして、Flood Paint
を使って、値0.5のグレーでテクスチャ全体を塗りつぶしておきましょう。
さて、次に、ノードエディタまたはハイパーグラフ(接続)を使って、3D Paint Toolで作成したテクスチャ(今回の例ではfile4)のoutColorRとpfxToonShape1(pfxToon1でないことに注意)のlineWidthMapを接続します。
これで、完了です。
9. ブラシで塗り塗りしてライン幅を調整してみよう!
さて、さっそく試してみましょう。まず、初期状態です。
(Flood Paint
を使って、テクスチャ全体を0.5のグレーに塗りつぶしてあります)
ここで、例えば前髪のラインを消して、ネクタイのラインを太くしてみたいと思います。どうすればいいかは、もうわかりますね?
はい。このとおり(この例、調整というか、なんかめちゃくちゃにしているだけのような気もするw)
テクスチャペイント値を、上手くグラデーションさせることで、ラインのいわゆる「入り」と「抜き」を表現することも可能です。
また、今回は直接テクスチャとpfxToonShapeのlineWidthMapを接続していますが、もちろん、間に値をスケールするMultiplyDivideノードを挟んだり、色々やって良いと思います。そこは、お好みでどうぞ。
応用編:アニメーション対応
さて、できました……が、これって、アニメーション対応はどうなんでしょうか?
対策としては、Fileノードのイメージシーケンス機能を使うことです。
FileノードのUse Image Sequenceをチェックしてください。そして、テクスチャファイル名を以下のように、拡張子の前に.#.
としてください。#
の部分に、現在のフレーム数が入り、それらのテクスチャにフレームごとにアクセスが行きます。
用意するテクスチャファイル名は、この場合mikuShape_diffuse.001.png、mikuShape_diffuse.002.pngのように、#の部分を3桁の数字にしたファイル名にしてください。
(注意:現状のMayaのバージョン(2015)では、テクスチャファイル名に#
が入っている状態では、正常にテクスチャペインティングができないようです。テクスチャペインティングする場合は、いったんテクスチャファイル名を実在するテクスチャファイル名に指定しなおす必要があるかもしれません。)
えー!? フレームごとに3Dテクスチャペイントしないといけないの!? とお思いになるかと思います。
そこで、私が考えているのがエクスプレッションです。イメージシーケンスを有効にしたFileノードに、エクスプレッションノードが接続されていると思います。
これをうまく書き換えてですね。キーとなる2つのフレームのテクスチャカラー値を取り出して、現在のフレーム番号でその2つを線形補間してやるというやり方が考えられます。
そうすることで、全てのフレームにテクスチャを用意する必要はなくなり、しかもキーフレーム間でテクスチャ値の補間が行われます。
具体的なコードは……まだやってないので宿題とさせてください。一応、フレーム間のテクスチャ値の補間までは私の手元ではすでにできています。みなさんもぜひチャレンジしてみてね。
さて、まぁこれで、なんとかアニメーション対応もできるのではないか……と思うのですが、どうでしょう? 苦しい?^^;
まとめ
以下、まとめです。
- 3D Paint ToolとMayaのノードを組み合わせることで、ブラシで塗り塗り調整が可能
- 基本的には、静止画のレンダリングに対応。
- アニメーション対応は、イメージシーケンス機能とエクスプレッションを駆使すれば可能……か?
ホントを言えば、モデルのメッシュに追加の頂点アトリビュートをブラシで塗り塗り設定できる方法があれば、いちいちテクスチャペインティングなんてしなくてもいいんですけどね。アニメーション対応も、普通にMayaデフォルトのキーフレーム補間が効くはずだし。
OLMさんの例のアレは、どうやって実装しているのかな……。
まぁいいや。この方法、ノードの組合せいかんでいかようにも改善できると思いますので、皆様色々いじってみてください。
あと、気になるのが、この方法が果たして実用的なのか否か。この点についても皆様のご意見を頂戴したいところでございます。
また、ノードを組むのがめんどくさい、ということなら、Pythonスクリプトでノードの構築を自動化して解決できるかもしれません。
では、このへんで。皆様ごきげんよう(@^^)/~~~