質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

Q&A

解決済

1回答

2745閲覧

SpriteをSpriteでくり抜いて、くり抜かれた部分は別の場所に描画させたい

aerosawyer

総合スコア7

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

HLSL

HLSLは、米マイクロソフト社によって開発された Direct3D APIで使われるプロプライエタリなシェーディング言語です。

0グッド

0クリップ

投稿2018/05/07 07:54

編集2018/05/07 07:58

前提・実現したいこと

Unityでゲームを作成しています
SpriteAをSpriteBでくり抜いて、くり抜かれた部分は、任意のOffsetで別の場所に描画させたいです。
最終的にはくり抜かれた部分はユーザーが動かせるようにしようと思っています。

Shaderを書いてStencilBufferを利用した方法で実現できると思ったのですが
Stencil の部分を追加するとOffsetでずらした部分が描画されません。

実現したいことはStencilBufferを使う事自体、見当違いなのでしょうか?
ご助力いただければと思います。

==追記==
Shaderの内容を簡単に書くと
SpriteB_Shader: StencilBufferに1を書き込み
SpriteA_Shader: 1回目の Pass では、 StencilBufferが1ではない部分を描画、2回目の Pass では、StencilBufferが1の部分をOffset分ずらして描画。
というつもりで書いています。

該当のソースコード

HLSL

1Shader "Custom/SpriteA_Shader" 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 Pass 32 { 33 Stencil 34 { 35 Ref 0 36 Comp equal 37 } 38 39 CGPROGRAM 40 #pragma vertex SpriteVert 41 #pragma fragment SpriteFrag 42 #pragma target 2.0 43 #pragma multi_compile_instancing 44 #pragma multi_compile _ PIXELSNAP_ON 45 #pragma multi_compile _ ETC1_EXTERNAL_ALPHA 46 #include "UnitySprites.cginc" 47 ENDCG 48 } 49 50 Pass 51 { 52 Stencil 53 { 54 Ref 1 55 Comp equal 56 } 57 58 CGPROGRAM 59 #pragma vertex vert 60 #pragma fragment SpriteFrag 61 #pragma target 2.0 62 #include "UnitySprites.cginc" 63 64 float4 _Offset; 65 float4 _MainTex_ST; 66 67 v2f vert(appdata_t IN) 68 { 69 70 v2f OUT; 71 72 UNITY_SETUP_INSTANCE_ID (IN); 73 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); 74 75 OUT.vertex = IN.vertex.xyzw + _Offset; 76 OUT.vertex = UnityObjectToClipPos(OUT.vertex); 77 OUT.texcoord = IN.texcoord; 78 OUT.color = IN.color * _Color * _RendererColor; 79 80 return OUT; 81 } 82 83 ENDCG 84 } 85 } 86}

HLSL

1Shader "Custom/SpriteB_Shader" { 2 Properties 3 { 4 [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} 5 _Color ("Tint", Color) = (1,1,1,1) 6 [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 7 [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) 8 [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1) 9 [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} 10 [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 11 } 12 SubShader 13 { 14 Tags 15 { 16 "Queue"="Transparent" 17 "IgnoreProjector"="True" 18 "RenderType"="Transparent" 19 "PreviewType"="Plane" 20 "CanUseSpriteAtlas"="True" 21 } 22 23 Cull Off 24 Lighting Off 25 ZWrite Off 26 Blend One OneMinusSrcAlpha 27 28 Pass 29 { 30 Stencil 31 { 32 Ref 1 33 Comp always 34 Pass replace 35 } 36 37 CGPROGRAM 38 #pragma vertex SpriteVert 39 #pragma fragment SpriteFrag 40 #pragma target 2.0 41 #pragma multi_compile_instancing 42 #pragma multi_compile _ PIXELSNAP_ON 43 #pragma multi_compile _ ETC1_EXTERNAL_ALPHA 44 #include "UnitySprites.cginc" 45 ENDCG 46 } 47 } 48}

補足情報(FW/ツールのバージョンなど)

Unity: 2017.4.0f1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

重なった部分だけ描く(または描かない)だけならステンシルが有効でしょうが、削られた部分を別の場所に描画したいとなるとやっかいになりますね...

次のような方法を試してみました。
※今回やりたい「重なった部分を切り取って別の場所に描画する」動作を何と呼ぶべきかよくわからなかったので、とりあえず以下では「かじる」と表現しています。かじる側(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#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5[ExecuteInEditMode] 6[RequireComponent(typeof(Renderer))] 7public 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#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5[ExecuteInEditMode] 6public 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

1Shader "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の上にかぶさるように描画されています。

プレビュー

投稿2018/05/11 22:33

編集2018/05/11 22:55
Bongo

総合スコア10807

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問