3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Unity] Dither11種を比較してみた

Last updated at Posted at 2025-11-13

はじめに

こんにちは、ユーゴです。今回は、Unityのシェーダー表現において、ディザーを11種類比較してみました。
プロジェクトにディザーの導入を検討されている方は、ぜひご参考にしてください。

成果物

プロジェクト丸ごと共有します。
mu5dvlp/dither-test

動画も撮ってみたのですが、再生環境次第でかなり表示が変わってしまう上に、再生する解像度次第で分かりにくかったので、やめました。

ディザーについて

一応、軽く説明します。知ってる方は「比較」までスキップしてください。

そもそもディザーとは?

言葉の定義通りなら、「意図的にノイズを加えることで、滑らかにする技法」です。
Unityのシェーダーに特定して言うなら、「不透明表現に描画クリップを意図的に加えることで、透明表現を行う技法」といったところです。

なんで必要なの?

通常、不透明のOpaqueでは、「Zバッファ保持 → Zテスト → 描画」という処理が行われます。この時、Zテストで負けたピクセルは「破棄」されるので、描画処理までしなくて済みます。

一方、透明のTransparentでは、通常Zテストを行いません。後ろにいても、透明で透けて見えることがあるからです。
代わりに、半透明の色同士を混ぜ合わせる「アルファブレンディング」という処理が入ります。アルファブレンディングでは、既に書き込まれたカラーバッファを読み出して、新しいピクセルとの加算処理を行います。そのせいで、メモリも倍食います。

具体的なプラットフォームや数値は言えませんが、VRゲーム開発では、ディザーを重宝します。ハードウェアのスペックとの戦いや、公式ストアでリリースするための厳しいFPS制約を乗り越えないといけないためです。

どれを使えばいいの?

極論、プロジェクトの目的に応じて使い分けてください。

目的 おすすめ
景観 Texture
軽量化 Dither17, Pulse5Int, Bayer16

個人的な格付け
🥇 Dither17
🥈 Pulse5Int
🥉 Bayer16

軽量化ポイントは、如何にメモリを使わないか、が論点となります。極端なこと(if使用、Transparentキュー使用など)をしない限りは、基本重たくはなりません。
Bayer系統は配列のせいで静的メモリの

景観の1つの評価ポイントには、モアレがあります。
モアレにも2種類あり、「表示上のモアレ」「物理スクリーンのモアレ」です。

「表示上のモアレ」は、おそらくアンチエイリアスなどソフト(特にUnity)のレンダリング上で発生、もしくは物理ピクセル格子との整合性が取れず発生する、縞模様のことです。これはまあいい。

▼ 表示上のモアレ

「物理スクリーンのモアレ」は、物理ピクセルや覗き防止フィルタなどとディザーの模様が干渉し合い、形成される縞模様です。様々な要因で、肉眼でも見えちゃう場合があります。
これがかなりキツい。体を少し動かすとモアレも一緒に動くので、酔いの原因になります。
スマホカメラでPCを撮影した時に、縞模様が発生する感じのやつです。(最近のスマホカメラは優秀なので、発生しないかもですが...)

▼ 物理スクリーンのモアレ
物理スクリーンのモアレ.jpg

比較

Bayer系

参考:配列ディザリング - Wikipedia

個人的に、数字的美学を感じにくい苦渋のディザーです。ゴリ押しです。
しかし、「そういうマスクだから」と言われたら言い返せない、そんなラインです。

Bayer4

階調数
表示モアレ
物理スクリーンモアレ

bayer4.png

最もシンプルなディザーの1つです。2x2の正方形領域について、X字のようにクリップしていきます。

{無, 3, 32, 321, 3210}の5階調表示となっており、透明度の段差がキツいと感じる場合もあるかもしれません。

表示モアレはありますが、正方形領域であることや、5階調しかないことから、比較的目に優しいです。

ただし、環境次第ではキツめな物理スクリーンのモアレが発生します。

bayer4
Shader "Dithers/Dither_bayer4"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            static const uint ditherMap[4] = {
                0, 2,
                3, 1
            };

            float Bayer4(float2 Pos)
            {
                return ditherMap[(uint(Pos.x) % 2) + (uint(Pos.y) % 2) * 2]/4.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Bayer4(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Bayer16

階調数
表示モアレ
物理スクリーンモアレ

bayer16.png

最も有名なディザーの1つです。4x4の正方形領域について、X字にさらにX字を重ねたような、幾何学的なクリップを行います。

非表示を含めた17階調表示となっており、透明度の段差も滑らかとなっております。

表示モアレが発生するものの、正方形領域で幾何学的なクリップなので、比較的目に優しいです。

同様に、環境次第ではキツめな物理スクリーンのモアレが発生します。

bayer16
Shader "Dithers/Dither_bayer16"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            static const uint ditherMap[16] = {
                0, 8, 2, 10,
                12, 4, 14, 6,
                3, 11, 1, 9,
                15, 7, 13, 5
            };

            float Bayer16(float2 Pos)
            {
                return ditherMap[(uint(Pos.x) % 4) + (uint(Pos.y) % 4) * 4]/16.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Bayer16(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Bayer64

階調数
表示モアレ
物理スクリーンモアレ

図は大変なので省略します...

8x8の正方形領域について、X字にさらにX字を重ね、さらにX字を重ねたような、幾何学的なクリップを行います。ここまで来るとお分かりかもしれませんが、多分Bayer Matrixは$2^n \times 2^n$に無限に増やせます。

非表示を含めた65階調表示となっており、透明度の段差も滑らかとなっております...が、ここまで来ると、正直人目に見て分かりません。

ここに来て、表示モアレが若干キツくなります。何事も適量が大事、ということでしょうか。

同様に、環境次第ではキツめな物理スクリーンのモアレが発生します。

bayer64
Shader "Dithers/Dither_bayer64"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            static const uint ditherMap[64] = {
                0, 48, 12, 60, 3, 51, 15, 63,
                32, 16, 44, 28, 35, 19, 47, 31,
                8, 56, 4, 52, 11, 59, 7, 55,
                40, 24, 36, 20, 43, 27, 39, 23,
                2, 50, 14, 62, 1, 49, 13, 61,
                34, 18, 46, 30, 33, 17, 45, 29,
                10, 58, 6, 54, 9, 57, 5, 53,
                42, 26, 38, 22, 41, 25, 37, 21
            };

            float Bayer64(float2 Pos)
            {
                return ditherMap[(uint(Pos.x) % 8) + (uint(Pos.y) % 8) * 8]/64.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Bayer64(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Tile-H

階調数
表示モアレ
物理スクリーンモアレ

tileh.png

自作ディザーで、個人的に力作です。クリップ順を守りたかったために配列で実装するというゴリ押しを通しました。
そのため、本来は後述のPulseに区分できたと思うのですが、Bayerの区分にしました。

階調数は8階調なので良し。

表示モアレは...まあ悪くないでしょう。どちらかと言うとある。Bayer16くらいかな?

残念ながら、物理スクリーンモアレが酷いです。

tile-H
Shader "Dithers/Dither_tileH"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            static const uint ditherMap[49] = {
                0,5,4,1,2,3,6,
                1,2,3,6,0,5,4,
                6,0,5,4,1,2,3,
                4,1,2,3,6,0,5,
                3,6,0,5,4,1,2,
                5,4,1,2,3,6,0,
                2,3,6,0,5,4,1,
            };

            float TileH(float2 Pos)
            {
                uint2 p = uint2(Pos);
                return ditherMap[(p.x % 7) + (p.y % 7) * 7]/7.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(TileH(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

余談ですが、以下のようにタイリングしていれば、晴れてPulse系統の仲間入り、綺麗な数値計算で収まった訳ですね。
どうしてかって?先生からの宿題です、考えてみてください。

tileh-alt.png

Asiro3

階調数
表示モアレ
物理スクリーンモアレ

asiro3.png

こちらも自作のディザーとなります。網代格子をイメージして作ったディザーです。
最初はネタ枠で思いついて実装するつもりがなかったのですが、Asiro3の制作で1点プラクティスがあったので、後述します。

▼網代格子
asiro3 about.png

階調数は10。十分合格でしょう。

表示モアレについてですが、「ナシではないが見えちゃう」くらいですね。どの階調でも頻繁に発生してしまうので、❌とします。
縞目模様に見えるので、これはこれで使い道があるか...?

物理スクリーンモアレも、ちょっとキツいです。

また、ネタ枠として思いついたディザーですが、1点重要なプラクティスを発見しました。
それは、模様が目の錯覚を引き起こすということです。

asiro3 目の錯覚.png

環境次第で伝わりにくいのですが、画面がぐにゃぐにゃと歪みます。すいません、やっぱり、リポジトリをダウンロードしてもらえますか...?()
基本どのディザーでも発生しない現象なので評価項目として取り扱いませんが、自作ディザーを何個も開発する方は覚えておいて損はないと思います。

asiro3
Shader "Dithers/Dither_asiro3"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            static const uint ditherMap[36] = {
                0, 1, 2, 6, 3, 0,
                3, 4, 5, 7, 4, 1,
                6, 7, 8, 8, 5, 2,
                2, 5, 8, 8, 7, 6,
                1, 4, 7, 5, 4, 3,
                0, 3, 6, 2, 1, 0
            };

            float Asiro3(float2 Pos)
            {
                return ditherMap[(uint(Pos.x) % 6) + (uint(Pos.y) % 6) * 6]/9.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Asiro3(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Pulse系

Diagonal4

階調数
表示モアレ
物理スクリーンモアレ

diagonal4.png

こちらは自作のディザーとなります。
また、名前がPulseではありませんが、後述のPulse5IntやPulse9Intとアルゴリズムが酷似しているため、Pulse系として分類しました。

非表示を含めた5階調表示となっており、透明度の段差が若干キツいと感じるかもしれません。

表示上のモアレは優しめです。

物理スクリーンモアレが非常にキツい印象でした。

diagonal4
Shader "Dithers/Dither_diagonal4"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            float Diagonal4(float2 Pos)
            {
                uint2 p = uint2(Pos);
                return (p.x + p.y)%4/4.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Diagonal4(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Dice5

階調数
表示モアレ
物理スクリーンモアレ

dice5.png

その名の通り、サイコロの5の目をイメージして自作したディザーとなります。

階調数は6階調、ギリ合格でしょう。

表示モアレはキツいです。

物理スクリーンモアレは、感じませんでした。

dice5
Shader "Dithers/Dither_dice5"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            float Diagonal4(float2 Pos)
            {
                uint2 p = uint2(Pos);
                return (p.x + p.y*3)%5/5.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Diagonal4(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Pulse5Int

階調数
表示モアレ
物理スクリーンモアレ

pulse5int.png

参考:Tech Note: Shader Snippets for Efficient 2D Dithering

こちらは参考記事にピクセルの配置が示されていたので、そこから関数は自分で作りました。
やってることはほとんどDice5と変わらないです。

表示上のモアレはかなりキツいです。

物理スクリーンのモアレは全くない印象でした。

pulse5int
Shader "Dithers/Dither_pulse5int"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            float Pulse5Int(float2 Pos)
            {
                return (Pos.x + (Pos.y*2))%5/5.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Pulse5Int(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

Pulse9Int

階調数
表示モアレ
物理スクリーンモアレ

pulse9int.png

こちらは、Pulse5Intにインスパイアされて、私が自作で考案したアルゴリズムとなります。

表示モアレは基本大丈夫かな...と思いましたが、画面サイズをいじるとエグいのが出ました。今回の比較で最大級の表示モアレです。実用的ではないでしょう。

物理スクリーンのモアレは、Pulse5Intよりも少し多い印象でしたが、気にならない程度でした。

pulse9int
Shader "Dithers/Dither_pulse9int"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            float Pulse9Int(float2 Pos)
            {
                return (Pos.x + (Pos.y*2))%9/9.0;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Pulse9Int(screenPos) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

その他

Dither17

階調数
表示モアレ
物理スクリーンモアレ

参考:Tech Note: Shader Snippets for Efficient 2D Dithering

個人的には、階調数・表示モアレ・物理スクリーンモアレ全てバランスが取れている、一番いいDitherだと思います。

関数を見ただけでは、何をやっているかさっぱりだったので、図がありません...
挙動を見る限り、Pulse系の親戚な気もします。

無を含め18階調でしょうか?

表示モアレはほぼ感じません。

物理スクリーンモアレもほぼ無しと言って良いでしょう。

dither17
Shader "Dithers/Dither_dither17"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 _Color;
            float _Alpha;

            float Dither17(float2 Pos, float FrameIndexMod4)
            {
                return frac(dot(float3(Pos.xy, FrameIndexMod4), uint3(2, 7, 23) / 17.0f));
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;
                screenPos *= _ScreenParams.xy;
                clip(Dither17(screenPos, 0) - (1-_Alpha));

                return _Color;
            }
            ENDCG
        }
    }
}

宣伝が入りますが、3Dマーダーミステリーゲーム「黒焦げのあなた」にて、Dither17が採用されています!
2025/11/11現在は未リリースなので、リリース後にぜひプレイして、どこに採用されているか見つけてみてください...!

Texture

tex1.jpg

tex2.jpg

正直、ディザーの枠組みですらないのですが、一応入れておきます。私は断固としてディザーと認めません(過激)。

個人的にはディザーの旨みがテクスチャのサンプリングで弱まっているので、あくまで映像表現としての利活用のみかなと思っています。全くの無意味ではないですが、数字に対する美学や尊敬を感じませんね(過激)。

階調は無限です(投げやり)。テクスチャのグラデーション精度に依存しますので、実際のところ256階調が限界でしょう。

表示モアレ、物理スクリーンモアレはないでしょう。これは利点かもしれません。これまでのディザーはピクセルレベルでクリップしていたので、発生していました。

texture
Shader "Dithers/Texture"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _DitherTex ("Dither Texture", 2D) = "white" {}
        _UvScale ("UV Scale", Vector) = (3,3,0,0)
        _Alpha("Alpha", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 screenPos : TEXCOORD0;
            };

            float4 _Color;
            sampler2D _DitherTex;
            float4 _DitherTex_TexelSize;
            float4 _UvScale;
            float _Alpha;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.screenPos = ComputeScreenPos(o.pos);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 screenPos = i.screenPos.xy / i.screenPos.w;

                float screenAspect = _ScreenParams.x / _ScreenParams.y;
                float texAspect = _DitherTex_TexelSize.z / _DitherTex_TexelSize.w;
                float2 scale = 1.0;
                scale.x = (screenAspect > texAspect)? screenAspect / texAspect : 1.0;
                scale.y = (screenAspect < texAspect)? texAspect / screenAspect : 1.0;

                fixed2 uv = (screenPos - 0.5) * scale + 0.5;
                uv = frac(uv * _UvScale.xy);
                fixed4 col = tex2D(_DitherTex, uv);

                clip(col.x - (1 - _Alpha));

                return _Color;
            }
            ENDHLSL
        }
    }
}

テクスチャの例も貼っておきます。こんなのでよければ、ご自由に改変、再配布、商用利用してください。

tex_dither.png

tex_dither2.png

補足

自作ディザーのコツ

正直、数字遊びだと思っています。

dice5.png

Pulse系統が分かりやすいですが、x軸に注目すると、綺麗に「0→1→2→...」となっているのが分かります。「そうなるように」意図的に並べているわけです。
あとは、y軸は「0→3→1→4→2」だから...$f=\mod(3y,5)$か、と頭で計算してあげれば良いです。

数字遊びが苦手な方にとっては、苦行でしかありません笑

また、TileHの実装についてはゴリ押しですが、対象ピクセル数(TileHなら7枚)の2乗分だけ配列を用意すれば、どうにかなります。なので、規則的なタイリングであれば、理論上実装できないものはないでしょう。

本番用途なら、オフセットを入れよう

お節介かもですが、同じ種類のDitherを使用したオブジェクトが重なると、当たり前ですが後ろのオブジェクトは表示されません。オフセットみたいなパラメータを導入して、対象ピクセルを1つずらすなどで対応してください。

ifは使わないこと

こちらもお節介だとは思いますが、ifは重いので使わないでください。ifを使用し過ぎると、稀に描画が間に合わず、表示乱れすることがあります。
三項演算子は軽いらしいですよ。

まとめ

いかがだったでしょうか。今回は、Unityのシェーダーにおいて、ディザー11種類を比較してみました。かなりニッチな内容となりましたが、グラフィックデザインを意識するゲームクリエイターの方には、刺さるものがあると嬉しいです。

このように、ゲーム制作に関わる知識から、AWS, GAS, 運用, 量子コンピューティングなど、幅広いジャンルのトピックを取り扱っております。
お役に立てましたら、いいね・フォローなどよろしくお願いします!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?