夜風のMixedReality

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

ゼロから始めるUnityShader開発 CustomShaderGUIでマテリアルのプロパティをカスタマイズする  任意にプロパティの表示非表示を切り替える

本日はShader学習枠です。

今回はCustomShaderGUIを使用して任意にプロパティの表示を切り替える実装を紹介します。

redhologerbera.hatenablog.com

〇チェックボックスと連動して表示を行う

 シェーダーによってはある機能を有効にしている場合のみ必要となるパラメータが存在します。

 例えば、ノーマルマップはUnityのライトを使用している状態でのみ有効に機能します。

 このようにあるプロパティを使用する場合にのみ対象のパラメータを表示することはマテリアル自体の可読性を上げ、ユーザーにとって非常にわかりやすい表示を行うことができます。

 チェックボックスによるShader自体の機能のオンオフは以下の記事で紹介しています。

redhologerbera.hatenablog.com

今回は以下のようにチェックボックスでライトのオンオフを調整できる上、 _LightIntensityでライトの強度を変更できるようなシェーダーを構築しました。(ノーマルマップは実装が複雑なので今回のプロパティのカスタマイズと趣旨が変わってくるため簡単な変数にしています。)

 この_LightIntensityはDirectionalLightを使用しているときのみ有効に機能します。

Shader "Unlit/ShaderGUITest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor("Color",color) =(1,1,1,1)
        [Toggle(_DIRECTIONALLIGHT)] _useLight("useDirectionalLight",float) =1
        _LightIntensity("Intensity",Range(0,5)) =1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma shader_feature _DIRECTIONALLIGHT

            #if defined(_DIRECTIONALLIGHT)
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #endif

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                #if defined(_DIRECTIONALLIGHT)
                half3 normal: NORMAL;
                #endif
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                #if defined(_DIRECTIONALLIGHT)
                float3 normalWS : TEXCOORD1;
                #endif
            };

            float4 _MainColor;
            float _LightIntensity;
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                #if defined(_DIRECTIONALLIGHT)
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                #endif

                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                float4 col = _MainColor;

                #if defined(_DIRECTIONALLIGHT)
                //Light.hlslで提供されるUnityのライトを取得する関数
                Light lt = GetMainLight();

                //ライトの向きを計算
                float strength = dot(lt.direction, i.normalWS)*_LightIntensity;
                float4 lightColor = float4(lt.color, 1);
                return col * lightColor * strength;
                #else
                return col;
                #endif
            }
            ENDHLSL
        }
    }
    CustomEditor "CustomShaderGUITest"
}

〇表示のカスタマイズ

まずはカスタムエディタでチェックボックスを表示することから始めます。

これはmaterialEditor.ShaderProperty()を使用します。

public class CustomShaderGUITest : ShaderGUI
{
    protected static class Styles
    {
        public static readonly GUIContent albedoMap = new GUIContent("MainTexture", "MainMap");
        public static readonly GUIContent directionalLight = new GUIContent("Light", "enable Light");//追加
    }

    
    protected MaterialProperty albedoMap;
    protected MaterialProperty light;

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        Material material = (Material)materialEditor.target;
        EditorGUILayout.LabelField("MainFeature");
        albedoMap = FindProperty("_MainTex", props);
        light = FindProperty("_useLight", props);

        materialEditor.TexturePropertySingleLine(Styles.albedoMap, albedoMap);
        materialEditor.ShaderProperty(light,Styles.directionalLight);//追加
    }
}

materialEditor.ShaderPropertyは第一引数にシェーダープロパティ、第二引数に表示スタイルを指定することができ、それぞれ次のように取得しています。

    protected static class Styles
    {
        public static readonly GUIContent directionalLight = new GUIContent("Light", "enable Light");//追加
    }

    
    protected MaterialProperty light;

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        light = FindProperty("_useLight", props);

        materialEditor.ShaderProperty(light,Styles.directionalLight);//追加
    }

これによってチェックボックスを表示させることができます。

〇子プロパティの表示

では次に関連するプロパティ、今回の場合LightIntensityがLightを有効にしている場合表示されるように設定していきます。

これを行うためにはCustomShaderGUI側でマテリアルごとのプロパティを取得する必要があります。

このために次のような関数を使用します。

  //MixedRealityGraphicsTools BaseShaderGUI
    protected static bool PropertyEnabled(MaterialProperty property)
    {
        return !property.floatValue.Equals(0.0f);
    }

これはMaterialPropertyを引数として渡すことでそのプロパティの状態をBool型として返します。

次のように使用します。

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        Material material = (Material)materialEditor.target;
        EditorGUILayout.LabelField("MainFeature");
        albedoMap = FindProperty("_MainTex", props);
        light = FindProperty("_useLight", props);
        lightIntensity = FindProperty("_LightIntensity",props);
        
        materialEditor.TexturePropertySingleLine(Styles.albedoMap, albedoMap);
        materialEditor.ShaderProperty(light,Styles.directionalLight);

        if (PropertyEnabled(light))
        {
            materialEditor.RangeProperty(lightIntensity,"");
        }
    }

 ここではlightを使用している場合にmaterialEditor.RangePropertyが表示されます。

 以上で実装が完了しました。

〇ソースコード

・shader側
Shader "Unlit/ShaderGUITest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor("Color",color) =(1,1,1,1)
        [Toggle(_DIRECTIONALLIGHT)] _useLight("useDirectionalLight",float) =1
        _LightIntensity("Intensity",Range(0,5)) =1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma shader_feature _DIRECTIONALLIGHT

            #if defined(_DIRECTIONALLIGHT)
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #endif

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                #if defined(_DIRECTIONALLIGHT)
                half3 normal: NORMAL;
                #endif
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                #if defined(_DIRECTIONALLIGHT)
                float3 normalWS : TEXCOORD1;
                #endif
            };

            float4 _MainColor;
            float _LightIntensity;
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                #if defined(_DIRECTIONALLIGHT)
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                #endif

                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                float4 col = _MainColor;

                #if defined(_DIRECTIONALLIGHT)
                //Light.hlslで提供されるUnityのライトを取得する関数
                Light lt = GetMainLight();

                //ライトの向きを計算
                float strength = dot(lt.direction, i.normalWS)*_LightIntensity;
                float4 lightColor = float4(lt.color, 1);
                return col * lightColor * strength;
                #else
                return col;
                #endif
            }
            ENDHLSL
        }
    }
    CustomEditor "CustomShaderGUITest"
}
・CustomShaderGUI側
using UnityEditor;
using UnityEngine;
using System;

public class CustomShaderGUITest : ShaderGUI
{
    protected static class Styles
    {
        public static readonly GUIContent albedoMap = new GUIContent("MainTexture", "MainMap");
        public static readonly GUIContent directionalLight = new GUIContent("Light", "enable Light");
    }

    
    protected MaterialProperty albedoMap;
    protected MaterialProperty light;
    protected MaterialProperty lightIntensity;

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        Material material = (Material)materialEditor.target;
        EditorGUILayout.LabelField("MainFeature");
        albedoMap = FindProperty("_MainTex", props);
        light = FindProperty("_useLight", props);
        lightIntensity = FindProperty("_LightIntensity",props);
        
        materialEditor.TexturePropertySingleLine(Styles.albedoMap, albedoMap);
        materialEditor.ShaderProperty(light,Styles.directionalLight);

        if (PropertyEnabled(light))
        {
            materialEditor.RangeProperty(lightIntensity,"");
        }
    }
    protected static bool PropertyEnabled(MaterialProperty property)
    {
        return !property.floatValue.Equals(0.0f);
    }
}