AnyPortrait > マニュアル > 「Command Buffer」の作成
Unityエンジンではレンダリングは自動的に行われますが、スクリプトを作成して機能を拡張することもできます。
これを行うためのさまざまな方法の1つは、「コマンドバッファ(Command Buffer)」を使用することです。
「コマンドバッファ」を簡単にまとめると、「対象のメッシュを指定された条件でレンダリングする要求をスケジュールすること」です。
Unityの公式マニュアルで詳細な説明を確認できます。
- ビルトインレンダーパイプラインのコマンドバッファによる拡張
- スクリプタブルレンダーパイプラインにおけるレンダリングコマンドのスケジューリングと実行
- CommandBuffer API
- CameraEvent API
Unityの「CommandBuffer」クラスを使用すると、AnyPortraitのキャラクターをコマンドバッファを介してレンダリングできますが、ユーザーがアクセスしにくいデータが多いので、それは実装するのが難しいです。
「AnyPortrait v1.4.0」に追加された「apCustomCommandBuffer」クラスは、「CommandBuffer」クラスのAnyPortrait用のバージョンです。
このクラスを使用すると、さまざまなテクニックを簡単に実装できます。
このページでは、「apCustomCommandBuffer」を使用してスクリプトを作成する方法と、いくつかの面白いテクニックを実装する方法について説明します。
「apCustomCommandBuffer」クラスの関数の詳細については、関連ページを参照してください。
実装に先立ち、コマンドバッファの配信方法を簡単に理解してみましょう。
スクリプトがコマンドバッファを作成してUnityのレンダリングパイプラインに渡すと、レンダリングパイプラインが動作しているときにスケジュールされたコマンドバッファの要求に従って追加のレンダリングが実行されます。
このときに渡さなければならない必須要素は、「メッシュ」と「カメラ」です。
この点を覚えて実装をしましょう。
コマンドバッファを使用する最も簡単で簡単な例を作成しましょう。
「カメラ(Camera 1)」が「キャラクターのメッシュ(Original Meshes)」をレンダリングする環境を用意します。
そして別の「カメラ(Camera 2)」で「コマンドバッファ」によって「キャラクター(Cloned Meshes)」が見えるようにしましょう。
コマンドバッファを作るために必要な要素として「Original Meshes」と「Camera 2」を記憶しましょう。
説明に合わせてUnityシーンを設定しました。
(1) 2つのカメラと1つのAnyPortraitキャラクターが存在することがわかります。
(2) AnyPortrait キャラクターです。
(3) キャラクターを描くメインカメラです。 先の図式での「Camera 1」と同じ役割を果たします。
(4) メインカメラがレンダリングを終了すると追加レンダリングを行うオーバーレイ用カメラです。 前の図式で「Camera 2」と同じ役割を果たします。
各カメラの設定です。
「Camera 1」は、メインカメラとして最初にレンダリングするように設定されています。
「Camera 2」は「Depth」の値によって「Camera 1」より後でレンダリングされ、「Culling Mask」によってキャラクターがレンダリングされません。
したがって、ゲームを実行すると、キャラクターは「メインカメラ」でのみ描画されます。
それでは、コマンドバッファを作成して更新する簡単なスクリプトを作成しましょう。
次のスクリプトは、「入力されたapPortraitキャラクタ」を「入力されたカメラ」に描画させる非常に簡単な役割を果たします。
using UnityEngine;
using AnyPortrait;
public class CmdBufferExample : MonoBehaviour
{
// 対象となるオブジェクト
public apPortrait portrait;
public Camera targetCamera;
// コマンドバッファ
private apCustomCommandBuffer _commandBuffer = null;
void Start()
{
// コマンドバッファを生成します。
if (_commandBuffer == null)
{
// apPortraitが初期化されていない場合は、ここで直接初期化します。
portrait.Initialize();
// apPortraitとカメラを使用してコマンドバッファインスタンスを作成します。
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
// レンダリング時点を設定して、カメラにコマンドバッファを登録します。 (ビルトインレンダーパイプライン用関数)
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.AfterForwardAlpha);
}
}
void OnDestroy()
{
// コマンドバッファを削除します。
if (_commandBuffer != null)
{
_commandBuffer.Destory();
_commandBuffer = null;
}
}
void LateUpdate()
{
// 更新ごとにレンダリング内容を再更新します。
if (_commandBuffer != null)
{
// コマンドバッファの内容を空にして書き換えます。
_commandBuffer.ClearCommands();
// カメラのMatrixをコマンドバッファに入力します。 (Unity 2019以降のバージョンで呼び出す必要がある関数)
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
// apPortraitのメッシュをレンダリングするように要求します。
_commandBuffer.DrawAllMeshes(true, true);
}
}
}
スクリプトの重要な構文を1つずつ見てみましょう。
// (1) 対象となるオブジェクト
public apPortrait portrait;
public Camera targetCamera;
// (2) コマンドバッファ
private apCustomCommandBuffer _commandBuffer = null;
メンバーオブジェクトです。
(1) コマンドバッファの必須要素であるapPortraitとカメラをUnityシーンから接続できるようにpublic変数にします。
(2) 最も重要なコマンドバッファを「apCustomCommandBuffer」型の変数を使用して生成および制御します。
// (1) apPortraitが初期化されていない場合は、ここで直接初期化します。
portrait.Initialize();
// (2) apPortraitとカメラを使用してコマンドバッファインスタンスを作成します。
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
// (3) レンダリング時点を設定して、カメラにコマンドバッファを登録します。 (ビルトインレンダーパイプライン用関数)
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.AfterForwardAlpha);
コマンドバッファを生成してレンダリングパイプラインに登録するコード。
(1) 例では「Start」関数で初期化が実行するように作成したため、apPortrait が初期化されていない可能性があり、初期化コードを作成しました。 必須コードではありません。
(2) コマンドバッファインスタンスを作成するときは、カメラとapPortraitを引数として入力し、コマンドバッファの名前を追加します。
(3) 「AddToCamera」関数を使用して、レンダリングパイプラインにコマンドバッファを登録します。
このとき、レンダリング過程でどの時点でコマンドバッファの要求が処理されるかを設定します。
プロジェクトの環境が「Scriptable Render Pipeline (SRP)」の場合は、「AddToCameraSRP」をこの関数の代わりに使用する必要があります。
_commandBuffer.Destory();
コマンドバッファを構成する要素である「キャラクター」、「カメラ」、「コマンドバッファを管理するこのスクリプト」のいずれかがシーンに存在しなくなる場合は、上記のコードを必ず呼び出してコマンドバッファをレンダリングパイプラインから削除する必要があります。
// (1) コマンドバッファの内容を空にして書き換えます。
_commandBuffer.ClearCommands();
// (2) カメラのMatrixをコマンドバッファに入力します。 (Unity 2019以降のバージョンで呼び出す必要がある関数)
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
// (3) apPortraitのメッシュをレンダリングするように要求します。
_commandBuffer.DrawAllMeshes(true, true);
コマンドバッファの要素に変更がある場合は、コマンドバッファの内容を再作成する必要があります。
「AnyPortrait」は各フレームごとにメッシュが更新されるため、「Update」または「LateUpdate」関数でコマンドバッファの内容を書き換える必要があります。
(1) コマンドバッファの内容を作成するには、既存のバッファ内容を削除する必要があります。
(2) レンダリングする「View-Projection Matrix」をコマンドバッファに入力します。 「Unity 2019」より前のバージョンではこの関数はサポートされていません。
(3) apPortrait内でどのメッシュをレンダリングするかをコマンドバッファに入力します。 「DrawAllMeshes」は、可能な現在表示されているすべてのメッシュをレンダリングするように要求します。
作成したスクリプトをシーンに入れましょう。
(1) 新しい「GameObject」を作成します。
(2) 作成したスクリプトをコンポーネントとして登録し、「apPortraitキャラクター」と「オーバーレイカメラ(Camera 2)」をそれぞれ入力します。
もう一度ゲームを実行すると、コマンドバッファによって2番目のカメラでもキャラクターが描かれていることがわかります。
AnyPortraitは、クリッピングメッシュをレンダリングするときにコマンドバッファを使用してマスクテクスチャを作成します。
ユーザーが作成したコマンドバッファによってレンダリングされると、対応するマスクテクスチャが正しく生成されない可能性が高くなります。
したがって、クリッピングされるメッシュは互換性が悪いため、可能なコマンドバッファによるレンダリングでは除外することをお勧めします。
コマンドバッファの内容を作成するコードを少しだけ変更しても、レンダリング結果は大きく異なります。
試してみて、次のようにコードを一行追加し、変わった結果を確認しましょう。
(省略)
void LateUpdate()
{
if (_commandBuffer != null)
{
_commandBuffer.ClearCommands();
// レンダリング先の色または奥行き値を初期化します。
_commandBuffer.ClearRenderTarget(true, true, Color.magenta);
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
_commandBuffer.DrawAllMeshes(true, true);
}
}
「オーバーレイカメラ(Camera 2)」でコマンドバッファによってキャラクタがレンダリングされる直前にレンダリングターゲット、つまり画面の色を紫色(Magenta)で初期化したため、上記のような結果が見られます。
コマンドバッファを使用することは、主に特殊なレンダリング効果を作成するためです。
レンダリング効果を作成するには、別々のマテリアルをコマンドバッファで利用できる必要があります。
今回はコマンドバッファでキャラクターをグレースケールでレンダリングするように実装しましょう。
今回は、キャラクターをグレースケールでレンダリングする「カスタムシェーダ」と、前述の「コマンドバッファスクリプト」を作成する必要があります。
まず、次の「カスタムシェーダ」を新しく作成します。
Shader "Custom Command Buffer Example/Grayscale"
{
Properties
{
_MainTex("Main Texture (RGBA)", 2D) = "white" {}
_Color("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// RGB色の平均値を使用して単色にします。
col.rgb = (col.r + col.g + col.b) / 3.0f;
return col;
}
ENDCG
}
}
}
前述のコマンドバッファスクリプトを変更または新しく作成します。
今回は外部のマテリアルをインポートしてレンダリングに使用するように作成する必要があります。
using UnityEngine;
using AnyPortrait;
public class CmdBufferExample : MonoBehaviour
{
public apPortrait portrait;
public Camera targetCamera;
// 別のマテリアルを適用できるように変数を追加します。
public Material alternativeMaterial;
private apCustomCommandBuffer _commandBuffer = null;
void Start()
{
if (_commandBuffer == null)
{
portrait.Initialize();
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.AfterForwardAlpha);
// 別のマテリアルがレンダリングされるように、コマンドバッファ変数内にマテリアルコピーをさせる関数を呼び出します。
_commandBuffer.CreateAlternativeMaterials(alternativeMaterial);
}
}
void OnDestroy()
{
if (_commandBuffer != null)
{
_commandBuffer.Destory();
_commandBuffer = null;
}
}
void LateUpdate()
{
if (_commandBuffer != null)
{
_commandBuffer.ClearCommands();
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
// 初期化時に登録した外部のマテリアルを利用してメッシュをレンダリングするように要求します。
_commandBuffer.DrawAllMeshesWithAlternativeMaterials(true, true);
}
}
}
(1) 完成した「カスタムシェーダ」を利用する「マテリアル(Material)」アセットを作成します。
(1) 先ほど作成した「GameObject」を選択します。
(2) スクリプトの追加された変数にカスタムシェーダを利用する「マテリアルアセット」を割り当てます。
完成した結果です。
コマンドバッファによってレンダリングされると、他のマテリアルに置き換えられてレンダリングされることがわかります。
この方法を適用すると、さまざまなレンダリング手法が利用可能になります。
上記の説明をもう少し適用すると、実用的な例を作成できます。
代表的な技術は「アウトライン」です。
ここでは、UVを使ってアウトラインを描く簡単なカスタムシェーダを使って簡単に実装できます。
さらに、コマンドバッファの作成時にレンダリングされる時点を変更する必要があります。
最後に、上記の例とは異なり、この手法ではメインカメラを対象にコマンドバッファが実行されるようにします。
アウトラインを描くカスタムシェーダを作成します。
さまざまな方法でアウトラインを描画しますが、ここでは単に基準で周囲のUVでの「Alpha」値を使用して色を拡張する方法を使用します。
新しいシェーダーアセットを作成し、以下のように作成します。
Shader "Custom Command Buffer Example/Outline"
{
Properties
{
_MainTex("Main Texture (RGBA)", 2D) = "white" {}
_Color("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
// UVをサンプリングする距離を使用して、アウトラインの厚さを設定できます。
_UVThickness("Line Thickness (UV Based)", Range(0, 0.2)) = 0.01
// アウトラインの色です。
_LineColor("Line Color", Color) = (0, 0, 0, 1)
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
float _UVThickness;
half4 _LineColor;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// 周囲をサンプリングして「Alpha」値を累積します。
fixed sumAlpha = col.a;
sumAlpha += tex2D(_MainTex, i.uv + float2(_UVThickness, 0.0f)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(-_UVThickness, 0.0f)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(0.0f, _UVThickness)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(0.0f, -_UVThickness)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(_UVThickness * 0.7f, _UVThickness * 0.7f)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(_UVThickness * 0.7f, -_UVThickness * 0.7f)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(-_UVThickness * 0.7f, _UVThickness * 0.7f)).a;
sumAlpha += tex2D(_MainTex, i.uv + float2(-_UVThickness * 0.7f, -_UVThickness * 0.7f)).a;
// アウトラインの透明度と色を計算して結果に反映します。
sumAlpha = saturate(sumAlpha);
col.rgb = _LineColor;
col.a = _LineColor.a * _Color.a * sumAlpha;
return col;
}
ENDCG
}
}
}
次に、コマンドバッファスクリプトを作成します。
前の例のコードとほぼ同じように書くだけで、レンダリングの時点を変更する必要があります。
作成したカスタムシェーダは、キャラクターイメージをもう少し拡張して単色で描くコードを持つため、これがアウトラインとして機能するにはキャラクターより先に描かれなければなりません。
using UnityEngine;
using AnyPortrait;
public class CmdBufferExample : MonoBehaviour
{
public apPortrait portrait;
public Camera targetCamera;
public Material alternativeMaterial;
private apCustomCommandBuffer _commandBuffer = null;
void Start()
{
if (_commandBuffer == null)
{
portrait.Initialize();
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
// 一般的なメッシュをレンダリングする時点より前に描画されるように設定を変更します。
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.BeforeForwardAlpha);
_commandBuffer.CreateAlternativeMaterials(alternativeMaterial);
}
}
(以降のコードは前の説明と同じです。)
(1) アウトラインシェーダを適用した「マテリアル」アセットを作成します。
(2) マテリアルアセットを選択し、アウトラインの太さと色を設定します。
(1) コマンドバッファを制御する「GameObject」を選択します。
(2) アウトラインの場合、オーバーレイカメラではなく、キャラクターをレンダリングする「メインカメラ」が対象になるように設定する必要があります。
(3) アウトラインを描くマテリアルアセットを割り当てます。
ゲームを実行すると、適切なクオリティのアウトラインが描かれていることがわかります。
前の説明を通じてコマンドバッファと別のマテリアルを利用してレンダリングを行う過程を習得しました。
今回はキャラクターのイメージが2つ以上の場合に外部のマテリアルを割り当てたい場合です。
画像によって異なる「セカンダリテクスチャ」を適用してレンダリングをしたい場合は、準備する必要のあるマテリアルとコマンドバッファの初期化コードを修正する必要があります。
左の2つのテクスチャを使用するキャラクターを用意しました。
それぞれの画像に対応する右側の補助テクスチャの色が乗算されるマテリアルを作って、コマンドバッファを通してレンダリングをしましょう。
今回も同様にカスタムシェーダを作ってみましょう。
「_MainTex」プロパティはAnyPortraitによって管理されているため使用されないため、別々のテクスチャプロパティを作成する必要があります。
Shader "Custom Command Buffer Example/Secondary"
{
Properties
{
_MainTex("Main Texture (RGBA)", 2D) = "white" {}
_Color("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
// 色を掛ける補助テクスチャです。
_SecondaryTex("Secondary Texture (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
sampler2D _SecondaryTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// 補助テクスチャの色を取得して RGB に乗算する演算を行うコードです。
fixed4 secondaryCol = tex2D(_SecondaryTex, i.uv);
col.rgb *= secondaryCol.rgb;
return col;
}
ENDCG
}
}
}
コマンドバッファを制御するスクリプトを作成する前に、マテリアルを作成しましょう。
(1) 画像数に合わせてマテリアルを作成します。
(2) 「_SecondaryTex」属性にそれぞれの補助テクスチャを割り当てます。
次に、2つ以上のマテリアルをコマンドバッファに割り当ててレンダリングするスクリプトを作成しましょう。
// Dictionary 変数を利用するためのパッケージです。
using System.Collections.Generic;
using UnityEngine;
using AnyPortrait;
public class CmdBufferExample : MonoBehaviour
{
public apPortrait portrait;
public Camera targetCamera;
// 画像名とそれに合ったマテリアルを割り当てるために、それぞれ配列の形で変数を作成します。
public string[] imageNames;
public Material[] alternativeMaterials;
private apCustomCommandBuffer _commandBuffer = null;
void Start()
{
if (_commandBuffer == null)
{
portrait.Initialize();
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.BeforeForwardAlpha);
// 配列変数を使用して、「イメージ名 - 置き換えるマテリアル」を連結するDictionary変数を作成して「_commandBuffer」に渡します。
Dictionary<string, Material> imageNamesToMaterial = new Dictionary<string, Material>();
for (int i = 0; i < imageNames.Length; i++)
{
imageNamesToMaterial.Add(imageNames[i], alternativeMaterials[i]);
}
_commandBuffer.CreateAlternativeMaterials(imageNamesToMaterial, alternativeMaterials[0]);
}
}
void OnDestroy()
{
if (_commandBuffer != null)
{
_commandBuffer.Destory();
_commandBuffer = null;
}
}
void LateUpdate()
{
if (_commandBuffer != null)
{
_commandBuffer.ClearCommands();
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
_commandBuffer.DrawAllMeshesWithAlternativeMaterials(true, true);
}
}
}
(1) 2つのイメージを使用するキャラクターが登場するユニティシーンです。
(2) Unity シーンは前述の説明と同じです。 スクリプトが追加された「GameObject」を選択します。
(3) apPortrait キャラクターが使用するイメージの名前と、それに合ったカスタムシェーダのマテリアルを順番に割り当てます。
ゲームを実行すると、メッシュの画像に対応する代替マテリアルが適切に適用され、上記の結果を見ることができます。
この問題は、複数のユーザーから要求を受けた内容です。
キャラクターを画面ではなく「レンダーテクスチャ(Render Texture)」を対象にレンダリングを行うと、レンダリングテクスチャの背景を透明にするとメッシュが消える問題が発生します。
レンダリングテクスチャでキャラクターをレンダリングする方法については、関連ページで詳細な説明をご覧ください。
このページでは、レンダリングテクスチャでレンダリングを行う際の問題をコマンドバッファで解決する方法を紹介しながら、「レンダリングターゲット」を制御するスクリプトについて説明します。
キャラクターをレンダリングした結果をレンダリングテクスチャに保存した後、これを別のメッシュに塗りつぶしてレンダリングする方法の概略です。
キャラクターがレンダリングされたレンダーテクスチャは、ゲーム内で多様に活用できるため、よく使われる手法です。
ここでは、まだコマンドバッファは使用されていません。
スキームに合わせてシーンを構想しました。
2つのレイヤー(UI、Default)で構成され、「AnyPortraitキャラクター」、」2つのカメラ」、そしてレンダーテクスチャが施された「Quad Mesh」がシーンに配置されています。
(1) 「キャラクター」とそれをレンダリングする「最初のカメラ」です。 メインカメラではレンダリングされません。
(2) レンダリングテクスチャが適用された[Quad Mesh(RT Quad)]と「メインカメラ」です。
(1) そして最も重要な「レンダーテクスチャ(Render Texture)」アセットです。
(2) このレンダーテクスチャは、キャラクターをレンダリングする最初のカメラの「Target Texture」として登録されます。
キャラクターはメインカメラでレンダリングされないように、「Default」ではなくレイヤー(ここでは UI) に設定されています。
(1) メインカメラでレンダリングされる「Quad Mesh」です。
(2) このメッシュのマテリアルのテクスチャは、最初のカメラで使用される「レンダリングテクスチャ」アセットです。
この「Quad Mesh」のマテリアルと最初のカメラがレンダリングテクスチャを共有するため、最初のカメラでレンダリングする結果が「Quad Mesh」に表示されます。
ゲームを実行すると、Quad Meshで最初のカメラのレンダリング結果、つまりキャラクターが正常に見えることを確認できます。
問題は、レンダリングテクスチャの背景色が不透明でない場合に発生します。
(1) キャラクターをレンダリングする最初のカメラを選択します。
(2) 背景色のプロパティを選択します。 (Clear Flagsは「Solid Color」でなければなりません。)
(3) 背景色の「Alpha」値を下げます。
(4) 背景はもちろん、キャラクターを含むレンダーテクスチャ全体がますます透明になることがわかります。
これを解決する方法です。
不透明な背景のレンダリングテクスチャを完成させ、これとは別に「マスクテクスチャ」を「コマンドバッファ」を用いて生成します。
マスクを使用して、「背景」と「キャラクター」の領域を区別できます。
このマスクテクスチャをレンダリングテクスチャと組み合わせて、背景のみ透明な画像をレンダリングします。
参考
明確に区別して説明するために、カメラで生成する既存のレンダリングテクスチャを「カラーレンダリングテクスチャ(Color Render Texture)」と呼び、コマンドバッファが生成するマスクとして機能するレンダリングテクスチャを「マスクレンダリングテクスチャ(Mask Render Texture)」と呼びます。
この手法を実装するには多くの作業が必要です。
(1) 「Alpha」値を「単色(Grayscale)」で保存する「マスクレンダリング用カスタムシェーダ」を作成します。
(2) 「マスクレンダリングテクスチャ」を追加作成します。
(3) マスクレンダリングテクスチャをレンダリングする「コマンドバッファスクリプト」を作成します。
(4) 2つのレンダリングテクスチャをマージする「Quad Meshに塗りつぶされるシェーダとマテリアル」を作成します。
まず、コマンドバッファで使用するカスタムシェーダ、つまりAlpha値をモノクロで保存するカスタムシェーダを作成しましょう。
Shader "Custom Command Buffer Example/White Mask"
{
Properties
{
_MainTex("Main Texture (RGBA)", 2D) = "white" {}
_Color("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Transparent" "PreviewType" = "Plane" }
// 不透明度は累積されるため、ブレンド方式を「Additive」に変更します。
Blend One One
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// Alphaの値をRGBに置き換え、Alphaは1に設定します。
col.rgb = saturate(col.a * _Color.a);
col.a = 1.0f;
return col;
}
ENDCG
}
}
}
その後、コマンドバッファを制御するスクリプトを作成します。
前の例のコードとほとんど同じですが、「レンダーターゲット(Render Target)」としてランダムな「レンダーテクスチャ」を指定する点で違いがあります。
using UnityEngine;
using AnyPortrait;
public class CmdBufferExample : MonoBehaviour
{
public apPortrait portrait;
public Camera targetCamera;
public Material alternativeMaterial;
// レンダーターゲットとして使用する「マスク」用途のレンダーテクスチャです。
public RenderTexture maskRenderTexture;
private apCustomCommandBuffer _commandBuffer = null;
void Start()
{
if (_commandBuffer == null)
{
portrait.Initialize();
_commandBuffer = new apCustomCommandBuffer(targetCamera, portrait, "Custom Command Buffer");
_commandBuffer.AddToCamera(UnityEngine.Rendering.CameraEvent.BeforeForwardAlpha);
_commandBuffer.CreateAlternativeMaterials(alternativeMaterial);
}
}
void OnDestroy()
{
if (_commandBuffer != null)
{
_commandBuffer.Destory();
_commandBuffer = null;
}
}
void LateUpdate()
{
if (_commandBuffer != null)
{
_commandBuffer.ClearCommands();
// レンダーテクスチャをレンダーターゲットとして設定し、黒で背景を初期化します。
_commandBuffer.SetRenderTarget(maskRenderTexture);
_commandBuffer.ClearRenderTarget(true, true, Color.black);
_commandBuffer.SetViewMatrix();
_commandBuffer.SetProjectionMatrix();
_commandBuffer.DrawAllMeshesWithAlternativeMaterials(true, true);
}
}
}
最後に、「Quad Mesh」に塗りつぶすマテリアルのシェーダーを作成します。
「カラーレンダリングテクスチャ」と「マスクレンダリングテクスチャ」を組み合わせて背景のみを透明にする役割をします。
Shader "Custom Command Buffer Example/Merge RT and Mask"
{
Properties
{
// 2つのレンダリングテクスチャを受け取るようにプロパティを作成します。
_MainTex("Main Texture (RGBA)", 2D) = "white" {}
_MaskTex("Mask Texture (Grayscale)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _MaskTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// マスクレンダリングテクスチャに保存されている色をアルファとして使用します。
fixed4 mask = tex2D(_MaskTex, i.uv);
col.a = mask.r;
return col;
}
ENDCG
}
}
}
(1) コマンドバッファで使用する「マスクレンダーテクスチャ」と「マテリアル」を作成します。 「Alpha」値を色で保存するカスタムシェーダをマテリアルに適用します。
(2) コマンドバッファを制御する「GameObject」を生成します。
(3) コマンドバッファを制御する「スクリプト」を追加し、「apPortraitキャラクター」、「ファーストカメラ」を割り当て、 (1) で生成したマスク生成用「マテリアル」と「マスクレンダリングテクスチャ」を割り当てます。
画像全体の透明度をマスクレンダリングテクスチャから計算するため、「カラーレンダリングテクスチャ」の背景は元々不透明でなければなりません。
(1) 最初のカメラを選択します。
(2) 背景色を選択して (3) 不透明にします。
このとき、画像の端より少し暗い色に設定すると、よりきれいな結果が得られます。
(1) そして「Quad Mesh」に塗られる「マテリアル」を作成し、2つのレンダリングテクスチャを組み合わせるカスタムシェーダを適用します。
(2) 「最初のプロパティ(_MainTex)」に「カラーレンダリングテクスチャ」を割り当てます。
(3) 「2番目のプロパティ(_MaskTex)」に「マスクレンダーテクスチャ」を割り当てます。
(1) 「Quad Mesh」を選択します。
(2) 先に作成した「2つのレンダリングテクスチャを組み合わせたマテリアル」をこのメッシュに適用します。
完成した結果です。
透明な背景では、キャラクターだけが鮮明にレンダリングされる素晴らしい結果を得ることができます。
「カラーレンダリングテクスチャ」と「マスクレンダリングテクスチャ」を組み合わせてレンダリングの問題を解決することがわかります。
コマンドバッファは、上記のようにレンダリングの問題を解決するために追加のレンダリングテクスチャなどを作成するときに効果的に使用されます。
コマンドバッファは素晴らしい効果を示していますが、パフォーマンスの最適化が難しいという欠点があります。
ユーザーが直接メッシュをレンダリングするため、UnityとAnyPortraitのドローコール最適化機能は動作しにくいです。
したがって、パフォーマンスを常にチェックしながら実装することをお勧めします。