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

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

ただいまの
回答率

90.47%

  • Unity2D

    1301questions

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

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 2,861

mizko

score 12

 前提・実現したいこと

ジャギーのない円マスクを作成したい

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

マスクとして利用すると、イメージが前面に出てきて裏のイメージが表示できない

 発生している問題・エラーメッセージ

エラーメッセージ

 該当のソースコード

イメージ説明
イメージ説明
赤丸がマスク。
マスクをオフにすると裏のイメージが表示されます。

シェーダーのソース

    Shader "Unlit/Circle"
    {
        Properties
        {
            _Color ("Color", Color) = (1,1,1,1)
        }
        SubShader
        {
            Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMask RGB
            ZWrite Off
            Cull Off

            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag

                #include "UnityCG.cginc"

                // Quality level
                // 2 == high quality
                // 1 == medium quality
                // 0 == low quality
                #define QUALITY_LEVEL 1

                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 texcoord : TEXCOORD0;
                };

                struct v2f
                {
                    float4 pos : SV_POSITION;
                    float2 uv : TEXCOORD0;
                };

                v2f vert (appdata v)
                {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.uv = v.texcoord - 0.5;
                    return o;
                }

                fixed4 _Color;

                fixed4 frag (v2f i) : SV_Target
                {
                    float dist = length(i.uv);

                #if QUALITY_LEVEL == 2
                    // length derivative, 1.5 pixel smoothstep edge
                    float pwidth = length(float2(ddx(dist), ddy(dist)));
                    float alpha = smoothstep(0.5, 0.5 - pwidth * 1.5, dist);
                #elif QUALITY_LEVEL == 1
                    // fwidth, 1.5 pixel smoothstep edge
                    float pwidth = fwidth(dist);
                    float alpha = smoothstep(0.5, 0.5 - pwidth * 1.5, dist);
                #else // Low
                    // fwidth, 1 pixel linear edge
                    float pwidth = fwidth(dist);
                    float alpha = saturate((0.5 - dist) / pwidth);
                #endif

                    return fixed4(_Color.rgb, _Color.a * alpha);
                }
                ENDCG
            }
        }
    }

 試したこと

描画順の問題かと思い、stack over flowを参考に以下のように編集しました。

 Properties
        {
         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
         _Color ("Tint", Color) = (1,1,1,1)

         // required for UI.Mask
         _StencilComp ("Stencil Comparison", Float) = 3
         _Stencil ("Stencil ID", Float) = 1
         _StencilOp ("Stencil Operation", Float) = 2
         _StencilWriteMask ("Stencil Write Mask", Float) = 255
         _StencilReadMask ("Stencil Read Mask", Float) = 255
         _ColorMask ("Color Mask", Float) = 15

        }
        SubShader
        {
            Tags
            {
            "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"
            }
            Stencil
            {
                Ref [_Stencil]
                Comp [_StencilComp]
                Pass [_StencilOp] 
                ReadMask [_StencilReadMask]
                WriteMask [_StencilWriteMask]
            }


が、マテリアル自体が描画されなくなりました

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

Unity 20171.1.p3

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

マスク側のマテリアルを変更して動的に描いた図形でマスキングすることも可能なはずですが、たとえマスク図形の縁にアンチエイリアス処理を施してなめらかにしても、切り抜かれる側の縁はギザギザになってしまうかと思います。

Maskコンポーネントはステンシルバッファを利用してマスキング効果を実現しているようです。マスク側の描画時に同時に各ピクセルにステンシル値を書き込み、切り抜かれる側はそのピクセルのステンシル値に応じて描画する・しないを決定する仕掛けになっており、切り抜かれる側が縁のピクセルを描画する際に透明度をどうするべきか判断する手掛かりがないため、ジャギーが生じてしまうことになります。

ご提示のAliasing When Using UI Mask - Unity Answersは、切り抜きにMaskコンポーネントは使用せず、その代替として切り抜かれる側のイメージのマテリアルにマスキング用テクスチャを持たせる方法を提案しています。同様の手法がUnity uGUIで綺麗なマスク表現 - 渋谷ほととぎす通信にも紹介されておりましたので、ご参考になるかと思います。

サイトの例ではマスクをテクスチャとして与える方式ですが、これを動的に描いた円に置き換えるとこんな感じでしょうか。

Shader "UI/OvalMasked"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _MaskCenterAndHalfExtents ("Mask Center / Half Extents", Vector) = (0.0,0.0,128.0,128.0) // 楕円の位置と水平・垂直半径のプロパティを追加

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile __ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = v.texcoord;

                OUT.color = v.color * _Color;
                return OUT;
            }

            sampler2D _MainTex;
            float4 _MaskCenterAndHalfExtents;

            fixed4 frag(v2f IN) : SV_Target
            {
                float2 center = _MaskCenterAndHalfExtents.xy; // 円の中心座標
                float2 halfExtents = _MaskCenterAndHalfExtents.zw; // 円の水平方向半径、垂直方向半径
                float2 relativePosition = IN.worldPosition.xy - center; // ピクセルの円の中心からの相対位置
                float fragDistance = length(relativePosition); // ピクセルの円の中心からの距離
                float2 edgePosition = normalize(relativePosition / halfExtents) * halfExtents; // ピクセルの方角におけるエッジの位置
                float edgeDistance = length(edgePosition); // エッジの円の中心からの距離
                float maskAlpha = 1.0 - smoothstep(edgeDistance - 0.5, edgeDistance + 0.5, fragDistance); // ピクセルがエッジよりも半ピクセル以上内側なら1、半ピクセル以上外側なら0、中間なら位置に応じた半透明

                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                color.a *= maskAlpha; // イメージのアルファにマスクをかける

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                return color;
            }
        ENDCG
        }
    }
}

このようなマテリアルを、Maskコンポーネントではなくイメージの方のマテリアルに使用します。すると下図のように、縁の部分がなめらかに切り抜かれました。

切り抜き部分の拡大図
プレビュー

渋谷ほととぎす通信さんでも言及されているように、この方式だと個々のイメージに個別にマスキングを行わなければならないのが弱点ですが、代わりにサイトでの例のように、グラデーションのかかったような任意のアルファでも切り抜ける利点があるかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/19 11:10

    ご回答ありがとうございます。
    マスクに滑らかな円を出せばいいと思っていました。
    言われてみると確かに、マスク自体を滑らかにしたところで意味がないですね。そもそも質問が的外れでした。

    マスクコンポーネントは透明度を渡さないのならば、渋谷ほととぎす通信さんのようにシェーダー自体をマスクとして利用させる方法がスマート(他に方法がない?)のようです。
    ご回答いただいたコードを勉強させて頂きます。
    ありがとうございました!

    キャンセル

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

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