uGUIのScrollViewに於ける要素の透過処理を実装してみた

やってみた事としては下記のGIFにある様に、ScrollViewの一部に透過処理を適用してフェードさせると言ったことです。

多分AssetStoreなりGitHubなり探せば既成のものが出てくるかもしれませんが...
今回はこちらについての実装が気になったので、勉強序に試しに一例を作ってみました。
折角なので実装内容についてメモしていきます。

![fade.gif](https://qiita-image-store.s3.amazonaws.com/0/80207/c593d0e3-10d4-fa0e-d25c-86988a5c313c.gif)

- Unity2017.3.1p2で実装/動作確認
- プロジェクト一式はGitHubにアップしております。
    - [mao-test-h/FadeScrollView](https://github.com/mao-test-h/FadeScrollView)
- ※サンプルシーンは「[Assets/MyContents/Scenes/Sample.unity](https://github.com/mao-test-h/FadeScrollView/blob/master/Assets/_MyContents/Scenes/Sample.unity)」を参照





# 実装について

ScrollView内でフェードさせている部分について大まかに説明すると、こちらは透過させる為の専用Shaderを実装する形で対応しております。
シェーダー自体はbuilt-in shaderである"UI-Default.shader"をベースに改造しているので、適用したマテリアルはそのまま要素に適用することで使用可能となっております。

内容としてはC#側から透過範囲の矩形領域を渡してもらい、後はフラグメントシェーダー内で領域を判定して透過させていると言った流れとなっております。

以下にコードの一部を載せます。

◆ UI-FadeColumn.shader

```text
    Properties
    {
        .......
        
        // フェードの境界(top, bottom, left, right)
        [HideInInspector] _Border ("_Border", Vector) = (0.0, 0.0, 0.0, 0.0)
        // フェードの影響度
        _FadeIntensity ("Fade Intensity", Float) = 0.01
        // 縦ラインのフェードの有効化
        [Toggle(FADE_VERTICAL)] _OnFadeVertical ("On Fade Vertical", Float) = 0
        // 横ラインのフェードの有効化
        [Toggle(FADE_HORIZONTAL)] _OnFadeHorizontal ("On Fade Horizontal", Float) = 0
    }

    .......
    
            float4 _Border;
            float _FadeIntensity;

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                // 境界の中央の算出
                float2 center = float2(lerp(_Border.z, _Border.w, 0.5), lerp(_Border.x, _Border.y, 0.5));

                // 境界の中央から離れるに連れて薄くしていく
                #if FADE_VERTICAL
                if(IN.worldPosition.y >= center.y || IN.worldPosition.y < center.y)
                {
                    // 縦ライン
                    float scaleY = (_Border.x - _Border.y) / 2.0;
                    float offsetY = IN.worldPosition.y - center.y;
                    color.a -= ((abs(offsetY) / scaleY)) * _FadeIntensity;
                }
                #endif
                #if FADE_HORIZONTAL
                if(IN.worldPosition.x >= center.x || IN.worldPosition.x < center.x)
                {
                    // 横ライン
                    float scaleX = (_Border.z - _Border.w) / 2.0;
                    float offsetX = IN.worldPosition.x - center.x;
                    color.a -= ((abs(offsetX) / scaleX)) * _FadeIntensity;
                }
                #endif
                
                .......
            }
```

# 補足

- 最初はC#側で要素ごとに全体的に透過させると言った実装も考えてみましたが、C#側でScrollViewの要素全体に透過処理をかけて制御すると、透過部分が離散的な感じとなり見掛け的に微妙になりそうな懸念があったので、一先ずは連続的(グラデーションが掛かるよう)に表示するためにフラグメントシェーダー内で対応してみたと言った経緯があります。
    - ※とは言え、要素ごとに離散的に透過を掛けたとしても実際に見てみたらそんなに違和感が無かったりするかもしれないので、そちらについては要検証です。。

  
- 現状の処理としてはフラグメントシェーダー内でif分岐などを行っているので処理コストが少し高いと言った懸念があります。
    - これに対し[optimizeブランチ](https://github.com/mao-test-h/FadeScrollView/tree/optimize)の方では「ScrollRectの中心座標を渡す」と言った形に変更しており、全体的に計算処理を減らしているのでひょっとしたらこちらの方が軽いかもしれません。(実際にプロファイリングをして比べたわけではありませんが...未検証です。。)
        - ※但し、現状こちらは上下のみしかフェードに対応していないので左右もフェードさせるとなると改造する必要が出てきます。
        
- 今回の実装例としてはあくまでサンプルまでとなるので、仮に本格的に使うとするなら要素のマテリアルの設定周りの自動化を考えるなどした方が使いやすいかもしれません。