重なった部分だけ描く(または描かない)だけならステンシルが有効でしょうが、削られた部分を別の場所に描画したいとなるとやっかいになりますね...
次のような方法を試してみました。
※今回やりたい「重なった部分を切り取って別の場所に描画する」動作を何と呼ぶべきかよくわからなかったので、とりあえず以下では「かじる」と表現しています。かじる側(B)は「Biter」、かじられる側(A)は「Bitee」、かじられる領域を示すテクスチャは「_BiteRegion」といった具合です。
描画する、しないの制御にステンシル機能は使わない。
Biter、Biteeスプライトのレンダリングに入る前に、追加の処理としてレンダーテクスチャ_BiteRegionをアルファ0でクリアしておいて、アクティブなBiterをそこに描画する(レンダリングパイプラインの途中で、特定のオブジェクトをかき集めて追加レンダリングさせる部分はUnity でスクリーンスペースのブーリアン演算をやってみた - 凹みTips で紹介されているテクニックにならいました)。
Biterのレンダリングが行われる。BiterのマテリアルはSprites-Defaultのままとし、通常通り描画させる。
Biteeのレンダリングが行われる。BiteeのマテリアルはSprites-DefaultをカスタマイズしたSprites-Biteeを使用した。2パス構成となっており、第1パスではかじられていない領域を本来の位置に描画、第2パスではかじられた領域をずらした位置に描画する。これら領域は、先ほど追加レンダリングした_BiteRegionをサンプリングして、そのアルファを見ることで判定する。
Biter(Bにアタッチ)
C#
1 using System . Collections . Generic ;
2 using UnityEngine ;
3 using UnityEngine . Rendering ;
4
5 [ ExecuteInEditMode ]
6 [ RequireComponent ( typeof ( Renderer ) ) ]
7 public class Biter : MonoBehaviour
8 {
9 // 凹みTipsさん方式を参考にアクティブなBiterを管理する
10 public static readonly HashSet < Biter > activeObjects = new HashSet < Biter > ( ) ;
11
12 private new Renderer renderer ;
13
14 public void AddRenderingCommand ( CommandBuffer commands )
15 {
16 if ( this . renderer == null )
17 {
18 this . renderer = this . GetComponent < Renderer > ( ) ;
19 }
20 commands . DrawRenderer ( this . renderer , this . renderer . sharedMaterial , 0 ) ;
21 }
22
23 private void OnDisable ( )
24 {
25 activeObjects . Remove ( this ) ;
26 }
27
28 private void OnEnable ( )
29 {
30 activeObjects . Add ( this ) ;
31 }
32 }
Bitee(Aにアタッチ)
C#
1 using System . Collections . Generic ;
2 using UnityEngine ;
3 using UnityEngine . Rendering ;
4
5 [ ExecuteInEditMode ]
6 public class Bitee : MonoBehaviour
7 {
8 private const CameraEvent EventToInsertCommands = CameraEvent . BeforeImageEffectsOpaque ;
9 private static readonly int BiteRegionId = Shader . PropertyToID ( "_BiteRegion" ) ;
10
11 private static readonly Dictionary < Camera , CommandBuffer > CommandsForCamera =
12 new Dictionary < Camera , CommandBuffer > ( ) ;
13
14 private void OnDisable ( )
15 {
16 RemoveCommands ( ) ;
17 }
18
19 private void OnEnable ( )
20 {
21 RemoveCommands ( ) ;
22 }
23
24 private void OnWillRenderObject ( )
25 {
26 // オブジェクトのレンダリングが発生したら追加処理内容を再構成
27 RecreateCommands ( ) ;
28 RecomposeCommands ( ) ;
29 }
30
31 private static void RecreateCommands ( )
32 {
33 // コマンドバッファの再作成が必要なら、適宜再作成
34 var camera = Camera . current ;
35 if ( ( camera == null ) || CommandsForCamera . ContainsKey ( camera ) )
36 {
37 return ;
38 }
39
40 var commands = new CommandBuffer { name = "RenderBiteRegion" } ;
41 camera . AddCommandBuffer ( EventToInsertCommands , commands ) ; // 透明オブジェクトの描画が始まる前の適当な位置に追加処理を挿入できるようにする
42 CommandsForCamera . Add ( camera , commands ) ;
43 }
44
45 private static void RecomposeCommands ( )
46 {
47 // 追加処理内容を再構成
48 // プレイ中にBiterが増えたり減ったりする場合を考慮し、凹みTipsさん方式で毎回アクティブなBiterだけをコマンドに追加
49 // この考慮が必要ないなら、毎回再構成しなくても問題ないかと思います
50 var camera = Camera . current ;
51 if ( ! CommandsForCamera . ContainsKey ( camera ) )
52 {
53 return ;
54 }
55
56 var commands = CommandsForCamera [ camera ] ;
57 commands . Clear ( ) ;
58 commands . GetTemporaryRT ( BiteRegionId , - 1 , - 1 , 0 ) ; // _BiteRegionを作成
59 commands . SetRenderTarget ( BiteRegionId ) ; // それをターゲットにして...
60 commands . ClearRenderTarget ( true , true , Color . clear ) ; // まず(0, 0, 0, 0)でクリア
61 foreach ( var biter in Biter . activeObjects )
62 {
63 biter . AddRenderingCommand ( commands ) ; // 各Biterを描画していく
64 }
65 }
66
67 private static void RemoveCommands ( )
68 {
69 foreach ( var kvp in CommandsForCamera )
70 {
71 var camera = kvp . Key ;
72 var commands = kvp . Value ;
73 if ( camera != null )
74 {
75 camera . RemoveCommandBuffer ( EventToInsertCommands , commands ) ;
76 }
77 }
78
79 CommandsForCamera . Clear ( ) ;
80 }
81 }
Sprites-Bitee(Bitee用マテリアル)
HLSL
1 Shader "Sprites/Bitee"
2 {
3 Properties
4 {
5 [ PerRendererData ] _MainTex ( "Sprite Texture" , 2D ) = "white" { }
6 _Color ( "Tint" , Color ) = ( 1 , 1 , 1 , 1 )
7 [ MaterialToggle ] PixelSnap ( "Pixel snap" , Float ) = 0
8 [ HideInInspector ] _RendererColor ( "RendererColor" , Color ) = ( 1 , 1 , 1 , 1 )
9 [ HideInInspector ] _Flip ( "Flip" , Vector ) = ( 1 , 1 , 1 , 1 )
10 [ PerRendererData ] _AlphaTex ( "External Alpha" , 2D ) = "white" { }
11 [ PerRendererData ] _EnableExternalAlpha ( "Enable External Alpha" , Float ) = 0
12 _Offset ( "Offset Position" , Vector ) = ( 0 , 0 , 0 , 0 )
13 }
14
15 SubShader
16 {
17 Tags
18 {
19 "Queue" = "Transparent"
20 "IgnoreProjector" = "True"
21 "RenderType" = "Transparent"
22 "PreviewType" = "Plane"
23 "CanUseSpriteAtlas" = "True"
24 }
25
26 Cull Off
27 Lighting Off
28 ZWrite Off
29 Blend One OneMinusSrcAlpha
30
31 // 共通部分はCGINCLUDEにまとめる
32 CGINCLUDE
33
34 # pragma target 2.0
35 # pragma multi_compile_instancing
36 # pragma multi_compile _ PIXELSNAP_ON
37 # pragma multi_compile _ ETC1_EXTERNAL_ALPHA
38 # pragma vertex vert
39 # pragma fragment frag
40
41 # include "UnitySprites.cginc"
42
43 struct v2fc
44 {
45 float4 vertex : SV_POSITION ;
46 fixed4 color : COLOR ;
47 float2 texcoord : TEXCOORD0 ;
48 UNITY_VERTEX_OUTPUT_STEREO
49 float4 clipcoord : TEXCOORD1 ; // オフセットなしのクリッピング座標を別途伝達する
50 } ;
51
52 sampler2D _BiteRegion ;
53 float4 _Offset ;
54
55 inline v2fc processVert ( in appdata_t IN , in float4 offset )
56 {
57 v2fc OUT ;
58
59 UNITY_SETUP_INSTANCE_ID ( IN ) ;
60 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO ( OUT ) ;
61
62 OUT . vertex = UnityFlipSprite ( IN . vertex , _Flip ) ;
63 float4 worldPos = mul ( unity_ObjectToWorld , float4 ( OUT . vertex . xyz , 1.0 ) ) ; // M変換後座標
64 OUT . clipcoord = mul ( UNITY_MATRIX_VP , worldPos ) ; // オフセットなしのクリッピング座標
65 OUT . vertex = mul ( UNITY_MATRIX_VP , worldPos + offset ) ; // M変換後、offsetだけずらしてからVP変換
66 OUT . texcoord = IN . texcoord ;
67 OUT . color = IN . color * _Color * _RendererColor ;
68
69 # ifdef PIXELSNAP_ON
70 OUT . vertex = UnityPixelSnap ( OUT . vertex ) ;
71 # endif
72
73 return OUT ;
74 }
75
76 inline fixed4 processFrag ( in v2fc IN , in float maskSign )
77 {
78 float2 maskcoord = ( ( IN . clipcoord / IN . clipcoord . w ) * 0.5 + 0.5 ) . xy ; // オフセットなしクリッピング座標を加工してマスクサンプリング座標にする
79 fixed4 c = SampleSpriteTexture ( IN . texcoord ) * IN . color ;
80 c . a *= step ( maskSign , 0.0 ) + maskSign * tex2D ( _BiteRegion , maskcoord ) . a ; // maskSignに応じてアルファを反転させてアルファ抜き
81 c . rgb *= c . a ;
82 return c ;
83 }
84
85 ENDCG
86
87 Pass
88 {
89 // かじられていない部分を描画するパス
90 CGPROGRAM
91
92 v2fc vert ( appdata_t IN )
93 {
94 return processVert ( IN , 0.0 ) ; // 頂点オフセットはしない
95 }
96
97 fixed4 frag ( v2fc IN ) : SV_Target
98 {
99 return processFrag ( IN , - 1.0 ) ; // かじられている部分はアルファ抜き
100 }
101
102 ENDCG
103 }
104
105 Pass
106 {
107 // かじられている部分を描画するパス
108 CGPROGRAM
109
110 v2fc vert ( appdata_t IN )
111 {
112 return processVert ( IN , _Offset ) ; // 頂点を_Offsetだけずらす
113 }
114
115 fixed4 frag ( v2fc IN ) : SV_Target
116 {
117 return processFrag ( IN , 1.0 ) ; // かじられていない部分はアルファ抜き
118 }
119
120 ENDCG
121 }
122 }
123 }
動作させるとこのような見た目になりました。
省略しましたが、動作確認のために別途スプライトのドラッグ移動用スクリプトと、断片オフセットをマウスポインタとAの相対座標に設定するスクリプトをアタッチしました。
なお、Order in Layerを調整してBが先、Aが後にレンダリングされるようにしたため、かじられた断片はBの上にかぶさるように描画されています。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。