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バッファはビットが少なくて精度が低いので、高い精度が欲しい時に使える。