[Unity] Material을 일일이 인스턴스화 하지 않고 UI 크기를 넣는 법

@ DAID Games 2025. 11. 10. 13:07
 

Custom UI components

The sample contains a few custom UI components to reproduce the behavior of some elements such as buttons, toggles and sliders, passing data to the material assigned. You can add these components from the main menu

Unity

 

원래 비율에 따라 달라지거나 픽셀 이미지들을 쓰려니 ShaderGraph에서는 잘 안되는 게 많았었는데
그걸 알았는 지는 모르겠지만

유니티에서  Canvas Graph shader 샘플로 Custom UI Components를 제공하기 시작했습니다.

 

 

Package Manger에서 Samples에 있습니다

 

사용법

1. 원하는 ShaderGraph에 RectTransform Size를 추가

2. 대상이 되는 컴포넌트에 RectTransform Size를 추가

 

3. 끝

 

편리성 면에서는 일일이 인스턴싱하고 값을 넣어줄 필요가 없으니까 매우 좋네요

원래도 가능은 했었을 테지만 Sample을 제공해주다보니 더더욱 편리합니다

 

* RectTransformSize.cs

더보기
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Unity.UI.Shaders.Sample
{
    /// <summary>
    /// Sets the UI Material's Property to the <see cref="UnityEngine.RectTransform"/>'s screen size.
    /// </summary>
    [AddComponentMenu("UI/ShaderGraph Samples/RectTransform Size")]
    [ExecuteAlways]
    [RequireComponent(typeof(RectTransform), typeof(Graphic))]
    [DisallowMultipleComponent]
    public class RectTransformSize : UIBehaviour, IMaterialModifier
    {
        private static readonly string _propertyName = "_RectTransformInfo";

        Material _material;

        private RectTransform _rectTransform;
        private RectTransform RectTransform
        {
            get
            {
                _rectTransform = _rectTransform != null ? _rectTransform : GetComponent<RectTransform>();
                return _rectTransform;
            }
        }

        private Canvas _rootCanvas;
        private Canvas RootCanvas
        {
            get
            {
                if (_rootCanvas == null)
                    _rootCanvas = GetComponentInParent<Canvas>()?.rootCanvas;
                return _rootCanvas;
            }
        }

        private Graphic _graphic;
        public Graphic Graphic
        {
            get
            {
                if (_graphic == null)
                    _graphic = GetComponent<Graphic>();
                return _graphic;
            }
        }

        int? _propertyId;
        int PropertyId
        {
            get
            {
                if (!_propertyId.HasValue)
                    _propertyId = Shader.PropertyToID(_propertyName);
                return _propertyId.Value;
            }
        }

        private Vector2 RectSize => RectTransformUtility.PixelAdjustRect(RectTransform, RootCanvas).size;

        private Vector4 RectTransformInfo
        {
            get
            {
                Vector4 rectTransformInfo = RectSize;
                rectTransformInfo.z = RootCanvas.scaleFactor;
                rectTransformInfo.w = RootCanvas.referencePixelsPerUnit;
                return rectTransformInfo;
            }
        }

        protected override void Start()
        {
            base.Start();
            UpdateMaterial();
        }

#if UNITY_EDITOR
        protected override void Reset()
        {
            base.Reset();
            UpdateMaterial();
        }

        protected override void OnValidate()
        {
            base.OnValidate();
            UpdateMaterial();
        }
#endif

        protected override void OnDidApplyAnimationProperties()
        {
            base.OnDidApplyAnimationProperties();
            UpdateMaterial();
        }

        protected override void OnRectTransformDimensionsChange()
        {
            base.OnRectTransformDimensionsChange();
            UpdateMaterial();
        }

        [ContextMenu("Update Material")]
        public void UpdateMaterial()
        {
            Graphic.SetMaterialDirty();
        }

        public Material GetModifiedMaterial(Material baseMaterial)
        {
            _material = new Material(baseMaterial);

            if (_material.HasVector(PropertyId))
                _material.SetVector(PropertyId, RectTransformInfo);

            return _material;
        }
    }
}

* RectTransformSizeNode.cs

더보기
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Internal;

namespace UnityEditor.ShaderGraph
{
    [Title("Input", "UGUI", "RectTransform Size")]
    class RectTransformSizeNode : AbstractMaterialNode, IGeneratesBodyCode
    {
        [SerializeField] Vector2 previewSize = Vector2.one * 100;
        public Vector2 PreviewSize { get => previewSize; set => previewSize = value; }

        [SerializeField] float previewScaleFactor = 1.0f;
        public float PreviewScaleFactor { get => previewScaleFactor; set => previewScaleFactor = value; }

        [SerializeField] float previewPixelsPerUnit = 100f;
        public float PreviewPixelsPerUnit { get => previewPixelsPerUnit; set => previewPixelsPerUnit = value; }

        public Vector4 PreviewValue => new Vector4(previewSize.x, previewSize.y, previewScaleFactor, previewPixelsPerUnit);

        public RectTransformSizeNode()
        {
            name = "RectTransform Size";
            UpdateNodeAfterDeserialization();
        }

        //public override string documentationURL => NodeUtils.GetDocumentationString("RectTransformSizeNode");

        public override bool hasPreview => false;

        public sealed override void UpdateNodeAfterDeserialization()
        {
            var slots = new List<int>();
            MaterialSlot slot = new Vector2MaterialSlot(0, "Size", "_RectTransformSize", SlotType.Output, PreviewSize);
            AddSlot(slot);
            slots.Add(0);
            MaterialSlot slot1 = new Vector1MaterialSlot(1, "Scale Factor", "_CanvasScaleFactor", SlotType.Output, PreviewScaleFactor);
            AddSlot(slot1);
            slots.Add(1);
            MaterialSlot slot2 = new Vector1MaterialSlot(2, "Pixel Per Unit", "_CanvasPixelPerUnit", SlotType.Output, PreviewPixelsPerUnit);
            AddSlot(slot2);
            slots.Add(2);
            RemoveSlotsNameNotMatching(slots, true);
        }

        public override void CollectShaderProperties(PropertyCollector properties, GenerationMode generationMode)
        {
            properties.AddShaderProperty(new Vector4ShaderProperty
            {
                hidden = true,
                overrideHLSLDeclaration = true,
                hlslDeclarationOverride = HLSLDeclaration.UnityPerMaterial,
                value = PreviewValue,
                overrideReferenceName = "_RectTransformInfo"
            });
        }

        public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
        {
            sb.AppendLine($"$precision2 {GetVariableNameForSlot(0)} = _RectTransformInfo.xy;");
            sb.AppendLine($"$precision {GetVariableNameForSlot(1)} = _RectTransformInfo.z;");
            sb.AppendLine($"$precision {GetVariableNameForSlot(2)} = _RectTransformInfo.w;");
        }
    }
}

* RectTransformSizeDrawer.cs

더보기
using System;
using System.Reflection;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph;
using UnityEditor.ShaderGraph.Drawing;
using UnityEditor.ShaderGraph.Drawing.Inspector;
using UnityEngine.UIElements;
using UnityEngine;
using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
using FloatField = UnityEngine.UIElements.FloatField;

[SGPropertyDrawer(typeof(RectTransformSizeNode))]
public class RectTransformSizeDrawer : IPropertyDrawer, IGetNodePropertyDrawerPropertyData
{
    Action m_setNodesAsDirtyCallback;
    Action m_updateNodeViewsCallback;

    public Action inspectorUpdateDelegate { get; set; }

    public void DisposePropertyDrawer()
    {
        
    }

    public VisualElement DrawProperty(PropertyInfo propertyInfo, object actualObject, InspectableAttribute attribute)
    {
        var node = (RectTransformSizeNode)actualObject;
        var propertySheet = new PropertySheet(PropertyDrawerUtils.CreateLabel($"{node.name} Node", 0, FontStyle.Bold));
        PropertyDrawerUtils.AddDefaultNodeProperties(propertySheet, node, m_setNodesAsDirtyCallback, m_updateNodeViewsCallback);

        var previewSizeField = new Vector2Field("Preview Size") { value = node.PreviewSize };
        previewSizeField.RegisterValueChangedCallback(v =>
        {
            m_setNodesAsDirtyCallback?.Invoke();
            node.owner.owner.RegisterCompleteObjectUndo("Change Preview Size");
            node.PreviewSize = v.newValue;
            m_updateNodeViewsCallback?.Invoke();
            node.Dirty(ModificationScope.Graph);
        });
        propertySheet.Add(previewSizeField);

        var previewScaleFactorField = new FloatField("Preview Scale Factor") { value = node.PreviewScaleFactor };
        previewScaleFactorField.RegisterValueChangedCallback(v =>
        {
            m_setNodesAsDirtyCallback?.Invoke();
            node.owner.owner.RegisterCompleteObjectUndo("Change Preview Scale Factor");
            node.PreviewScaleFactor = v.newValue;
            m_updateNodeViewsCallback?.Invoke();
            node.Dirty(ModificationScope.Graph);
        });
        propertySheet.Add(previewScaleFactorField);

        var previewPPUField = new FloatField("Preview Pixels Per Units") { value = node.PreviewPixelsPerUnit };
        previewPPUField.RegisterValueChangedCallback(v =>
        {
            m_setNodesAsDirtyCallback?.Invoke();
            node.owner.owner.RegisterCompleteObjectUndo("Change Preview Pixels Per Units");
            node.PreviewPixelsPerUnit = v.newValue;
            m_updateNodeViewsCallback?.Invoke();
            node.Dirty(ModificationScope.Graph);
        });
        propertySheet.Add(previewPPUField);

        return propertySheet;
    }

    public void GetPropertyData(Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
    {
        m_setNodesAsDirtyCallback = setNodesAsDirtyCallback;
        m_updateNodeViewsCallback = updateNodeViewsCallback;
    }
}
목차