上一篇使用ShaderGraph实现了圆角矩形ImagePro,最近在开发过程中发现了一个问题,就是在ScrollRect中使用Mask遮罩,ImagePro不会被遮罩,其中原因是ShaderGraph制作的shader不是标准的UI的Shader,然后我用AI帮我生成了标准的UI的Shader,就解决了问题。据说后面Unity6的ShaderGraph支持了UI的Shader,不知道能不能支持。废话不多说,下面直接上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
namespace UnityEngine.UI { public class ImagePro : Image { [SerializeField] [Range(0, 1)] private float _Radius; public float Radius { get => _Radius; set { _Radius = value; Refresh(); } } private static readonly int SizeID = Shader.PropertyToID("_Size"); private static readonly int RadiusID = Shader.PropertyToID("_Radius"); private Shader _shader = null; private Material _material = null; private Material Material { get { if (!_shader) { _shader = Shader.Find($"UI/RoundedRectangle"); } if (!_material) { _material = new Material(_shader) { name = "Rounded Rectangle" }; } return _material; } } protected override void Awake() { base.Awake(); Refresh(); } protected override void OnRectTransformDimensionsChange() { base.OnRectTransformDimensionsChange(); Refresh(); } private void Refresh() { if (material != Material) { material = Material; } var maxRadius = Mathf.Min(rectTransform.rect.size.x, rectTransform.rect.size.y) / 2; material.SetFloat(RadiusID, Radius * maxRadius); material.SetVector(SizeID, rectTransform.rect.size); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
using UnityEngine; using UnityEditor.AnimatedValues; using UnityEditor.SceneManagement; using UnityEngine.UI; using UnityEngine.EventSystems; namespace UnityEditor.UI { [CustomEditor(typeof(ImagePro), true)] [CanEditMultipleObjects] public class ImageProEditor : ImageEditor { private SerializedProperty radius; private ImagePro targetComponent; SerializedProperty m_Sprite; SerializedProperty m_PreserveAspect; SerializedProperty m_UseSpriteMesh; SerializedProperty m_Type; AnimBool m_ShowTypePro; // bool m_bIsDrivenPro; protected override void OnEnable() { base.OnEnable(); EditorApplication.update += Excute; targetComponent = target as ImagePro; radius = serializedObject.FindProperty("_Radius"); m_Sprite = serializedObject.FindProperty("m_Sprite"); m_Type = serializedObject.FindProperty("m_Type"); m_PreserveAspect = serializedObject.FindProperty("m_PreserveAspect"); m_UseSpriteMesh = serializedObject.FindProperty("m_UseSpriteMesh"); m_ShowTypePro = new AnimBool(m_Sprite.objectReferenceValue != null); m_ShowTypePro.valueChanged.AddListener(Repaint); } protected override void OnDisable() { base.OnDisable(); EditorApplication.update -= Excute; } public override void OnInspectorGUI() { serializedObject.Update(); var rect = targetComponent.GetComponent<RectTransform>(); // m_bIsDrivenPro = (rect.drivenByObject as Slider)?.fillRect == rect; SpriteGUI(); AppearanceControlsGUI(); RaycastControlsGUI(); EditorGUILayout.PropertyField(radius); MaskableControlsGUI(); m_ShowTypePro.target = m_Sprite.objectReferenceValue != null; if (EditorGUILayout.BeginFadeGroup(m_ShowTypePro.faded)) TypeGUI(); EditorGUILayout.EndFadeGroup(); SetShowNativeSize(false); if (EditorGUILayout.BeginFadeGroup(m_ShowNativeSize.faded)) { EditorGUI.indentLevel++; if ((Image.Type)m_Type.enumValueIndex == Image.Type.Simple) EditorGUILayout.PropertyField(m_UseSpriteMesh); EditorGUILayout.PropertyField(m_PreserveAspect); EditorGUI.indentLevel--; } EditorGUILayout.EndFadeGroup(); NativeSizeButtonGUI(); serializedObject.ApplyModifiedProperties(); } private void SetShowNativeSize(bool instant) { var type = (Image.Type)m_Type.enumValueIndex; var showNativeSize = (type == Image.Type.Simple || type == Image.Type.Filled) && m_Sprite.objectReferenceValue != null; base.SetShowNativeSize(showNativeSize, instant); } private void Excute() { if (!targetComponent) return; var method = targetComponent.GetType().GetMethod("Refresh", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (method != null) { method.Invoke(targetComponent, null); } } [MenuItem("GameObject/UI/ImagePro")] public static void ImagePro() { var parent = GetOrCreateCanvasGameObject(); var go = new GameObject("ImagePro"); go.transform.SetParent(parent.transform, false); go.AddComponent<RectTransform>(); go.AddComponent<CanvasRenderer>(); go.AddComponent<ImagePro>(); go.layer = 5; Selection.activeGameObject = go; } private static bool IsValidCanvas(Canvas canvas) { if (canvas == null || !canvas.gameObject.activeInHierarchy) return false; // It's important that the non-editable canvas from a prefab scene won't be rejected, // but canvases not visible in the Hierarchy at all do. Don't check for HideAndDontSave. if (EditorUtility.IsPersistent(canvas) || (canvas.hideFlags & HideFlags.HideInHierarchy) != 0) return false; if (StageUtility.GetStageHandle(canvas.gameObject) != StageUtility.GetCurrentStageHandle()) return false; return true; } private static GameObject GetOrCreateCanvasGameObject() { var selected = Selection.activeGameObject; // Try to find a gameobject that is the selected GO or one if its parents. var canvas = selected?.GetComponentInParent<Canvas>(); if (IsValidCanvas(canvas)) return selected; // No canvas in selection or its parents? Then use any valid canvas. // We have to find all loaded Canvases, not just the ones in main scenes. var canvasArray = StageUtility.GetCurrentStageHandle().FindComponentsOfType<Canvas>(); foreach (var t in canvasArray) if (IsValidCanvas(t)) return t.gameObject; // No canvas in the scene at all? Then create a new one. return CreateNewUI(); } private static GameObject CreateNewUI() { // Root for the UI var root = new GameObject("Canvas") { layer = LayerMask.NameToLayer("UI") }; var canvas = root.AddComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; root.AddComponent<CanvasScaler>(); root.AddComponent<GraphicRaycaster>(); // Works for all stages. StageUtility.PlaceGameObjectInCurrentStage(root); var customScene = false; var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); if (prefabStage != null) { root.transform.SetParent(prefabStage.prefabContentsRoot.transform, false); customScene = true; } Undo.RegisterCreatedObjectUndo(root, "Create " + root.name); // If there is no event system add one... // No need to place event system in custom scene as these are temporary anyway. // It can be argued for or against placing it in the user scenes, // but let's not modify scene user is not currently looking at. if (!customScene) CreateEventSystem(false, null); return root; } private static void CreateEventSystem(bool select, GameObject parent) { var stage = parent == null ? StageUtility.GetCurrentStageHandle() : StageUtility.GetStageHandle(parent); var esys = stage.FindComponentOfType<EventSystem>(); if (esys == null) { var eventSystem = new GameObject("EventSystem"); if (parent == null) StageUtility.PlaceGameObjectInCurrentStage(eventSystem); else GameObjectUtility.SetParentAndAlign(eventSystem, parent); esys = eventSystem.AddComponent<EventSystem>(); eventSystem.AddComponent<StandaloneInputModule>(); Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name); } if (select && esys != null) { Selection.activeGameObject = esys.gameObject; } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
Shader "UI/RoundedRectangle" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) // Rounded Rectangle Properties _Radius ("Corner Radius", Range(0, 0.5)) = 0.1 _Size ("Size", Vector) = (100, 100, 0, 0) // UI Stencil Properties _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; float2 localPos : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; float _Radius; float2 _Size; v2f vert(appdata_t v) { v2f OUT; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = v.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); OUT.color = v.color * _Color; // Convert UV to local position for rounded rectangle calculation // Center the coordinates and scale by size OUT.localPos = (v.texcoord - 0.5) * _Size; return OUT; } // Rounded rectangle SDF function - based on the Shader Graph logic float roundedRectSDF(float2 pos, float2 size, float radius) { // This replicates the logic from the Shader Graph: // 1. Get absolute position float2 absPos = abs(pos); // 2. Subtract size/2 from absolute position float2 d = absPos - size * 0.5; // 3. Add radius back d = d + radius; // 4. Calculate distance using max and length float2 maxD = max(d, 0.0); float len = length(maxD); float minMaxD = min(max(d.x, d.y), 0.0); // 5. Final distance float dist = len + minMaxD - radius; return dist; } fixed4 frag(v2f IN) : SV_Target { half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; // Calculate rounded rectangle mask using the same logic as Shader Graph float dist = roundedRectSDF(IN.localPos, _Size, _Radius); // Create smooth edge using smoothstep (like in the Shader Graph) float alpha = 1.0 - smoothstep(0.0, 0.01, dist); // Apply the mask to alpha color.a *= alpha; #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif return color; } ENDCG } } } |
文章源自大腿Plus-https://www.zhaoshijun.com/archives/2085
我的微信
微信扫一扫

shijun_z
我的QQ
QQ扫一扫

846207670
评论