Anime Toolbox の Visual Compositor 拡張 - カスタムエフェクト適用編
本記事について
2022年12月にAnime Toolboxを一般公開してから、「Visual Compositor を拡張できるの?」という疑問をチラホラ聞きました。これはカスタムのエフェクトなどをかけるための新しいノードを作るということであれば、「はい、拡張できます」が答えです。
実際の例で紹介した方が伝わりやすいと思うので、本記事ではグレースケールに変換するノードの書き方を紹介したいと思います。
事前準備
まず、まだの方は AnimeToolbox を手に入れましょう。AnimeToolbox のインストールなどは「今日からはじめるAnime Toolbox 第2回『Anime Toolboxを手に入れよう!』」詳しく説明されていますので、ご参照ください。
モデルの準備
次はモデルの準備です。学習に使うモデルはなんでも良いのですが、せっかくなので、今回は「今日からはじめるAnime Toolbox第4回『「歩くSDユニティちゃん」のシーンをつくってみよう!』」に使われているSDユニティちゃんを使用します。SDユニティちゃんのインストールのやり方は上記の記事に詳しく書いてありますので、是非ご参照ください。
シーンの準備
次にシーンを準備します。まず、上記のステップで準備していた SDユニティちゃんのプレハブをシーンにドラッグアンドドロップし、モデルの位置や回転などを調整します。
次にヒエラルキーウィンドウに「空のゲームオブジェクト」を生成し、「Compositor」と名前を変えましょう。
次にヒエラルキーウィンドウに Compositor ゲームオブジェクトをクリックし、インスペクターウィンドウで、「コンポネントを追加」ボタンより Visual Compositor コンポネントを追加しましょう。
Visual Compositor コンポネントを追加した後、インスペクターウィンドウの右横のスクロールバーを下に下げ、Visual Compositor コンポーネントの中のCompositor Graph という項目の右端にある「New」ボタンをクリックします。
準備の確認
最後にシーンの準備ができているかどうかを確認します。ゲームビューのDisplay 選択を Visual Compositor がデフォルトで出力する 「Display 8」にセットします。
次に Compositor ゲームオブジェクトをクリックし、メニューから「ウィンドウ > レンダリング > Visual Compositor」をクリックして、Visual Compositor ウィンドウを開きます。
ゲームビューと Visual Compositor ウィンドウの中身が下記のようになっていれば、準備は完了しました。
グレースケールに変換するシェーダー
いよいよカスタムノードの作成に入ります。最初、プロジェクトウィンドウで右クリックし、「作成 > シェーダー > Unlit シェーダー」を選択します。そこで NewUnlitShader というシェーダーが作成されますが、名前を GrayScale に変えましょう。
次にシェーダーを開き、下記のコードで上書きしましょう。Unity で Cg 言語も HLSL 言語も使えますが、今回はもともと Unity に使われている Cg 言語を使います。
Shader "Custom/GrayScale" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy;
return o;
}
float4 frag(const v2f i) : SV_Target {
const float4 c = tex2D(_MainTex, i.uv);
float gray = dot(c.rgb, float3(0.299, 0.587, 0.114));
return float4(gray, gray, gray,c.a);
}
ENDCG
}
}
}
バーテックスシェーダーとフラグメントシェーダーの部分を少々説明します。今回作りたいエフェクトは色付きの入力 (テクスチャ)をグレースケールに変換するエフェクトなので、バーテックスシェーダーには特に変換を行わず、そのまま流れた値を出力にセットします。
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy;
return o;
}
次にフラグメントシェーダーでは、UV 座標で入力 (テクスチャ)のRGBの色を取り、その色をグレースケールに変換します。RGB の値の平均を計算するのも良いですが、各色の要素の対して、人が認識する明るさは違うと言われているので、(0.299 * R + 0.587 * G + 0.114 * B) という式を使います。
float4 frag(const v2f i) : SV_Target {
const float4 c = tex2D(_MainTex, i.uv);
float gray = dot(c.rgb, float3(0.299, 0.587, 0.114));
return float4(gray, gray, gray,c.a);
}
シェーダーについてもうちょっと詳しく知りたい方は Unity が用意した「シェーダを理解しよう」の学習マテリアルをお勧めします。
カスタムノード用の C#スクリプト
次にカスタムノード用の C#スクリプトを作成します。プロジェクトウィンドウで右クリックし、「作成 > シェーダー > C# スクリプト」を選択した後、できたファイルの名前を GrayScaleNode にしましょう。
デフォルトとして作成される C# スクリプトはコンポーネント用のスクリプトになりますので、Anime Toolbox の Visual Compositor のノードを作るのには、まずファイルを開いてから、CompositorNode を継承した下記のクラスでファイルの中身を上書きしましょう。
using UnityEngine;
using Unity.VisualCompositor;
public class GrayScaleNode : CompositorNode {
public override void Render() { }
[InputPort(name: "input")] private RenderTexture m_input;
[OutputPort(name: "output")] private RenderTexture m_output;
}
CompositorNode を継承したクラスを作ると、自動的に Visual Compositorのコンテキストメニューにそのクラスが登録されます。試しに Visual Compositor ウィンドウの何もない背景部分で右クリックし、新しく登録された GrayScaleNode を選択しましょう。
GrayScaleNode の中に input という入力ポートとoutput という出力ポートがついています。これらはコード中にある InputPort と OutputPort の定義で作られます。複数のポートを必要とするノードを作る場合、新たな InputPort と OutputPort を追加すれば良いですが、今回の GrayScaleNode では、それぞれが1つになります。
次に、Render() 関数の中に GrayScaleNode が実行する処理を書きます。まず、m_output の出力用の RenderTexture を作ることから始めます。この RenderTexture はシーンに保存する必要はないので、HideFlags.DontSaveInEditor を使います。HideFlags についてもうちょっと詳しく知りたい方はマニュアルをご参照ください。
public override void Render() {
//出力用の RenderTexture を作る
if (null == m_output) {
m_output = new RenderTexture(m_input);
m_output.hideFlags = HideFlags.DontSaveInEditor;
}
}
次に、本記事で作ったシェーダーでマテリアルを動的に生成し、そのマテリアルを入力に適用します。上記の m_output の RenderTexture と同様、この マテリアルはシーンに保存する必要はないので、HideFlags.DontSaveInEditor を使います。
public override void Render() {
//出力用の RenderTexture を作る
..
//マテリアルを生成し、適用する
if(m_material == null) {
Shader shader = Shader.Find("Custom/GrayScale");
m_material = new Material(shader);
m_material.hideFlags = HideFlags.DontSaveInEditor;
}
Graphics.Blit(m_input, m_output, m_material);
}
private Material m_material;
最後に、入力ポートが外されたら、出力用の RenderTexture をクリアしないと、入力ポートが繋がった時の処理結果が残ってしまうので、ここにクリアの処理を入れます。
public override void Render() {
//出力用の RenderTexture をクリアするかどうかのチェック
if (null == m_input) {
if (null != m_output) {
ClearRenderTexture(m_output);
}
return;
}
//出力用の RenderTexture を作る
..
//マテリアルを生成し、適用する
..
}
private static void ClearRenderTexture(RenderTexture rt) {
RenderTexture prevRT = RenderTexture.active;
RenderTexture.active = rt;
GL.Clear(true, true, Color.clear);
RenderTexture.active = prevRT;
}
参考のため、GrayScaleNode のフールソースコードを添付します。
using UnityEngine;
using Unity.VisualCompositor;
public class GrayScaleNode : CompositorNode {
public override void Render() {
if (null == m_input) {
if (null != m_output) {
ClearRenderTexture(m_output);
}
return;
}
if (null == m_output) {
m_output = new RenderTexture(m_input);
m_output.hideFlags = HideFlags.DontSaveInEditor;
}
if(m_material == null) {
Shader shader = Shader.Find("Custom/GrayScale");
m_material = new Material(shader);
m_material.hideFlags = HideFlags.DontSaveInEditor;
}
Graphics.Blit(m_input, m_output, m_material);
}
private static void ClearRenderTexture(RenderTexture rt) {
RenderTexture prevRT = RenderTexture.active;
RenderTexture.active = rt;
GL.Clear(true, true, Color.clear);
RenderTexture.active = prevRT;
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
[InputPort(name: "input")] private RenderTexture m_input;
[OutputPort(name: "output")] private RenderTexture m_output;
private Material m_material;
}
GrayScaleNode を繋げましょう
Visual Compositor ウィンドウで RenderingNode の出力を GrayScaleNodeの input にセットし、GrayScaleNode の output を LayerNode にセットしたら、完成!
ランタイムのビルドで表示したい場合の設定
エディターでの出力だけが必要であれば、このセクションを飛ばしても構いませんが、Unity でランタイムビルドを作って、ビルドでカスタムノードを見たい場合はちょっとした追加の設定が必要です。
まずメニューから 「編集 > プロジェクト設定」を選択しましょう。そこで、左のメニューにグラフィックスを選択して、「常に含まれるシェーダー設定」のところに本記事のカスタムノードが使う「Custom/GrayScale」のシェーダーを追加しましょう。
この設定の必要な理由はランタイムビルドを作る時、最適化のため不要だと判断されるシェーダーはランタイムビルドから外されます。本記事の C# スクリプトはカスタムシェーダーを使って動的にマテリアルを作成しているため、この設定がないとランタイムビルドを作る時にそのカスタムシェーダーが不要だと判断され、ランタイムビルドでエフェクトが正しく表示されません。
カスタムノードを配布したい場合
さて、自分が作ったカスタムノードがどんなにすばらしくても、他の人に共有できなかったら少々盛り上がりません。実は、共有するためには下記の4つのファイルを配布するだけで、とても簡単です。
シェーダー
シェーダーのメタデータ
C# スクリプト
C# スクリプトのメタデータ
本記事の GrayScaleNode の配布版は AnimeToolbox の Github レポにアップされましたので、良ければご参照ください。
注意点として、ほかの人が配布したノードを自分用にカストマイズしたい場合、後々のアセットの衝突を回避するため、Unity にインポートする前にメタデータのファイルを削除しましょう。メタデータについてもうちょっと詳しく知りたい方は、マニュアルをご参照ください。
まとめ
本記事では、カスタムノードの作り方を纏めてみました。基本的に必要なステップが 2つだけです。
シェーダーを作る
CompositorNodeを継承した C# のクラスを作り、その中に上記のシェーダ使ったマテリアルを適用するコードを書く
また、Visual Compositor パッケージの中で様々なノードが入っていますので、カスタムノードを書く時にこれらのノードを参考にするのが良いでしょう。これらのノードはプロジェクトウィンドウの「Packages/Visual Compositor」の下にある Nodes と Shaders の中に確認できます。
最後に
いかがだったでしょうか?何かご意見やご感想等ございましたら、Twitterにて#AnimeToolbox のハッシュタグを付けていただいて、ツィートしていただければ嬉しいです。
では、コミュニティーの皆様が面白いノードを作ってくださるのを楽しみにしています!
この記事が気に入ったらサポートをしてみませんか?