【Unity】【Shader】Blendを試してみる

Unityのシェーダーのコードを読んでいるとBlendは当たり前のように出てくるが、重要そうな割に全然理解していなかったので、いくつかサンプルを作りながら理解してみる。

#Blend SrcFactor DstFactor

よく見るパターンはこれ。``` Blend SrcFactor DstFactor ```

[公式ページ](https://docs.unity3d.com/Manual/SL-Blend.html)には次のように説明されている。

> Blend SrcFactor DstFactor: Configure and enable blending. The generated color is multiplied by the SrcFactor. The color already on screen is multiplied by DstFactor and the two are added together.

### SrcFactor

> The generated color is multiplied by the SrcFactor. 

つまり、**"Fragmentシェーダーでreturnされた色"**にSrcFactorを乗算する。
### DstFactor

> The color already on screen is multiplied by DstFactor

該当Shaderが処理される前に、すでに画面に描画されている色にDstFactorを乗算する。

### 最終的に描画される色

上記二つの色を加算するので、次のようになる。

``` Shaderで計算した色 * SrcFactor + 既に画面に描画されている色 * DstFactor ```

# 検証

青(0,0,1)のPlaneの上方向に、赤(1,0,0)から緑(0,1,0)のグラデーションテクスチャをセットしたQuadを配置。このQuadのにセットしたシェーダーでBlendを変更してみる。

<img width="532" alt="スクリーンショット 2019-10-08 9.58.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/2288480c-a300-7c09-09fe-310b124f334f.png">

# Blend factors

公式ページによるとBlend factorは10個あって、SrcFactorとDstFactorに設定できるので、合計100通りの組み合わせがあることにある。
全部確認するのは大変なので、その内のいくつかを試してみる。

## One, Zero

- Shaderで計算した色 * One = Shaderで計算した色
- 既に画面に描画されている色 * Zero = (0,0,0)

っといった風に各色をそのまま使用するか、あるいは黒にしてしまう。

### Blend One One

赤(1,0,0) * 1 + 青(0,0,1) * 1 = (1,0,1)
から
緑(0,1,0) * 1 + 青(0,0,1) * 1 = (0,1,1)
へのグラデーションになっているっぽい

<img width="232" alt="スクリーンショット 2019-10-07 9.37.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/b5cf985d-8e41-05c7-bffe-7193ffc7b73d.png">

### Blend Zero One

シェーダーで計算された色に0を乗算するので、既にあるPlaneの色だけが描画される。

赤(1,0,0) * 0 + 青(0,0,1) * 1 = (0,0,1)
緑(0,1,0) * 0 + 青(0,0,1) * 1 = (0,0,1)

<img width="564" alt="スクリーンショット 2019-10-07 9.42.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/d7e08fb5-9e36-c921-966a-3a47fcd67919.png">

### Blend One Zero

既にあるPlaneの色に0をかけるので、グラーデーションの色が描画される

赤(1,0,0) * 1 + 青(0,0,1) * 0 = (1,0,0)
緑(0,1,0) * 1 + 青(0,0,1) * 0 = (0,1,0)

<img width="526" alt="スクリーンショット 2019-10-07 9.46.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/71879633-7705-329b-9c6e-f7e974ed504e.png">

### Blend Zero Zero

シェーダーで計算された色、既にあるPlaneの色のどちらにも0を乗算するので、黒(0,0,0)になる

赤(1,0,0) * 0 + 青(0,0,1) * 0 = (0,0,0)
緑(0,1,0) * 0 + 青(0,0,1) * 0 = (0,0,0)

<img width="554" alt="スクリーンショット 2019-10-07 9.48.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/ff8ba0a0-93e5-a718-6fd9-0770b9bd4408.png">

## SrcColor, DstColor

### SrcColor
> The value of this stage is multiplied by the source color value.

Shaderで計算された色を乗算する

### DstColor
> The value of this stage is multiplied by frame buffer source color value

既に描画されている（実際にはバッファに保存されている？）色を乗算する。

### Blend Zero SrcColor

シェーダーで計算した色とPlaneの色は被っている要素がないので、Quadを配置した部分は黒(0,0,0)になる。

赤(1,0,0) * 0 + 青(0,0,1) * 赤(1,0,0) = (0,0,0)
緑(0,1,0) * 0 + 青(0,0,1) * 緑(0,1,0) = (0,0,0)

<img width="308" alt="スクリーンショット 2019-10-08 7.28.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/52169bdb-50ac-b6f2-3e73-0c9b0b63511f.png">

### Blend DstColor Zero

この場合もQuadは黒になる。

赤(1,0,0) * 青(0,0,1) + 青(0,0,1) * 0 = (0,0,0)
緑(0,1,0) * 青(0,0,1) + 青(0,0,1) * 0 = (0,0,0)

### Blend SrcColor Zero

シェーダーで計算した色を２乗することになるので、全体的に暗くなる。
例えば(0.5, 0.5, 0)の点を考えると

(0.5, 0.5, 0) * (0.5, 0.5, 0) + 青(0,0,1) * 0 = (0.25, 0.25 ,0)

<img width="260" alt="スクリーンショット 2019-10-08 8.30.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/1d1f83c8-5a39-0d25-dbb3-562efb36e578.png">

## OneMinusSrcColor, OneMinusDstColor

### OneMinusSrcColor

> The value of this stage is multiplied by (1 - source color).

(1 - Shaderで計算された色)を乗算する。仮に計算された色が(0.3, 0.3, 0.3)だとしたら、(0.7, 0.7, 0.7)が乗算される。

### OneMinusDstColor

> The value of this stage is multiplied by (1 - destination color).

### Blend Zero OneMinusSrcColor

Planeの青だけになる。

赤(1,0,0) * 0 + 青(0,0,1) * (1 - (1,0,0))
= 0 + (0,0,1) * (0,1,1)
= (0,0,1)

緑(0,1,0) * 0 + 青(0,0,1) * (1 - (0,1,0))
= 0 + (0,0,1) * (1,0,1)
= (0,0,1)

<img width="280" alt="スクリーンショット 2019-10-08 8.54.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/64f1865e-5b2e-349a-363c-cfe6228a6f0e.png">

### Blend OneMinusSrcColor Zero

(0.5,0.5,0)点で考えると、
(0.5,0.5,0) * (1 - (0.5,0.5,0)) + 青(0,0,1) * 0
= (0.5,0.5,0) * (0.5,0.5,1) + 0
= (0.25,0.25,0)

画像はちょっと分かりにくいが、暗い黄色のグラデーションになる。

<img width="467" alt="スクリーンショット 2019-10-08 9.39.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/2b41e4a6-2a02-bf50-636d-2e67c3f53f39.png">

## アルファも試してみる

アルファによる変化がわかりやすいようにPlaneのテクスチャを以下のような画像に変更。

<img width="239" alt="スクリーンショット 2019-10-09 13.44.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/a0dfecec-5b17-9cb5-fac0-70b5505a29c6.png">

## SrcAlpha

> The value of this stage is multiplied by the source alpha value.

Shaderで計算されたアルファを乗算する。

## DstAlpha

> The value of this stage is multiplied by frame buffer source alpha value.

フレームバッファに保存されたアルファを乗算する。

## OneMinusSrcAlpha

> The value of this stage is multiplied by (1 - source alpha).

## OneMinusDstAlpha

> The value of this stage is multiplied by (1 - destination alpha).


### Blend SrcAlpha OneMinusSrcAlpha

公式によるとこれば一般的な透過設定とのこと。
アルファを以下の様にフラグメントシェーダーで変更した場合を考えてみる。

```アルファを指定.glsl
fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    col.a = ここを変更;
    return col;
}
```

##### 前提

フラグメントシェーダーで計算された色は赤(1,0,0)、Planeの色は白(1,1,1)の点を例にとって考えてみる。

##### アルファを0.1とした場合

col.a = 0.1の場合は以下のように計算して、ブレンド後の色は白に近くなる。

赤(1,0,0) * 0.1 + 白(1,1,1) * (1 - 0.1)
= (0.1, 0, 0) + (0.9, 0.9, 0.9)
= (1, 0.9, 0.9)

<img width="215" alt="スクリーンショット 2019-10-09 12.25.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/ce7ee638-b5d2-36c6-5406-c8fbf4047e2e.png">


##### アルファを0.9とした場合

col.a = 0.9の場合は以下のように計算して、ブレンド後の色はほぼ赤となる。

赤(1,0,0) * 0.9 + 白(1,1,1) * (1 - 0.9)
= (0.9, 0, 0) + (0.1, 0.1, 0.1)
= (1, 0.1, 0.1)


<img width="226" alt="スクリーンショット 2019-10-09 12.24.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/f19cd903-53c9-5b4b-1067-6d8388f42bcd.png">

### Blend SrcAlpha SrcAlpha

こんな設定をすることは無いだろうけど、確認のためにこの設定もためしてみる。

##### アルファを0.1とした場合

赤(1,0,0) * 0.1 + 白(1,1,1) * 0.1
= (0.1, 0, 0) + (0.1, 0.1, 0.1)
= (0.2, 0.1, 0.1)

黒に近くなる。

<img width="234" alt="スクリーンショット 2019-10-09 13.56.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/cada1e4c-472d-a5e2-4403-045dc30fe090.png">

##### アルファを0.9とした場合

白に近くなる。

赤(1,0,0) * 0.9 + 白(1,1,1) * 0.9
= (0.9, 0, 0) + (0.9, 0.9, 0.9)
= (1.0, 0.9, 0.9)

<img width="260" alt="スクリーンショット 2019-10-09 13.58.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/14a68f5c-a348-b16e-2fcd-68b91bcc7c5c.png">


#### 今回のコード  

コード自体は新規作成したUnlitシェーダーに以下の変更をしただけ。

- Fogの記述を削除
- Tagを変更
- Blendを追加

```.glsl
Shader "Unlit/SH_GradientColor"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        LOD 100

//        Blend One One

        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}
```

## 参考
https://wgld.org/d/webgl/w029.html