[Unity2022.3.22f1]UnlitAlbedShader for Toon Charactor



Unityで黒にしてるのは
最低値を定義/設定するためです。

D:\Github\ENRAI_CLO3D_UnityGit\VRChat_Dev_Prj\Assets\Shaders\UnlitAlbedo.shader

// ライティング・影・トーンマップ等の影響を受けず、テクスチャの色をそのまま出力する Unlit シェーダー。
// Built-in RP / VRChat (Single Pass Instanced 対応)。
Shader "Custom/UnlitAlbedo"
{
    Properties
    {
        _MainTex ("Albedo (RGBA)", 2D) = "white" {}
        _Color ("Color (multiply)", Color) = (1, 1, 1, 1)
        _Brightness ("Brightness (multiply)", Float) = 1
        _AmbientInfluence ("Ambient Color Influence (0 = texture as is, 1 = x ambient light color)", Float) = 0
        _AmbientCustom ("Ambient Custom (0 = world ambient color, 1 = RGB below)", Float) = 0
        _AmbientR ("Ambient Custom R", Float) = 0
        _AmbientG ("Ambient Custom G", Float) = 0
        _AmbientB ("Ambient Custom B", Float) = 0
        [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 2
        _Cutoff ("Alpha Cutoff (0 = off)", Range(0, 1)) = 0
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" "IgnoreProjector"="True" }

        Pass
        {
            // ShadeSH9 (環境光/ライトプローブ) の値は ForwardBase パスでのみ確実に渡される
            Tags { "LightMode"="ForwardBase" }
            Cull [_Cull]
            ZWrite On

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            float _Brightness;
            float _AmbientInfluence;
            float _AmbientCustom;
            float _AmbientR;
            float _AmbientG;
            float _AmbientB;
            half _Cutoff;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                if (_Cutoff > 0) clip(col.a - _Cutoff);
                // 環境光の色 (ワールドのライティング設定 / ライトプローブ) を、_AmbientInfluence の割合だけ乗算で混ぜる
                // 法線を使わず (0,0,0) で評価すると SH の定数項だけ = 向きに依存しない環境光の平均色 1 色になる
                float3 worldAmbient = ShadeSH9(float4(0, 0, 0, 1));
                // _AmbientCustom > 0 でワールドの環境光の代わりに、自分で選んだ RGB を使う (VRChat のスライダーで 3 値を動かす)
                float3 ambient = lerp(worldAmbient, float3(_AmbientR, _AmbientG, _AmbientB), saturate(_AmbientCustom));
                float3 rgb = col.rgb * lerp(float3(1, 1, 1), ambient, saturate(_AmbientInfluence));
                return fixed4(rgb * _Brightness, 1);
            }
            ENDCG
        }
    }

    Fallback Off
}

VRC Furyでの設定例

albedShaderBrightness

albedShaderAmbient

albedShaderAmbientCustom

albedShaderAmbientR

albedShaderAmbientG

albedShaderAmbientB

っていうのが面倒くさいのでツールにした

D:\Github\ENRAI_CLO3D_UnityGit\VRChat_Dev_Prj\Assets\Editor\BlendShapeToggleBuilder\AlbedBrightnessSliderBuilder.cs

using System;
using System.Collections.Generic;
using System.Linq;
using com.vrcfury.api;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

// 指定シェーダー (既定 Custom/UnlitAlbedo) を使うレンダラーを集め、そのシェーダーの Float プロパティ
// (_Brightness / _AmbientInfluence など) を 0%→100% で動かす VRCFury スライダー Toggle
// (Use a Slider (Radial)) を、一覧のチェックした行ぶんまとめて生成する。
// PublicApi に Material Property アクションが無いので、そこだけリフレクションで追加する。
public class AlbedBrightnessSliderBuilder : EditorWindow
{
    const string ActionTypeName = "VF.Model.StateAction.MaterialPropertyAction";

    [Serializable]
    class Entry
    {
        public bool enabled = true;
        public string property;
        public string menuPath;
        public float valueAt100 = 1f;
        public float defaultPercent; // スライダーの初期位置 (0〜100%)。VRChat での起動時の値になる
    }

    // 一覧の初期内容を変えたら上げる。保存済みウィンドウの古い一覧を作り直すため
    const int EntriesVersion = 2;

    [SerializeField] GameObject _target;
    [SerializeField] string _shaderName = "Custom/UnlitAlbedo";
    [SerializeField] int _entriesVersion;
    [SerializeField] List<Entry> _entries = new List<Entry>();
    List<Renderer> _renderers = new List<Renderer>();
    Vector2 _scroll;

    [MenuItem("Tools/VRCFury/Albed Brightness Slider Builder")]
    static void Open()
    {
        GetWindow<AlbedBrightnessSliderBuilder>("Albed Sliders");
    }

    void OnEnable()
    {
        if (_entries == null || _entries.Count == 0 || _entriesVersion != EntriesVersion) ResetEntries();
        OnSelectionChange();
    }

    void ResetEntries()
    {
        _entries = DefaultEntries();
        _entriesVersion = EntriesVersion;
    }

    static List<Entry> DefaultEntries()
    {
        return new List<Entry>
        {
            new Entry { property = "_Brightness", menuPath = "albedShaderBrightness", defaultPercent = 100 },
            new Entry { property = "_AmbientInfluence", menuPath = "albedShaderAmbient" },
            new Entry { property = "_AmbientCustom", menuPath = "albedShaderAmbientCustom" },
            new Entry { property = "_AmbientR", menuPath = "albedShaderAmbientR", defaultPercent = 100 },
            new Entry { property = "_AmbientG", menuPath = "albedShaderAmbientG", defaultPercent = 100 },
            new Entry { property = "_AmbientB", menuPath = "albedShaderAmbientB", defaultPercent = 100 },
        };
    }

    // シーン上のオブジェクトを選ぶと、そのアバター最上位 (root) が Target になる
    void OnSelectionChange()
    {
        var go = Selection.activeGameObject;
        if (go != null && !EditorUtility.IsPersistent(go) && go.transform.root.gameObject != _target)
        {
            _target = go.transform.root.gameObject;
            Refresh();
        }
        Repaint();
    }

    void Refresh()
    {
        _renderers = _target == null
            ? new List<Renderer>()
            : _target.GetComponentsInChildren<Renderer>(true)
                .Where(r => r.sharedMaterials.Any(UsesShader))
                .ToList();
    }

    bool UsesShader(Material m)
    {
        return m != null && m.shader != null && m.shader.name == _shaderName;
    }

    static Type FindActionType()
    {
        return AppDomain.CurrentDomain.GetAssemblies()
            .Select(a => a.GetType(ActionTypeName, false))
            .FirstOrDefault(t => t != null);
    }

    void OnGUI()
    {
        EditorGUI.BeginChangeCheck();
        _target = (GameObject)EditorGUILayout.ObjectField(
            new GUIContent("Target Object", "Toggle を追加する GameObject (アバター最上位)。シーン上のオブジェクトを選ぶと自動で入る"),
            _target, typeof(GameObject), true);
        _shaderName = EditorGUILayout.TextField("Shader Name", _shaderName);
        if (EditorGUI.EndChangeCheck()) Refresh();

        var targetOk = _target != null && !EditorUtility.IsPersistent(_target);
        if (!targetOk)
        {
            EditorGUILayout.HelpBox("Target Object にシーン上の GameObject を指定してください。", MessageType.Info);
            return;
        }

        if (GUILayout.Button("Refresh")) Refresh();
        EditorGUILayout.LabelField($"'{_shaderName}' を使うレンダラー {_renderers.Count} 個", EditorStyles.boldLabel);
        foreach (var r in _renderers) EditorGUILayout.LabelField("  " + r.name);

        EditorGUILayout.Space();
        EditorGUILayout.LabelField("スライダー (チェックした行を追加)", EditorStyles.boldLabel);
        using (var scroll = new EditorGUILayout.ScrollViewScope(_scroll))
        {
            _scroll = scroll.scrollPosition;
            Entry remove = null;
            foreach (var e in _entries)
            {
                using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                {
                    using (new EditorGUILayout.HorizontalScope())
                    {
                        e.enabled = EditorGUILayout.ToggleLeft(e.property, e.enabled, EditorStyles.boldLabel);
                        if (GUILayout.Button("×", GUILayout.Width(22))) remove = e;
                    }
                    e.property = EditorGUILayout.TextField("Property", e.property);
                    e.menuPath = EditorGUILayout.TextField("Menu Path", e.menuPath);
                    e.valueAt100 = EditorGUILayout.FloatField(
                        new GUIContent("Value at 100%", "スライダー 100% のときのプロパティ値 (0% はマテリアルの現在値)"), e.valueAt100);
                    e.defaultPercent = EditorGUILayout.Slider(
                        new GUIContent("Default %", "スライダーの初期位置。VRChat で起動した直後のこの値になる"), e.defaultPercent, 0f, 100f);
                    DrawWarnings(e);
                }
            }
            if (remove != null) _entries.Remove(remove);
        }

        using (new EditorGUILayout.HorizontalScope())
        {
            if (GUILayout.Button("+ 行を追加")) _entries.Add(new Entry { property = "_Property", menuPath = "albedShader" });
            if (GUILayout.Button("初期値に戻す")) ResetEntries();
        }

        var count = _entries.Count(e => e.enabled);
        using (new EditorGUI.DisabledScope(_renderers.Count == 0 || count == 0))
        {
            if (GUILayout.Button($"Add Slider Toggles ({count})", GUILayout.Height(28))) Build();
        }
    }

    void DrawWarnings(Entry e)
    {
        if (string.IsNullOrEmpty(e.property)) return;
        var mats = _renderers.SelectMany(r => r.sharedMaterials).Where(UsesShader).Distinct().ToList();

        var missing = mats.Where(m => !m.HasProperty(e.property)).Select(m => m.name).ToList();
        if (missing.Count > 0)
            EditorGUILayout.HelpBox(
                $"次のマテリアルには {e.property} がありません (シェーダーの更新待ちか名前違い):\n" + string.Join(", ", missing),
                MessageType.Error);

        // 0% はマテリアルの現在値になるので、0 でないと「スライダー 0% = 0.0」にならない
        var nonZero = mats.Where(m => m.HasProperty(e.property) && m.GetFloat(e.property) != 0f).Select(m => m.name).ToList();
        if (nonZero.Count > 0)
            EditorGUILayout.HelpBox(
                $"次のマテリアルの {e.property} が 0 ではありません。スライダー 0% はこの値になります:\n" + string.Join(", ", nonZero),
                MessageType.Warning);
    }

    static void SetField(object obj, string name, object value)
    {
        obj.GetType().GetField(name).SetValue(obj, value);
    }

    void Build()
    {
        var actionType = FindActionType();
        if (actionType == null)
        {
            EditorUtility.DisplayDialog("Albed Sliders", $"VRCFury の {ActionTypeName} が見つかりません。VRCFury のバージョンが違う可能性があります。", "OK");
            return;
        }

        var target = _target;
        var entries = _entries.Where(e => e.enabled && !string.IsNullOrEmpty(e.property) && !string.IsNullOrEmpty(e.menuPath)).ToList();
        if (entries.Count == 0) return;

        // 同じ Menu Path の Toggle が既にあれば、置き換えるかスキップするか選ぶ
        var paths = new HashSet<string>(entries.Select(e => e.menuPath.Trim('/', ' ')));
        var existing = target.GetComponents<Component>()
            .Where(c => BlendShapeToggleBuilder.TryGetToggleMenuPath(c, out var p) && paths.Contains(p)).ToList();

        Undo.IncrementCurrentGroup();
        Undo.SetCurrentGroupName("Add Albed Sliders");

        var skip = new HashSet<string>();
        if (existing.Count > 0)
        {
            var choice = EditorUtility.DisplayDialogComplex("Albed Sliders",
                $"'{target.name}' に同じ Menu Path の Toggle が既に {existing.Count} 個あります。", "置き換える", "キャンセル", "既存はそのまま(重複はスキップ)");
            if (choice == 1) return;
            foreach (var c in existing)
            {
                BlendShapeToggleBuilder.TryGetToggleMenuPath(c, out var p);
                // プレハブ本体側にあるコンポーネントはインスタンスから削除できないのでスキップ扱い
                if (choice == 2 || PrefabUtility.GetCorrespondingObjectFromSource(c) != null)
                    skip.Add(p);
                else
                    Undo.DestroyObjectImmediate(c);
            }
        }

        var latestVersion = actionType.GetMethod("GetLatestVersion").Invoke(Activator.CreateInstance(actionType), null);
        var added = new List<string>();
        foreach (var e in entries)
        {
            var path = e.menuPath.Trim('/', ' ');
            if (skip.Contains(path)) continue;

            var before = new HashSet<Component>(target.GetComponents<Component>());
            var toggle = FuryComponents.CreateToggle(target);
            toggle.SetMenuPath(path);
            toggle.SetSlider();
            var vf = target.GetComponents<Component>().First(c => c != null && !before.Contains(c));
            Undo.RegisterCreatedObjectUndo(vf, "Add Albed Sliders");

            var so = new SerializedObject(vf);
            var actions = so.FindProperty("content").FindPropertyRelative("state").FindPropertyRelative("actions");
            foreach (var r in _renderers)
            {
                var action = Activator.CreateInstance(actionType);
                actionType.GetProperty("Version").SetValue(action, latestVersion);
                SetField(action, "renderer2", r.gameObject);
                SetField(action, "affectAllMeshes", false);
                SetField(action, "propertyName", e.property);
                SetField(action, "value", e.valueAt100); // propertyType は既定の Float
                actions.arraySize++;
                actions.GetArrayElementAtIndex(actions.arraySize - 1).managedReferenceValue = action;
            }
            so.FindProperty("content").FindPropertyRelative("defaultSliderValue").floatValue = e.defaultPercent / 100f;
            so.ApplyModifiedProperties();
            added.Add($"{path} ({e.property})");
        }

        EditorUtility.SetDirty(target);
        EditorSceneManager.MarkSceneDirty(target.scene);
        Debug.Log($"[AlbedBrightnessSliderBuilder] '{target.name}' にスライダー Toggle を {added.Count} 個追加しました " +
                  $"(スキップ {skip.Count} 個 / {_renderers.Count} レンダラー): {string.Join(", ", added)}", target);
    }
}

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です