SpriteをSpriteでくり抜いて、くり抜かれた部分は別の場所に描画させたい
- 評価
- クリップ 0
- VIEW 1,564
前提・実現したいこと
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分ずらして描画。
というつもりで書いています。
該当のソースコード
Shader "Custom/SpriteA_Shader"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
_Offset("Offset Position", Vector) = (0, 0, 0, 0)
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
Pass
{
Stencil
{
Ref 0
Comp equal
}
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment SpriteFrag
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
ENDCG
}
Pass
{
Stencil
{
Ref 1
Comp equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment SpriteFrag
#pragma target 2.0
#include "UnitySprites.cginc"
float4 _Offset;
float4 _MainTex_ST;
v2f vert(appdata_t IN)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID (IN);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.vertex = IN.vertex.xyzw + _Offset;
OUT.vertex = UnityObjectToClipPos(OUT.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color * _RendererColor;
return OUT;
}
ENDCG
}
}
}
Shader "Custom/SpriteB_Shader" {
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
Pass
{
Stencil
{
Ref 1
Comp always
Pass replace
}
CGPROGRAM
#pragma vertex SpriteVert
#pragma fragment SpriteFrag
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
ENDCG
}
}
}
補足情報(FW/ツールのバージョンなど)
Unity: 2017.4.0f1
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
重なった部分だけ描く(または描かない)だけならステンシルが有効でしょうが、削られた部分を別の場所に描画したいとなるとやっかいになりますね...
次のような方法を試してみました。
※今回やりたい「重なった部分を切り取って別の場所に描画する」動作を何と呼ぶべきかよくわからなかったので、とりあえず以下では「かじる」と表現しています。かじる側(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にアタッチ)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
[RequireComponent(typeof(Renderer))]
public class Biter : MonoBehaviour
{
// 凹みTipsさん方式を参考にアクティブなBiterを管理する
public static readonly HashSet<Biter> activeObjects = new HashSet<Biter>();
private new Renderer renderer;
public void AddRenderingCommand(CommandBuffer commands)
{
if (this.renderer == null)
{
this.renderer = this.GetComponent<Renderer>();
}
commands.DrawRenderer(this.renderer, this.renderer.sharedMaterial, 0);
}
private void OnDisable()
{
activeObjects.Remove(this);
}
private void OnEnable()
{
activeObjects.Add(this);
}
}
Bitee(Aにアタッチ)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class Bitee : MonoBehaviour
{
private const CameraEvent EventToInsertCommands = CameraEvent.BeforeImageEffectsOpaque;
private static readonly int BiteRegionId = Shader.PropertyToID("_BiteRegion");
private static readonly Dictionary<Camera, CommandBuffer> CommandsForCamera =
new Dictionary<Camera, CommandBuffer>();
private void OnDisable()
{
RemoveCommands();
}
private void OnEnable()
{
RemoveCommands();
}
private void OnWillRenderObject()
{
// オブジェクトのレンダリングが発生したら追加処理内容を再構成
RecreateCommands();
RecomposeCommands();
}
private static void RecreateCommands()
{
// コマンドバッファの再作成が必要なら、適宜再作成
var camera = Camera.current;
if ((camera == null) || CommandsForCamera.ContainsKey(camera))
{
return;
}
var commands = new CommandBuffer {name = "RenderBiteRegion"};
camera.AddCommandBuffer(EventToInsertCommands, commands); // 透明オブジェクトの描画が始まる前の適当な位置に追加処理を挿入できるようにする
CommandsForCamera.Add(camera, commands);
}
private static void RecomposeCommands()
{
// 追加処理内容を再構成
// プレイ中にBiterが増えたり減ったりする場合を考慮し、凹みTipsさん方式で毎回アクティブなBiterだけをコマンドに追加
// この考慮が必要ないなら、毎回再構成しなくても問題ないかと思います
var camera = Camera.current;
if (!CommandsForCamera.ContainsKey(camera))
{
return;
}
var commands = CommandsForCamera[camera];
commands.Clear();
commands.GetTemporaryRT(BiteRegionId, -1, -1, 0); // _BiteRegionを作成
commands.SetRenderTarget(BiteRegionId); // それをターゲットにして...
commands.ClearRenderTarget(true, true, Color.clear); // まず(0, 0, 0, 0)でクリア
foreach (var biter in Biter.activeObjects)
{
biter.AddRenderingCommand(commands); // 各Biterを描画していく
}
}
private static void RemoveCommands()
{
foreach (var kvp in CommandsForCamera)
{
var camera = kvp.Key;
var commands = kvp.Value;
if (camera != null)
{
camera.RemoveCommandBuffer(EventToInsertCommands, commands);
}
}
CommandsForCamera.Clear();
}
}
Sprites-Bitee(Bitee用マテリアル)
Shader "Sprites/Bitee"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" { }
_Color ("Tint", Color) = (1, 1, 1, 1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1, 1, 1, 1)
[HideInInspector] _Flip ("Flip", Vector) = (1, 1, 1, 1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" { }
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
_Offset ("Offset Position", Vector) = (0, 0, 0, 0)
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
// 共通部分はCGINCLUDEにまとめる
CGINCLUDE
#pragma target 2.0
#pragma multi_compile_instancing
#pragma multi_compile _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#pragma vertex vert
#pragma fragment frag
#include "UnitySprites.cginc"
struct v2fc
{
float4 vertex: SV_POSITION;
fixed4 color: COLOR;
float2 texcoord: TEXCOORD0;
UNITY_VERTEX_OUTPUT_STEREO
float4 clipcoord: TEXCOORD1; // オフセットなしのクリッピング座標を別途伝達する
};
sampler2D _BiteRegion;
float4 _Offset;
inline v2fc processVert(in appdata_t IN, in float4 offset)
{
v2fc OUT;
UNITY_SETUP_INSTANCE_ID(IN);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.vertex = UnityFlipSprite(IN.vertex, _Flip);
float4 worldPos = mul(unity_ObjectToWorld, float4(OUT.vertex.xyz, 1.0)); // M変換後座標
OUT.clipcoord = mul(UNITY_MATRIX_VP, worldPos); // オフセットなしのクリッピング座標
OUT.vertex = mul(UNITY_MATRIX_VP, worldPos + offset); // M変換後、offsetだけずらしてからVP変換
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color * _RendererColor;
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap(OUT.vertex);
#endif
return OUT;
}
inline fixed4 processFrag(in v2fc IN, in float maskSign)
{
float2 maskcoord = ((IN.clipcoord / IN.clipcoord.w) * 0.5 + 0.5).xy; // オフセットなしクリッピング座標を加工してマスクサンプリング座標にする
fixed4 c = SampleSpriteTexture(IN.texcoord) * IN.color;
c.a *= step(maskSign, 0.0) + maskSign * tex2D(_BiteRegion, maskcoord).a; // maskSignに応じてアルファを反転させてアルファ抜き
c.rgb *= c.a;
return c;
}
ENDCG
Pass
{
// かじられていない部分を描画するパス
CGPROGRAM
v2fc vert(appdata_t IN)
{
return processVert(IN, 0.0); // 頂点オフセットはしない
}
fixed4 frag(v2fc IN): SV_Target
{
return processFrag(IN, -1.0); // かじられている部分はアルファ抜き
}
ENDCG
}
Pass
{
// かじられている部分を描画するパス
CGPROGRAM
v2fc vert(appdata_t IN)
{
return processVert(IN, _Offset); // 頂点を_Offsetだけずらす
}
fixed4 frag(v2fc IN): SV_Target
{
return processFrag(IN, 1.0); // かじられていない部分はアルファ抜き
}
ENDCG
}
}
}
動作させるとこのような見た目になりました。
省略しましたが、動作確認のために別途スプライトのドラッグ移動用スクリプトと、断片オフセットをマウスポインタとAの相対座標に設定するスクリプトをアタッチしました。
なお、Order in Layerを調整してBが先、Aが後にレンダリングされるようにしたため、かじられた断片はBの上にかぶさるように描画されています。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.37%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる