Unityシェーダ入門 #005 Multiple Render Target

以下はUnity4.3.0f4、DX11で行っています。

1回のレンダリングで複数の値を出力するMRT(MultipleRenderTarget)をやってみます。

手順は

1.複数の値を出力するシェーダを作成する(SurfaceShaderではなく、展開されたシェーダじゃなきゃ駄目っぽい。やり方が今のところわかりません。)

2.メインカメラとは別のカメラを作成し、MRTレンダリングを行うスクリプトをくっつける。
(複数出力する専用シェーダが必要になるため、メインカメラの通常レンダリングにバッファを追加して……ってのは無理みたい)


です。

 まずは複数の値を出力するシェーダから見てみます。
これはUnityでシェーダを新規作成し、#pragma debugで展開されたシェーダからコピペしてきて作ったものです。

Shader "Custom/TestShader" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {

	Pass {
		Name "FORWARD"
		Tags { "LightMode" = "ForwardBase" }

		CGPROGRAM
		#pragma dualforward
		#pragma vertex vert_surf
		#pragma fragment frag_surf
		#pragma multi_compile_fwdbase
		#include "HLSLSupport.cginc"
		#include "UnityShaderVariables.cginc"
		#define UNITY_PASS_FORWARDBASE
		#include "UnityCG.cginc"
		#include "Lighting.cginc"
		#include "AutoLight.cginc"

		#define INTERNAL_DATA
		#define WorldReflectionVector(data,normal) data.worldRefl
		#define WorldNormalVector(data,normal) normal
		#line 1
		#line 10

		//#pragma surface surf Lambert
		#pragma debug

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		#ifdef LIGHTMAP_OFF
		struct v2f_surf {
		  float4 pos : SV_POSITION;
		  float2 pack0 : TEXCOORD0;
		  fixed3 normal : TEXCOORD1;
		  fixed3 vlight : TEXCOORD2;
		  LIGHTING_COORDS(3,4)
		};
		#endif
		#ifndef LIGHTMAP_OFF
		struct v2f_surf {
		  float4 pos : SV_POSITION;
		  float2 pack0 : TEXCOORD0;
		  float2 lmap : TEXCOORD1;
		  LIGHTING_COORDS(2,3)
		};
		#endif
		#ifndef LIGHTMAP_OFF
		float4 unity_LightmapST;
		#endif
		float4 _MainTex_ST;
		v2f_surf vert_surf (appdata_full v) {
		  v2f_surf o;
		  o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
		  o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
		  #ifndef LIGHTMAP_OFF
		  o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
		  #endif
		  float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
		  #ifdef LIGHTMAP_OFF
		  o.normal = worldN;
		  #endif
		  #ifdef LIGHTMAP_OFF
		  float3 shlight = ShadeSH9 (float4(worldN,1.0));
		  o.vlight = shlight;
		  #ifdef VERTEXLIGHT_ON
		  float3 worldPos = mul(_Object2World, v.vertex).xyz;
		  o.vlight += Shade4PointLights (
			unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
			unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
			unity_4LightAtten0, worldPos, worldN );
		  #endif // VERTEXLIGHT_ON
		  #endif // LIGHTMAP_OFF
		  TRANSFER_VERTEX_TO_FRAGMENT(o);
		  return o;
		}


		#ifndef LIGHTMAP_OFF
		sampler2D unity_Lightmap;
		#ifndef DIRLIGHTMAP_OFF
		sampler2D unity_LightmapInd;
		#endif
		#endif

		struct COL_OUTPUT
		{
			float4 Col0 : COLOR0;
			float4 Col1 : COLOR1;
			float4 Col2 : COLOR2;
		};

		COL_OUTPUT frag_surf (v2f_surf IN) {
		  #ifdef UNITY_COMPILER_HLSL
		  Input surfIN = (Input)0;
		  #else
		  Input surfIN;
		  #endif
		  surfIN.uv_MainTex = IN.pack0.xy;
		  #ifdef UNITY_COMPILER_HLSL
		  SurfaceOutput o = (SurfaceOutput)0;
		  #else
		  SurfaceOutput o;
		  #endif
		  o.Albedo = 0.0;
		  o.Emission = 0.0;
		  o.Specular = 0.0;
		  o.Alpha = 0.0;
		  o.Gloss = 0.0;
		  #ifdef LIGHTMAP_OFF
		  o.Normal = IN.normal;
		  #endif
		  surf (surfIN, o);
		  fixed atten = LIGHT_ATTENUATION(IN);


		  fixed4 c = 0;
		  #ifdef LIGHTMAP_OFF
		  c = LightingLambert (o, _WorldSpaceLightPos0.xyz, atten);
		  #endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
		  #ifdef LIGHTMAP_OFF
		  c.rgb += o.Albedo * IN.vlight;
		  #endif // LIGHTMAP_OFF
		  #ifndef LIGHTMAP_OFF
		  #ifndef DIRLIGHTMAP_OFF
		  fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
		  fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
		  half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 0).rgb;
		  #else // !DIRLIGHTMAP_OFF
		  fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
		  fixed3 lm = DecodeLightmap (lmtex);
		  #endif // !DIRLIGHTMAP_OFF
		  #ifdef SHADOWS_SCREEN
		  #if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
		  c.rgb += o.Albedo * min(lm, atten*2);
		  #else
		  c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
		  #endif
		  #else // SHADOWS_SCREEN
		  c.rgb += o.Albedo * lm;
		  #endif // SHADOWS_SCREEN
		  c.a = o.Alpha;
		#endif // LIGHTMAP_OFF


		COL_OUTPUT cols;
		cols.Col0 = float4(c.r,0,0,0);
		cols.Col1 = float4(0,c.g,0,0);
		cols.Col2 = float4(0,0,c.b,0);

		  return cols;
		}
		ENDCG
	} // PassEnd
	} // SubShaderEnd
	FallBack "Diffuse"
}

長いので大事な所だけ抜粋します。


		struct COL_OUTPUT
		{
			float4 Col0 : COLOR0;
			float4 Col1 : COLOR1;
			float4 Col2 : COLOR2;
		};

		COL_OUTPUT frag_surf (v2f_surf IN) {

                // ~省略

		COL_OUTPUT cols;
		cols.Col0 = float4(c.r,0,0,0);
		cols.Col1 = float4(0,c.g,0,0);
		cols.Col2 = float4(0,0,c.b,0);

		return cols;

		}

 COL_OUTPUTという構造体を作ったうえで、フラグメントシェーダの戻り値をCOL_OUTPUTに変更します。(あと後ろについている:COLORを消します)
 上の例ではRGB各色を3つのバッファに分けて出力しているので、上手く行けば結果は3色にわかれたものになります。


 次にカメラに貼り付けるスクリプトです。

using UnityEngine;
using System.Collections;

public class MRTTest : MonoBehaviour {

    public RenderTexture[] RT;
    public Shader MRTShader;
    
	// Use this for initialization
	void Start () 
        {
        RT = new RenderTexture[3];
        for (int i = 0; i < 3; i++)
        {
            int Depth = i == 0 ? 24 : 0;
            RT[i] = new RenderTexture(Screen.width, Screen.height, Depth);
        }

        RenderBuffer[] rb = new RenderBuffer[3];
        for (int i = 0; i < 3; i++)
            rb[i] = RT[i].colorBuffer;

        camera.SetTargetBuffers(rb, RT[0].depthBuffer);
	}
	
	// Update is called once per frame
	void Update () 
	{
        camera.RenderWithShader(MRTShader, null);
	}
}

RenderTextureを作成しSetTargetBuffersで割り当てています。深度バッファは1つで十分なので、最初のRenderTextureだけ割り当てます。
レンダリングはRenderWithShaderで、先ほど作成したシェーダを用いて行われます。(前回説明したReplaced Shaderの仕組みが使われます)




※各色を別バッファに出力出来るならRGBAの32bitにfloatにエンコードした色1つを入れればMSAA+HDRレンダリング出来るかなーと思ったけど、RenderTextureのantiAliasingに値を入れると、RenderTextureFormatに関わらずMRTが動かなかったので無理っぽい


※RenderTextureFormatはFloatもイケるので、ワールド座標とか深度とか法線みたいなG-バッファ的なのは一度のレンダリングで作れる。Unityがイメージエフェクトとかに使うために標準で作るDepth-Normalバッファはビットが少なくて精度が低いので、高い精度が欲しい時に使える。