お米 is ライス

C#やらUnityやらを勉強していて、これはメモっといたほうがええやろ、ということを書くつもりです

【Cg Programming/Unity】透過 ~ 透明な物体の描画【順番にやっていく】

今回はこちら。
en.wikibooks.org
この節では透過について学ぶ。
サンプル画像として載っている絵画がエッチでかわいい。

透過

例えば色ガラスを覗いたときのように、向こうのものが透けて見えるオブジェクトを描画したい場合、透けて見える景色の色と、色ガラス自身の色とを混ぜ合わせた色を描画する必要がある。
このような操作をBlendingと呼ぶ。
大抵の場合、向こうの景色の色を何割、色ガラスの色を残りの何割といったような感じで混ぜ合わせるのだが、何割という値を指定するのにColorの4番目のパラメータであるα値を用いる。このようなBlendingを特にアルファブレンディングと呼ぶ。

用語

これから描画しようとしているフラグメントシェーダの出力値をSource、すでに描画されている背景のことをDestinationと呼ぶ。

一般的なアルファブレンディング

一般的なアルファブレンディングでは最終的に描画される色は以下のようになっている。

result = SrcAlpha * SrcColor + (1 - SrcAlpha) * DstColor

ただし、SrcAlphaは4つのパラメータすべてがSourceのアルファ値であるfloat4ベクトル。
SrcColorはそのままSourceの色(float4)、DstColorDestinationの色(float4)である。

このようなブレンディングを行いたい場合、コード上のCGPROGRAMの外側で以下のように指定してやる。

Blend SrcAlpha OneMinusSrcAlpha

Blendの後ろ、1つ目にSourceの色に掛け合わせる値(float4)の名前、2つ目にDestinationの色に掛け合わせる値(float4)の名前を指定する。

Blend係数

SrcAlphaのように指定できる値はBlend係数と呼び、ほかにも色々ある。

One

float4(1, 1, 1, 1)

Zero

float4(0, 0, 0, 0)

SrcColor

Sourceの色(フラグメントシェーダで返す値)

SrcAlpha

SrcColor.aaaa

DstColor

背景の色

DstAlpha

DstColor.aaaa

OneMinusSrcColor

float4(1, 1, 1, 1) - SrcColor

OneMinusSrcAlpha

float4(1, 1, 1, 1) - SrcAlpha

詳しくは公式マニュアルで。(ただし時々翻訳が頼りないので注意)
docs.unity3d.com

コード

実際にBlendingを行うシェーダのコードは以下のようになる。

Shader "Custom/Transparency"
{
    SubShader
    {
        Tags { "Queue" = "Transparent" }
        pass
        {
            ZWrite Off
            Cull Front

            //アルファブレンディングの形式を指定
            //この場合はSrcAlphaがこのパスで出力する色のアルファ値で、
            //最終的には (このパスで出力する色)xSrcAlpha + (背景にすでに描画されている色)x(1-SrcAlpha)がピクセルの色になる
            Blend SrcAlpha OneMinusSrcAlpha
            //Blend One Zero    //こんな書き方をすると単純に上書きしているのと同じ
            //Blend SrcColor DstColor   //こんな書き方をすると色の2乗同士を足し合わせることになるが、使う場面が思いつかない

            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct v2f
            {
                float4 position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            v2f v(float4 vertexPos : POSITION)
            {
                v2f output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.color = float4(vertexPos.xyz, 0.5) + float4(0, 0, 0, 0);
                return output;
            }

            float4 f(v2f input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }
        
        pass
        {
            ZWrite Off
            Cull Back
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct v2f
            {
                float4 position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            v2f v(float4 vertexPos : POSITION)
            {
                v2f output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.color = float4(vertexPos.xyz, 0.5) + float4(1, 1, 1, 0);
                return output;
            }

            float4 f(v2f input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }
    }
}

まず裏面をアルファブレンディングで背景に重ねて描画し、そのあとに表面を同じように重ねて描画している。
こうすることによってCubeが透明な箱のように見える。
わかりやすくするため同じシェーダを適用したCubeをもう一つ少しずらした位置に置いてある。
f:id:spi_8823:20200505235624p:plain

注意点

さて、ここで少し注意しておきたいのが、描画がオブジェクト単位で行われるということだ。
例えば箱同士が重なっている場合、正確には箱①表、箱②表、箱①裏、箱②裏の順に描画されてほしいところだがこの方法だと箱①表、箱①裏、箱②表、箱②裏になってしまう。
これを解決する方法はのちのち出てくると思う。

と思っていたら次の節で解決方法が1つ示されていた。
spi8823.hatenablog.com