夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Unityで画像が浮かび上がるShaderを書く

本日はShader調査枠です。

〇作ったShader

f:id:Holomoto-Sumire:20201105222454j:plain

今回は文字や画像が浮かび上がるようなShaderを作成します。

ゲーム、アニメ、映画、SF作品などでは特殊な状況に置かれると模様が浮かび上がるというシーンがよくあります。

今回はShaderの基礎的な部分を応用して文字やアイコンなどの画像が浮かび上がるShaderを書きます。

〇テクスチャを張るShader

まずベースとなるテクスチャをマッピングできるShaderを作成します。

MRTKStandardShaderをもとにしてテクスチャをマッピングできるShaderを用意しました。

redhologerbera.hatenablog.com

Shader "Custom/Texture"
{
    Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("MainTexture", 2D) = "white" {}

          [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
    }

        SubShader
            {
                Pass
                {
                    Name "Main"
                    Tags{ "RenderType" = "Transparent" "LightMode" = "ForwardBase" }
                    LOD 100
                    
                    CGPROGRAM

                    #pragma vertex vert
                    #pragma fragment frag

                    #pragma shader_feature _DIRECTIONAL_LIGHT

                    #include "UnityCG.cginc"
                    #include "UnityStandardConfig.cginc"
                    #include "UnityStandardUtils.cginc"


    #if defined(_DIRECTIONAL_LIGHT) 
                #define _NORMAL
    #else
                #undef _NORMAL
    #endif

    #if defined(_NORMAL) 
                #define _WORLD_POSITION
    #else
                #undef _WORLD_POSITION
    #endif
                #define _UV

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    // The default UV channel used for texturing.
                    float2 uv : TEXCOORD0;

                    fixed3 normal : NORMAL;

                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct v2f
                {
                    float4 position : SV_POSITION;

    #if defined(_UV)
                    float2 uv : TEXCOORD0;
                 
#endif

    #if defined(_WORLD_POSITION)

                    float3 worldPosition : TEXCOORD2;
    #endif

    #if defined(_NORMAL)

                    fixed3 worldNormal : COLOR3;
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };


                fixed4 _Color;

                sampler2D _MainTex;
                fixed4 _MainTex_ST;


                fixed _Metallic;
                fixed _Smoothness;

    #if defined(_DIRECTIONAL_LIGHT)
    #if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                CBUFFER_START(_LightBuffer)
                float4 _MainLightPosition;
                half4 _MainLightColor;
                CBUFFER_END
    #else
                fixed4 _LightColor0;
    #endif
    #endif

    #if defined(_DIRECTIONAL_LIGHT)
                static const fixed _MinMetallicLightContribution = 0.7;
                static const fixed _IblContribution = 0.1;
    #endif
                v2f vert(appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                    float4 vertexPosition = v.vertex;

                    fixed3 localNormal = v.normal;

    #if defined(_NORMAL) 
                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
    #endif

                    o.position = UnityObjectToClipPos(vertexPosition);
    #if defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
              
    #endif

    #if defined(_NORMAL)

                    o.worldNormal = worldNormal;
    #endif

                    return o;
                }
              

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
                    fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;


    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));

                    fixed3 worldNormal;


                    worldNormal = normalize(i.worldNormal) * facing;
    #endif

                    fixed pointToLight = 1.0;
                    fixed3 fluentLightColor = fixed3(0.0, 0.0, 0.0);

    #if defined(_DIRECTIONAL_LIGHT)

                    float4 directionalLightDirection = _WorldSpaceLightPos0;

                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));

                    fixed specular = 0.0;
    #endif

                    // Image based lighting (attempt to mimic the Standard shader).

                    fixed3 ibl = unity_IndirectSpecColor.rgb;


                    // Fresnel lighting.

                    // Final lighting mix.
                    fixed4 output = albedo;

                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);

                    fixed minProperty = min(_Smoothness, _Metallic);
    #if defined(_DIRECTIONAL_LIGHT)
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);

                    fixed3 directionalLightColor = _LightColor0.rgb;
                    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);
                    output.rgb += (directionalLightColor * albedo * specular) + (directionalLightColor * specular * _Smoothness);
                    output.rgb += ibl * oneMinusMetallic * _IblContribution;
    #endif
                  

                    return output;
                }

                ENDCG
                }
            }
                FallBack "Diffuse"

}

このShaderではフラグメントシェーダーでtex2D関数を使用してMainTexをuvに沿ってカラーをマッピングし、Colorを積算することでテクスチャだけでなくプロパティ側で設定できるカラーで色味を調整できるようにしています。

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
                    fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;
                    ...

                    fixed4 output = albedo;

                   ...                  

                    return output;
                }

f:id:Holomoto-Sumire:20201105220344j:plain

f:id:Holomoto-Sumire:20201105220427j:plain

〇発光を加える。

 次に浮かび上がるための発光の機能をShaderに加えます。

Propertiesブロックに発光色のパラメータを加えます。

 Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("MainTexture", 2D) = "white" {}

        [HDR]_EmissiveColor("EmissivePower",Color) = (0,0,0,0)//追加

          [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
    }

発光を機能させるためにフラグメントシェーダーの出力に_EmissiveColorを追加します。

[HDR]という属性はハイダイナミックレンジの意味でHDRカラーとして使用します。

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
                    fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;

                    ...

                    fixed4 output = albedo;

                    ...
                    output += _EmissiveColor;//追加
                    return output;
                }

これは最終的に書き出されるピクセル色の値に_EmissiceColorの値分追加しています。

f:id:Holomoto-Sumire:20201105221659j:plain

これで発光を使用できるようになりました。

output += _EmissiveColor;//追加

コードからもわかるように_EmissiveColorを0,0,0=黒に設定することでテクスチャのままの表示もできます。

f:id:Holomoto-Sumire:20201105221939j:plain

〇浮かび上がるテクスチャを使用する

次に今回の目的である浮かび上がる際に現れる画像を使用できるようにします。

Propertiesブロックに画像のパラメータを追加します。

ここでは_EmissiveColor同様発光で使用するために[HDR]の属性を使用します。

    Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("MainTexture", 2D) = "white" {}

       [HDR]_EmissiveMap("EmissiveMap",2D) = "white"{}
       [HDR] _EmissiveColor("EmissivePower",Color) = (0,0,0,0)

          [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
    }

これによりマテリアルのプロパティ側でテクスチャを使用できるようになりました。

f:id:Holomoto-Sumire:20201105222559j:plain

テクスチャを処理に加えます。 _EmissiveColorに積算するように処理を記述しました。

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
                    fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;

                    ...

                    fixed4 output = albedo;

                    ...
                    output +=tex2D(_EmissiveMap,i.uv)* _EmissiveColor;//追加
                    return output;
                }

これによって通常のテクスチャ同様EmissiveMapに EmissiveColorが積算された値が発光として出力に追加されます。

f:id:Holomoto-Sumire:20201105222454j:plain

画像の背景を黒(もしくはping画像)にすることで縁取りされた部分のみ浮かび上がるように発光します。

遺贈でUnityで画像が浮かび上がるShaderができました。

〇今回作成したShader

Shader "Custom/Texture"
{
    Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("MainTexture", 2D) = "white" {}

       [HDR]_EmissiveMap("EmissiveMap",2D) = "white"{}
       [HDR] _EmissiveColor("EmissivePower",Color) = (0,0,0,0)

          [Toggle(_DIRECTIONAL_LIGHT)] _DirectionalLight("Directional Light", Float) = 1.0
    }

        SubShader
            {
                Pass
                {
                    Name "Main"
                    Tags{ "RenderType" = "Transparent" "LightMode" = "ForwardBase" }
                    LOD 100
                    
                    CGPROGRAM

                    #pragma vertex vert
                    #pragma fragment frag

                    #pragma shader_feature _DIRECTIONAL_LIGHT

                    #include "UnityCG.cginc"
                    #include "UnityStandardConfig.cginc"
                    #include "UnityStandardUtils.cginc"


    #if defined(_DIRECTIONAL_LIGHT) 
                #define _NORMAL
    #else
                #undef _NORMAL
    #endif

    #if defined(_NORMAL) 
                #define _WORLD_POSITION
    #else
                #undef _WORLD_POSITION
    #endif
                #define _UV

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    // The default UV channel used for texturing.
                    float2 uv : TEXCOORD0;

                    fixed3 normal : NORMAL;

                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct v2f
                {
                    float4 position : SV_POSITION;

    #if defined(_UV)
                    float2 uv : TEXCOORD0;
                 
#endif

    #if defined(_WORLD_POSITION)

                    float3 worldPosition : TEXCOORD2;
    #endif

    #if defined(_NORMAL)

                    fixed3 worldNormal : COLOR3;
    #endif
                    UNITY_VERTEX_OUTPUT_STEREO
                };


                fixed4 _Color;

                fixed4 _EmissiveColor;

                sampler2D _MainTex;
                fixed4 _MainTex_ST;
                sampler2D _EmissiveMap;


                fixed _Metallic;
                fixed _Smoothness;

    #if defined(_DIRECTIONAL_LIGHT)
    #if defined(_LIGHTWEIGHT_RENDER_PIPELINE)
                CBUFFER_START(_LightBuffer)
                float4 _MainLightPosition;
                half4 _MainLightColor;
                CBUFFER_END
    #else
                fixed4 _LightColor0;
    #endif
    #endif

    #if defined(_DIRECTIONAL_LIGHT)
                static const fixed _MinMetallicLightContribution = 0.7;
                static const fixed _IblContribution = 0.1;
    #endif
                v2f vert(appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                    float4 vertexPosition = v.vertex;

                    fixed3 localNormal = v.normal;

    #if defined(_NORMAL) 
                    fixed3 worldNormal = UnityObjectToWorldNormal(localNormal);
    #endif

                    o.position = UnityObjectToClipPos(vertexPosition);
    #if defined(_UV)
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
              
    #endif

    #if defined(_NORMAL)

                    o.worldNormal = worldNormal;
    #endif

                    return o;
                }
              

                fixed4 frag(v2f i, fixed facing : VFACE) : SV_Target
                {
                    fixed4 albedo = tex2D(_MainTex, i.uv) * _Color;


    #if defined(_NORMAL)
                    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition.xyz));

                    fixed3 worldNormal;


                    worldNormal = normalize(i.worldNormal) * facing;
    #endif

                    fixed pointToLight = 1.0;
                    fixed3 fluentLightColor = fixed3(0.0, 0.0, 0.0);

    #if defined(_DIRECTIONAL_LIGHT)

                    float4 directionalLightDirection = _WorldSpaceLightPos0;

                    fixed diffuse = max(0.0, dot(worldNormal, directionalLightDirection));

                    fixed specular = 0.0;
    #endif

                    // Image based lighting (attempt to mimic the Standard shader).

                    fixed3 ibl = unity_IndirectSpecColor.rgb;


                    // Fresnel lighting.

                    // Final lighting mix.
                    fixed4 output = albedo;

                    fixed3 ambient = glstate_lightmodel_ambient + fixed3(0.25, 0.25, 0.25);

                    fixed minProperty = min(_Smoothness, _Metallic);
    #if defined(_DIRECTIONAL_LIGHT)
                    fixed oneMinusMetallic = (1.0 - _Metallic);
                    output.rgb = lerp(output.rgb, ibl, minProperty);

                    fixed3 directionalLightColor = _LightColor0.rgb;
                    output.rgb *= lerp((ambient + directionalLightColor * diffuse + directionalLightColor * specular) * max(oneMinusMetallic, _MinMetallicLightContribution), albedo, minProperty);
                    output.rgb += (directionalLightColor * albedo * specular) + (directionalLightColor * specular * _Smoothness);
                    output.rgb += ibl * oneMinusMetallic * _IblContribution;
    #endif
                  
                    output += tex2D(_EmissiveMap,i.uv)*_EmissiveColor;
                    return output;
                }

                ENDCG
                }
            }
                FallBack "Diffuse"

}