HLSLでのDissolve Shaderの実装について

Dissolve Shaderとは次のチュートリアル動画で説明されているものです。

https://www.youtube.com/watch?v=taMp1g1pBeE

動画ではShader Graphで実装されていますが、今回はHLSLの勉強も兼ねて、HLSLでDissolve Shaderを実装しました。本記事ではHLSLでのDissolve Shaderの実装について説明します。

今回はこのようなものを作ります（Bloomをpost processingとして加えています）。
![Shader_DOJO_-_Dissolve_-_Windows__Mac__Linux_-_Unity_2021_2_1f1_Personal__DX11__2022-03-20_20-10-32_AdobeCreativeCloudExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191216/e54cb34c-3da1-b88e-dba3-10795dc3d1b6.gif)


プログラムは[GitHub](https://github.com/JunNishimura/Shader-DOJO/blob/main/Assets/Shaders/Dissolve.shader)にて公開しています。

## 方針
大まかな実装の流れを説明します。
まず、次のようなNoise Textureを用意します。
![noise.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191216/2441b71e-cfed-4466-71eb-5707da5ab646.jpeg)
このテクスチャの各画素をアルファ値に変換します。グレースケールなので0.0 ~ 1.0の値が得られます。

ユーザーが任意に指定できる閾値を用意し、その閾値以下のアルファ値を持つ画素を消すという処理を記述する事で実現できます。

## 実装
それではコードを見ていきたいと思います。

```hlsl
Shader "Custom/Dissolve"
{
    Properties
    {
        [HDR] _BaseColor ("Color", Color) = (1,1,1)
        [HDR] _EdgeColor ("Dissolve Color", Color) = (0, 0, 0)
        _MainTex ("Texture", 2D) = "white" {}
        _DissolveTex ("Dissolve Texture", 2D) = "white" {}
        _AlphaClipThreshold ("Alpha Clip Threshold", Range(0,1)) = 0.5
        _EdgeWidth ("Disolve Margin Width", Range(0,1)) = 0.01
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

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

            fixed4 _BaseColor;
            fixed4 _EdgeColor;
            half _AlphaClipThreshold;
            half _EdgeWidth;

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _DissolveTex;
            float4 _DissolveTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 edgeCol = fixed4(1, 1, 1, 1);
                
                // noise textureからalpha値を取得
                fixed4 dissolve = tex2D(_DissolveTex, i.uv);
                float alpha = dissolve.r * 0.2 + dissolve.g * 0.7 + dissolve.b * 0.1;

                // dissolveを段階的な色変化によって実現する
                if (alpha < _AlphaClipThreshold + _EdgeWidth && _AlphaClipThreshold > 0) {
                    edgeCol = _EdgeColor;
                }
                if (alpha < _AlphaClipThreshold) {
                    discard;
                }

                fixed4 col = tex2D(_MainTex, i.uv) * _BaseColor * edgeCol;
                return col;
            }
            ENDCG
        }
    }
}
```

まずPropertiesにて必要な変数を定義します。
```hlsl
Properties
{
    [HDR] _BaseColor ("Color", Color) = (1,1,1)
    [HDR] _EdgeColor ("Dissolve Color", Color) = (0, 0, 0)
    _MainTex ("Texture", 2D) = "white" {}
    _DissolveTex ("Dissolve Texture", 2D) = "white" {}
    _AlphaClipThreshold ("Alpha Clip Threshold", Range(0,1)) = 0.5
    _EdgeWidth ("Disolve Margin Width", Range(0,1)) = 0.01
}
```
ここで注目してほしいのは_AlphaClipThresholdで、この変数がクリッピングの閾値になっています。


次に、フラグメントシェーダーを見ます。
```hlsl
fixed4 frag (v2f i) : SV_Target
{
    ...
    
    // noise textureからalpha値を取得
    fixed4 dissolve = tex2D(_DissolveTex, i.uv);
    float alpha = dissolve.r * 0.2 + dissolve.g * 0.7 + dissolve.b * 0.1;

    ...
}
```
まずnoise画像からアルファ値を取得します。そして、このアルファ値が先程定義した_AlphaClipThresholdより小さい画素に対してはdiscardするようにします。
```hlsl
if (alpha < _AlphaClipThreshold) {
    discard;
}
```

ここまででDissolve Shaderは完成です。

必須ではありませんが、消えていく部分のエッジを光らせたいので次のコードを加えます。
```hlsl
Properties
{
    ...
    [HDR] _EdgeColor ("Dissolve Color", Color) = (0, 0, 0)
    ...
    _EdgeWidth ("Disolve Margin Width", Range(0,1)) = 0.01
}
```
_EdgeColorが光らせる色で_EdgeWidthが幅になります。

そして次のように、閾値_AlphaClipThresholdを増やす事でクリッピングするタイミングを早めます。
```hlsl
if (alpha < _AlphaClipThreshold + _EdgeWidth && _AlphaClipThreshold > 0) {
    edgeCol = _EdgeColor;
}
```
最後にこのedgeColを出力します。
```hlsl
fixed4 col = tex2D(_MainTex, i.uv) * _BaseColor * edgeCol;
return col;
```

以上で完成です。

## 終わりに
今回はDissolve Shaderをhlslで実装しました。
アウトプットを続けて表現の幅を広げていきたいです。


## 参考資料
- [DISSOLVE using Unity Shader Graph](https://www.youtube.com/watch?v=taMp1g1pBeE)
- [【Unityシェーダ入門】Dissolve（溶けるような）シェーダをつくる](https://nn-hokuson.hatenablog.com/entry/2017/04/14/204822)