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

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

ただいまの
回答率

90.49%

  • Unity

    4165questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity2D

    919questions

  • HLSL

    14questions

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

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 306

 前提・実現したいこと

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

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で質問しよう!

  • ただいまの回答率 90.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    UnityでSpriteを使って一部を透明化する方法

    最近、シーンチェンジを実装するために基礎となる画像をSpriteを用いて一部透明化しようと考えたのですが、当たり前ですが単純に配置しても基礎画像が表示されてしまいます。(以下参考画

  • 解決済

    Texture2Dをななめに切り出す方法

    Texture2Dをななめに切り出す方法を探しています。 4点を基準に画像を切り取る関数などはないでしょうか? GetPixelsの引数も一つの座標と幅、高さしかないためどう斜めに

  • 解決済

    Unityのスライスで外枠だけ広げたいです

     前提・実現したいこと UnityでSlicedを使ってこのようなspriteを外枠の青の濃いところの太さは変えず広げたいです → こんな感じです  発生している問題 普通はこ

  • 解決済

    Unityで影だけを表示させたい

    Unityでゲームオブジェクトを、ライトで真上から照らした光沢にしつつ、それに付く影はライトを上斜めから当てた感じで表示させたいと考えています。 Dlirectional ligh

  • 解決済

    【Unity 2D】マテリアルをMaskにすることは出来ないのでしょうか

     前提・実現したいこと ジャギーのない円マスクを作成したい ジャギーの少ない円形しシェーダーを作成し、マテリアルに付加。 これを使ってイメージとして円表示ができる。 マスク

  • 解決済

    PostProcessingStackの効果が重複してしまう

    Unityでスマホ向けのアプリを制作しています。 アセットのPostProcessingStackを使って画面効果を付与しているのですが、メインカメラとは別にUI用のカメラを用

  • 解決済

    Unityで2D画像を旋回するパーティクルの表現

    Unity 2017を使用しています。 Scene上にSprite Rendererで描画した画像を前後に旋回し、軌跡が残るエフェクト(Particle System)を実装したい

  • 解決済

    ガウシアンブラーの実装

    f(x,y)=1/(2*π*σ)*exp(-(x^2+y^2)/(2*σ^2)) unityのshaderで上記の式を基にしたガウシアンブラーを実装したいと考えています。フィル

同じタグがついた質問を見る

  • Unity

    4165questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

  • Unity2D

    919questions

  • HLSL

    14questions

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