お米 is ライス

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

【Cg Programming/Unity】テクスチャ基礎 ~ テクスチャのアルファ値を活用する【順番にやっていく】

f:id:spi_8823:20200523202113p:plain

お~~~~~ヱ”ッほっほっほっけほっけほっ!

今日はこちらのチュートリアルをやっていきますわよ!!
en.wikibooks.org
en.wikibooks.org

この節では、テクスチャのアルファ値を活用して描画する方法を学びますわよ!
前回の内容が前提になるのでまずはそちらから見るのですわよ!せっかちなのは乙女に嫌われますの。
spi8823.hatenablog.com

前回はテクスチャのRGB値だけを使って描画していたのでしたわね!
そんなことじゃあ立派な貴族にはなれませんわ!わたくしのように第四の色を使いこなせないことには、社交界でみっともない姿をさらすだけですのよ!
この間なんて、全身アルファ値ゼロの服を纏った殿方が乱入してきて大変な騒ぎだったんですから!あなたはあんなことにならないように、ちゃんとここで勉強しておくのですわよ!

アルファ値を活用したテクスチャの描画

というわけで、あなたにはコレを渡しておきますわ。
何って、陸地の部分のアルファ値を1、海の部分のアルファ値を0にした地球の画像ですわよ。どうせ貧乏なあなたのことだから画像の一枚も持っていないと思って執事に用意させたのですわ。まったくもう、いい加減自分で画像くらい用意できるようになりなさい!
f:id:spi_8823:20200522031749p:plain

というわけで、まずは前回と同じように球の表面にテクスチャを貼り付けるんですのよ。もう一人で出来るわよね?
するとこんな風になったかしら?
f:id:spi_8823:20200522035022p:plain
こんな闇に包まれた地球なんて嫌ですけれども、今はこれが正しいんですのよ。海の部分は青色の代わりに(0, 0, 0, 0)となっていますの。このうちアルファ値を無視しているのだから真っ黒くなるのは当然ですわね。

テクスチャの透過

ではまずは一番オーソドックスなアルファ値の活用方法、アルファブレンディングをやってみるのですわ。
と言ってもtex2D関数で取得してピクセルの色として出力しているテクスチャの色情報にはすでにアルファ値が含まれているんですの。ですから、頂点シェーダやフラグメントシェーダはそのままpassをコピペして、あとは適切にアルファブレンディングを指定してやるだけで出来ますわね。
アルファブレンディングについて忘れてしまったという困った人はこの記事を見るといいのですわ!
さて、上の闇地球儀をアルファブレンディングして描画するとこんな風になりますわ。日本の裏側に南米大陸が透けて見えていますわね。
ブラジルの人~~~~!!聞こえるかしら~~~~~?!
f:id:spi_8823:20200523193131p:plain
コホン……、とまあこんな感じでテクスチャから色を取得・出力すること以外はやってることは変わらないですわね。
これだけじゃあ面白くないのでもう少しアルファ値で遊んでみるのですわ。

アルファ値を反射に適用

アルファ値は何もアルファブレンディングだけに使わないといけないわけではありませんの。
例えば、アルファ値が0のところだけ反射を描画するといったこともできますのよ。
この記事でやっているように反射による表面の色を計算して、アルファ値が0の場合には出力に反射の色を足してやればいいんですのよ。
ざっくり言えばこんな感じのフラグメントシェーダを書けばいいだけですのよ。

float4 f(v2f input) : COLOR
{
    float4 texColor = tex2D(_MainTex, input.texcoord);
    float4 reflectionColor = getReflectionColor(input);

    return texColor.a == 0 ? float4(reflectionColor.xyz, 0.7) : texColor;
}

余談なのだけど、シェーダでif文を書くのはパフォーマンス的にあまり好ましくないのですわ。
その代わりに3項演算子を使えば問題ありませんのよ。

と、そんな感じで描画したのがこれになりますわ。
鏡面反射や環境光による色がアルファ値が0の部分、すなわち海の部分だけに描画されているのがわかりますわね!
f:id:spi_8823:20200523202113p:plain

コード

Shader "Custom/TransparentTextures"
{
    Properties
    {
        _MainTex ("Main", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "Queue" = "Transparent" }

        //CGINCLUDE~ENDCGの部分に書いた関数や構造体はSubShader内で共通のものとして使うことが出来ますのよ!
        //複数のパスに同じようなコードを長々と書く必要がある場合にはこのように共通処理としてまとめて差し上げればすっきりしますのよ!
        CGINCLUDE
        uniform sampler2D _MainTex;

        struct vertexInput
        {
            float4 vertexPos : POSITION;
            float2 texcoord : TEXCOORD0;
            float3 normal : NORMAL;
        };

        struct v2f
        {
            float4 sv_position : SV_POSITION;
            float2 texcoord : TEXCOORD0;
            float4 vertexPos : TEXCOORD1;
            float3 normal : TEXCOORD2;
            float4 ambientColor : TEXCOORD3;
        };

        //法線ベクトルから環境光の計算をしてますの!
        float4 getAmbientColor(float3 normal)
        {
            float4 upAmbient = lerp(unity_AmbientEquator, unity_AmbientSky, normal.y);
            float4 downAmbient = lerp(unity_AmbientEquator, unity_AmbientGround, -normal.y);

            float4 ambientColor = upAmbient * step(0, normal.y) + downAmbient * step(normal.y, 0);
            ambientColor = ambientColor;
            return ambientColor;
        }

        //頂点シェーダのインプットからフラグメントシェーダに受け渡す値をとりまとめる関数ですわ!
        v2f vertexInput2Output(vertexInput input)
        {
            v2f output;
            output.sv_position = UnityObjectToClipPos(input.vertexPos);
            output.texcoord = input.texcoord;
            output.vertexPos = input.vertexPos;
            output.normal = input.normal;
            output.ambientColor = getAmbientColor(output.normal);

            return output;
        }

        //フラグメントシェーダのインプットから鏡面反射光の色を計算しますわ!
        float4 getReflectionColor(v2f input)
        {
            float4 worldPos = mul(unity_ObjectToWorld, input.vertexPos);
            float3 cameraDirection = (_WorldSpaceCameraPos - worldPos).xyz;
            cameraDirection = normalize(cameraDirection);

            float4 incidentVector = normalize(-_WorldSpaceLightPos0); 
            float isValid = step(dot(incidentVector, input.normal), 0);
            float3 specularDirection = incidentVector.xyz - input.normal * 2 * dot(input.normal.xyz, incidentVector.xyz);
            specularDirection = normalize(specularDirection) * isValid;

            float intensity = dot(cameraDirection, specularDirection.xyz);
            intensity = max(0, intensity);
            intensity = pow(intensity, 20);
            float4 color = float4(intensity * float3(1, 1, 1) + input.ambientColor, 1);

            return color;
        }

        //フラグメントシェーダで出力すべき色を計算しますの!
        float4 getFragmentColor(v2f input)
        {
            float4 texColor = tex2D(_MainTex, input.texcoord);
            float4 reflectionColor = getReflectionColor(input);

            //3項演算子を使って、アルファ値が0の場合は反射光を、それ以外の場合はテクスチャの色を出力しますのよ!
            return texColor.a == 0 ? float4(reflectionColor.xyz, 0.7) : texColor;
        }
        ENDCG

        pass
        {
            //まず裏面の描画
            ZWrite Off
            Cull Front
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            v2f v(vertexInput input)
            {
                return vertexInput2Output(input);
            }

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

        pass
        {
            //次に表面の描画
            ZWrite Off
            Cull Back
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            v2f v(vertexInput input)
            {
                return vertexInput2Output(input);
            }

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

アルファ値を活用したテクスチャの描画は以上になりますわ。
これであなたも社交界で人気者ですわね!