散々ImageEffectを書いてきたわりに、
MRTについて触れていなかったので簡単なMRTのサンプルコードを置いておきます
どうにも自分にとって良い感じのMRTのサンプルが見つけられなかったので実装。
— MIYAKE (@ScreenPocket) June 19, 2020
動画のキューブだけにメイン(深度),r成分,g成分,b成分を同時出力するシェーダをつけて描画→FrameBufferにコピー。 pic.twitter.com/ENAXQRH0t9
今回のサンプルで必要なものは2つ。
- カメラ用のスクリプト
- 描画用のRenderTextureを作る(今回は4つ)
- カメラの描画先をその4つのRenderTextureに設定する
- 描画が終わったらメイン画像を、本来描画するはずだったフレームバッファにコピーする
- MRT描画する用のシェーダ
- MRTで4つの出力先を使用するので shader target 3.5を指定
- FragmentShaderの出力先をSV_TargetからColor4つに変更
- それ用の構造体を定義
- 色の流し込み
まず、カメラに貼っ付けるコンポーネントのコードから。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
/// <summary>
/// MRTチェック用のカメラ
/// </summary>
[RequireComponent(typeof(Camera))]
public class MrtCamera : MonoBehaviour
{
/// <summary>
/// 出力先
/// </summary>
private RenderTexture _main;
private RenderTexture _red;
private RenderTexture _green;
private RenderTexture _blue;
/// <summary>
/// 確認用
/// </summary>
[SerializeField]
private RawImage[] _rawImages;
private void Start()
{
var width = Screen.width;
var height = Screen.height;
//描画先作成(Mainだけ深度有り)
_main = new RenderTexture(width, height, 24);
_red = new RenderTexture(width, height, 0);
_green = new RenderTexture(width, height, 0);
_blue = new RenderTexture(width, height, 0);
//描画先設定
var cam = GetComponent<Camera>();
cam.SetTargetBuffers(new []
{
_main.colorBuffer,
_red.colorBuffer,
_green.colorBuffer,
_blue.colorBuffer
}, _main.depthBuffer);
//書き終わったmainをフレームバッファにコピーするコマンドを作成
var commandBuffer = new CommandBuffer();
var currentActiveRenderTargetIdentifier = new RenderTargetIdentifier(BuiltinRenderTextureType.CurrentActive);
commandBuffer.SetRenderTarget (-1);
commandBuffer.Blit(
_main,
currentActiveRenderTargetIdentifier);
//全部終わった後でコマンド発行
cam.AddCommandBuffer(CameraEvent.AfterEverything, commandBuffer );
//確認用RawImageに貼り付ける
_rawImages[0].texture = _main;
_rawImages[1].texture = _red;
_rawImages[2].texture = _green;
_rawImages[3].texture = _blue;
}
}
_rawImagesの辺りは確認する必要がなかったら除去して下さい。
念の為 SystemInfo.supportedRenderTargetCount でサポートしているRenderTarget数をチェックするとなお良いかと。
オブジェクトに貼り付けて複数の描画先に出力するシェーダはこちら。
Shader "ScreenPocket/3d/MrtTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma target 3.5
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
struct mrt
{
fixed4 color0 : COLOR0;
fixed4 color1 : COLOR1;
fixed4 color2 : COLOR2;
fixed4 color3 : COLOR3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
mrt frag (v2f i)
{
mrt o;
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
o.color0 = col;
o.color1 = fixed4(col.r,0,0,1);
o.color2 = fixed4(0,col.g,0,1);
o.color3 = fixed4(0,0,col.b,1);
return o;
}
ENDCG
}
}
}
Unlitシェーダの改変です。
ポイントは
- #pragma target 3.5を指定
- targetについてはこちら → シェーダーコンパイルターゲットレベル
mrt4: 複数のレンダーターゲット、少なくとも 4
一般に言う #pragma target ディレクティブは上の要件を簡略にしたものであり、以下に対応します。
3.5: 3.0 + interpolators15 + mrt4 + integers + 2darray + instancing
※なので#pragma require mrt4でも良いかも?
- 出力用にstruct mrtを定義して、それをFragmentShaderの出力先に指定
- o.color0〜3にそれぞれ色成分を出力
参考にしたサイトは下記
1つのカメラでMRT出力を用いたPostEffectを実現する
【Unity】カメラ1つでUI解像度を維持し、3D解像度だけを下げる方法