【Unity】SRP (Scriptable Render Pipeline) 入門②
前回はSRPで画面を塗りつぶしました。
今回は不透明オブジェクトと半透明オブジェクトを描画します。
スクリプト
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Experimental; using UnityEngine.Experimental.Rendering; [ExecuteInEditMode] public class DrawObjAssetPipe : RenderPipelineAsset { [SerializeField] private float modelRenderResolutionRate = 0.7f; public float ModelRenderResolutionRate => modelRenderResolutionRate; protected override RenderPipeline CreatePipeline() { return new DrawObjPipeInstance(this); } #if UNITY_EDITOR [UnityEditor.MenuItem("SRP-Demo/02 - Create Basic Asset Pipeline")] static void CraeteDrawObjAssetPipeline() { // このクラスのインスタンスを作成 var instance = ScriptableObject.CreateInstance<DrawObjAssetPipe>(); // アセット化 UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/Shader/SRP/1-DrawObjAssetPipe.asset"); } #endif } public class DrawObjPipeInstance : RenderPipeline { private const int MAX_CAMERA_COUNT = 4; private const string FORWARD_SHADER_TAG = "ToonForward"; private CommandBuffer[] commandBuffers = new CommandBuffer[MAX_CAMERA_COUNT]; private DrawObjAssetPipe drawObjAssetPipe; private CullingResults cullingResults; private RenderTargetIdentifier[] renderTargetIdentifiers = new RenderTargetIdentifier[(int)RenderTextureType.Count]; private enum RenderTextureType { ModelColor, ModelDepth, Count, } public DrawObjPipeInstance(DrawObjAssetPipe asset) { drawObjAssetPipe = asset; // CommandBufferの事前生成 for (int i = 0; i < commandBuffers.Length; i++) { commandBuffers[i] = new CommandBuffer(); commandBuffers[i].name = "ToonRP"; } } /// <summary> /// 描画処理 /// </summary> protected override void Render(ScriptableRenderContext context, Camera[] cameras) { for (int i = 0; i < cameras.Length; i++) { var camera = cameras[i]; var commandBuffer = commandBuffers[i]; // カメラプロパティ設定 context.SetupCameraProperties(camera); // カメラからカリングのための情報を取得 if (!camera.TryGetCullingParameters(false, out var cullingParameters)) { continue; } // ScriptableCullingParametersに基づいてカリングをスケジュール cullingResults = context.Cull(ref cullingParameters); // RenderTexture作成 CreateRenderTexture(context, camera, commandBuffer); // モデル描画用RTのClear ClearModelRenderTexture(context, camera, commandBuffer); // ライト情報のセットアップ SetupLights(context, camera, commandBuffer); // 不透明オブジェクト描画 DrawOpaque(context, camera, commandBuffer); // Skybox描画 if (camera.clearFlags == CameraClearFlags.Skybox) { context.DrawSkybox(camera); } // 半透明オブジェクト描画 DrawTransparent(context, camera, commandBuffer); // CameraTargetに描画 RestoreCameraTarget(context, commandBuffer); #if UNITY_EDITOR // Gizmo if (UnityEditor.Handles.ShouldRenderGizmos()) { context.DrawGizmos(camera, GizmoSubset.PreImageEffects); } #endif // PostProcessing #if UNITY_EDITOR // Gizmo if (UnityEditor.Handles.ShouldRenderGizmos()) { context.DrawGizmos(camera, GizmoSubset.PostImageEffects); } #endif // RenderTexture解放 ReleaseRenderTexture(context, commandBuffer); } context.Submit(); } /// <summary> /// レンダーテクスチャ作成 /// </summary> private void CreateRenderTexture(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); var width = camera.targetTexture?.width ?? Screen.width; var height = camera.targetTexture?.height ?? Screen.height; var modelWidth = (int)((float)width * drawObjAssetPipe.ModelRenderResolutionRate); var modelHeight = (int)((float)height * drawObjAssetPipe.ModelRenderResolutionRate); // カラー用とデプス用RenderTextureを作成 commandBuffer.GetTemporaryRT((int)RenderTextureType.ModelColor, modelWidth, modelHeight, 0, FilterMode.Bilinear, RenderTextureFormat.Default); commandBuffer.GetTemporaryRT((int)RenderTextureType.ModelDepth, modelWidth, modelHeight, 0, FilterMode.Point, RenderTextureFormat.Depth); // コマンドバッファの実行をスケジュールします。 context.ExecuteCommandBuffer(commandBuffer); // RenderTextureの識別子を生成 for (int i = 0; i < (int)RenderTextureType.Count; i++) { renderTargetIdentifiers[i] = new RenderTargetIdentifier(i); } } /// <summary> /// RenderTextureをクリア /// </summary> private void ClearModelRenderTexture(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); // RenderTarget設定 commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]); if (camera.clearFlags == CameraClearFlags.Depth || camera.clearFlags == CameraClearFlags.Skybox) { commandBuffer.ClearRenderTarget(true, false, Color.black, 1.0f); } else if (camera.clearFlags == CameraClearFlags.SolidColor) { commandBuffer.ClearRenderTarget(true, true, camera.backgroundColor, 1.0f); } context.ExecuteCommandBuffer(commandBuffer); } /// <summary> /// ライトのセットアップ /// </summary> private void SetupLights(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); // DirectionalLightの探索 int lightIndex = -1; for (int i = 0; i < cullingResults.visibleLights.Length; i++) { var visibleLight = cullingResults.visibleLights[i]; var light = visibleLight.light; if (light == null || light.shadows == LightShadows.None || light.shadowStrength <= 0f || light.type != LightType.Directional) { continue; } lightIndex = i; break; } // ディレクショナルライトが無効の時、シェーダーのライトヴァリアントを無効に if (lightIndex < 0) { commandBuffer.DisableShaderKeyword("ENABLE_DIRECTIONAL_LIGHT"); context.ExecuteCommandBuffer(commandBuffer); return; } // ディレクショナルライトが有効の時、シェーダーのライトヴァリアントの有効に { var visibleLight = cullingResults.visibleLights[lightIndex]; var light = visibleLight.light; commandBuffer.EnableShaderKeyword("ENABLE_DIRECTIONAL_LIGHT"); commandBuffer.SetGlobalColor("_LightColor", light.color * light.intensity); commandBuffer.SetGlobalVector("_LightVector", -light.transform.forward); context.ExecuteCommandBuffer(commandBuffer); } } /// <summary> /// 不透明オブジェクトの描画 /// </summary> private void DrawOpaque(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]); context.ExecuteCommandBuffer(commandBuffer); // 描画順 var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque }; // ここで指定したTagを記述したシェーダーのみ描画される var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings); var filterSettings = new FilteringSettings( // 指定されたレンダーキューの間のオブジェクトのみ描画する(0-2500) new RenderQueueRange(0, (int)RenderQueue.GeometryLast), // カメラに指定されたレイヤーのみ描画する camera.cullingMask ); // Rendering context.DrawRenderers(cullingResults, ref settings, ref filterSettings); } /// <summary> /// 半透明オブジェクトの描画 /// </summary> private void DrawTransparent(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]); context.ExecuteCommandBuffer(commandBuffer); // Filtering, Sort var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonTransparent }; var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings); var filterSettings = new FilteringSettings( // 指定されたレンダーキューの間のオブジェクトのみ描画する(2500-3000) new RenderQueueRange((int)RenderQueue.GeometryLast, (int)RenderQueue.Transparent), camera.cullingMask ); // 描画 context.DrawRenderers(cullingResults, ref settings, ref filterSettings); } /// <summary> /// 画面に対して描画する /// </summary> private void RestoreCameraTarget(ScriptableRenderContext context, CommandBuffer commandBuffer) { commandBuffer.Clear(); var cameraTarget = new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget); commandBuffer.SetRenderTarget(cameraTarget); commandBuffer.Blit(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], cameraTarget); context.ExecuteCommandBuffer(commandBuffer); } private void ReleaseRenderTexture(ScriptableRenderContext context, CommandBuffer commandBuffer) { commandBuffer.Clear(); for (int i = 0; i < (int)RenderTextureType.Count; i++) { commandBuffer.ReleaseTemporaryRT(i); } context.ExecuteCommandBuffer(commandBuffer); } }
解説
protected override void Render(ScriptableRenderContext context, Camera[] cameras) { for (int i = 0; i < cameras.Length; i++) { var camera = cameras[i]; var commandBuffer = commandBuffers[i]; // カメラプロパティ設定 context.SetupCameraProperties(camera);
Render関数の頭を見ていきます。 引数にScriptableRenderContextとシーン上のカメラが配列で渡されいます。
ScriptableRenderContext
Unity - Scripting API: ScriptableRenderContext
コマンドバッファの実行タイミングやオブジェクトやスカイボックス、影の描画といったカスタムレンダーパイプラインの状態の保存とコマンドの実行を担うコンテキストになります。
なお、コマンドバッファについてはこちらをご参照ください。
代表的なメソッドを紹介します。
public void ExecuteCommandBuffer(Rendering.CommandBuffer commandBuffer);
Unity - Scripting API: Rendering.ScriptableRenderContext.ExecuteCommandBuffer 引数のコマンドバッファを予約しておきます。 この段階でコマンドバッファの実行は行われません。
public void Submit();
Unity - Scripting API: Rendering.ScriptableRenderContext.Submit ExecuteCommandBufferで予約したコマンドバッファを実行します。
SRPの主な処理の流れは以下のようになります。
1.コマンドバッファに描画に必要な処理を登録
2.ExecuteCommandBufferでコマンドバッファを予約
カメラごとに1と2を実行
3.最後にSubmitで予約したコマンドバッファを実行
ShaderTagId
private const string FORWARD_SHADER_TAG = "ToonForward"; ・ ・ /// <summary> /// 不透明オブジェクトの描画 /// </summary> private void DrawOpaque(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer) { commandBuffer.Clear(); commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]); context.ExecuteCommandBuffer(commandBuffer); // 描画順 var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque }; // ここで指定したTagを記述したシェーダーのみ描画される var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings); var filterSettings = new FilteringSettings( // 指定されたレンダーキューの間のオブジェクトのみ描画する(0-2500) new RenderQueueRange(0, (int)RenderQueue.GeometryLast), // カメラに指定されたレイヤーのみ描画する camera.cullingMask ); // Rendering context.DrawRenderers(cullingResults, ref settings, ref filterSettings); }
今回のパイプラインでは描画設定に"ToonForward"をShaderTagIdとして設定しています。 ここで設定したTagが記述されたシェーダのみが描画されるため、対応した専用のシェーダを記述する必要があります。
不透明オブジェクトのシェーダー
Shader "ToonRP/Unilt" { Properties { _Color ("Color", COLOR) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } LOD 100 Pass { Name "Unlit" Tags {"LightMode" = "ToonForward"} HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ ENABLE_DIRECTIONAL_LIGHT #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; float4 _Color; sampler2D _MainTex; float4 _MainTex_ST; #if ENABLE_DIRECTIONAL_LIGHT float4 _LightColor; float4 _LightVector; #endif 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 { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; #if ENABLE_DIRECTIONAL_LIGHT col *= _LightColor; #endif return col; } ENDHLSL } } }
半透明オブジェクトのシェーダー
Shader "ToonRP/UnlitTransparent" { Properties { _Color("Color", COLOR) = (1, 1, 1, 1) _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } LOD 100 Pass { Name "Transparent" Tags {"LightMode" = "ToonForward"} Blend SrcAlpha OneMinusSrcAlpha ZWrite Off HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ ENABLE_DIRECTIONAL_LIGHT #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; float4 _Color; sampler2D _MainTex; float4 _MainTex_ST; #if ENABLE_DIRECTIONAL_LIGHT float4 _LightColor; float4 _LightVector; #endif 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 { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; #if ENABLE_DIRECTIONAL_LIGHT col *= _LightColor; #endif return col; } ENDHLSL } } }
実際に作成したシェーダがこちらです。 前項の通り、Tags以下に "LightMode" = "ToonForward" が記述されています。 このタグでシェーダの絞り込みが行われているため,この記述が入ったシェーダのみが描画されます。
あとは前回の記事の実装したSRPを適用の作業を行えば反映されます。 ny-program.hatenablog.com
描画結果はこちらです。 不透明・半透明どちらも描画されています。