ステンシルバッファを使ってアウトラインを描画する方法を紹介します。
考え方
次のような考え方で実装します。
- モデルよりも一回り大きい範囲のステンシルバッファに書き込む
- モデルを通常通り描画し、ステンシルバッファに上記の1とは違う値を書き込む
- 上記の1で書き込んだ値のフラグメントのみ、アウトラインの色で描画する
以前書いた下記の方法と似ていますが、最後にアウトラインを書き込むため、下の記事の課題2のような問題が起きません。
実装: 1. ステンシルバッファに書き込む
まずモデルより一回り大きい範囲のステンシルバッファに値を書き込みます。
今回は1を書き込んでいます。
Shader "StencilOutline" { Properties { _OutlineWidth ("Outline Width", float) = 0.1 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } Pass { Stencil{ Ref 1 Comp always Pass replace } Cull Front ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { half4 vertex : POSITION; half3 normal : NORMAL; }; struct v2f { half4 pos : SV_POSITION; }; half _OutlineWidth; v2f vert (appdata v) { v2f o = (v2f)0; o.pos = UnityObjectToClipPos(v.vertex + v.normal * _OutlineWidth); return o; } fixed4 frag (v2f i) : SV_Target { return 0; } ENDCG } } }
描画色は最終的に上書きされるので何でもいいです。
今回は描画順をZ値できっちり制御したいため、QueueをTransparentにしています。
(不透明パスだときっちり制御できません)
ここまでの描画結果は次のようになります。
実装: 2. モデルを描画する
モデルを描画しつつステンシルバッファに2を書き込みます。
ライティングはシンプルなDiffuseのみです。
Shader "Diffuse" { Properties { _Color ("Color", Color) = (1,1,1,1) } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent+1" } ZWrite Off Pass { Stencil{ Ref 2 Comp always Pass replace } Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { half4 vertex : POSITION; half3 normal : NORMAL; }; struct v2f { half4 pos : SV_POSITION; half3 normal: TEXCOORD1; }; half4 _Color; half4 _LightColor0; v2f vert (appdata v) { v2f o = (v2f)0; o.pos = UnityObjectToClipPos(v.vertex); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { half3 diff = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz)) * _LightColor0; fixed4 col; col.rgb = _Color * diff; return col; } ENDCG } } }
ここまでの描画結果は次のようになります。
実装: 3. アウトラインを描画する
最後にステンシルバッファが1のピクセルのみアウトライン色で描画します。
今回はポストエフェクトとして描画します。
ただしポストエフェクトからステンシルバッファを使うにはちょっと条件があるので下記の記事のとおりCommandBufferで実装します。
CommandBuffer登録用スクリプトの実装はこのようにします。
上の記事に色の変数を追加してマテリアルに登録する処理を書いただけです。
using UnityEngine; using UnityEngine.Rendering; [RequireComponent(typeof(Camera))] public class StencilOutline : MonoBehaviour { [SerializeField] private Shader _shader; [SerializeField] private Color _outlineColor; private void Awake() { Initialize(); } private void Initialize() { var camera = GetComponent<Camera>(); if (camera.allowMSAA || camera.allowHDR) { return; } var material = new Material(_shader); // アウトラインの色を適用 material.SetColor("_OutlineColor", _outlineColor); // CommandBufferを登録 var commandBuffer = new CommandBuffer(); int tempTextureIdentifier = Shader.PropertyToID("_WorldPostEffectTempTexture"); commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1); commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempTextureIdentifier); commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CurrentActive, material); commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier); camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer); } }
ポストエフェクトのシェーダは次のようにします。
Shader "StencilOutlinePostEffect" { Properties { _MainTex ("Texture", 2D) = "white" {} _OutlineColor ("Color", Color) = (1, 1, 1, 1) } SubShader { Cull Off ZWrite Off ZTest Always Stencil{ Ref 1 Comp Equal } 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 _OutlineColor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { return _OutlineColor; } ENDCG } } }
結果
レンダリング結果は次のようになります。
モデル同士が重なっても問題は起こりません。