Unity 2D中实现多重叠Image合并后统一设置透明度的方法
解决Unity 2D多Image重叠时透明度叠加问题
问题描述
多个挂载Image组件的GameObject在场景中重叠时,若单独给每个Image设置Color(1.0, 1.0, 1.0, 0.5)(透明度0.5),重叠区域会出现颜色叠加的异常效果(比如蓝红重叠处呈现紫色)。需要实现先将所有重叠Image合并为整体,再统一设置0.5透明度的效果,且支持任意数量的Image。
实现方案
方案一:RenderTexture批量渲染合并(通用动态场景)
通过RenderTexture将所有目标Image渲染为一张纹理,再用单个Image承载这张纹理并设置透明度,从根本上避免叠加问题。
步骤说明
- 计算所有目标Image的整体包围盒:遍历每个Image的
RectTransform,获取其世界空间的四角坐标,计算出覆盖所有Image的最小矩形区域。 - 创建匹配包围盒尺寸的
RenderTexture,将UI相机的渲染目标临时切换为该RenderTexture。 - 临时隐藏目标Image,让相机将这些Image渲染到RenderTexture中。
- 从RenderTexture读取像素生成
Texture2D,再创建Sprite赋值给新的Image组件。 - 给新Image设置0.5透明度,按需销毁或隐藏原Image。
代码实现
using UnityEngine; using UnityEngine.UI; public class ImageMerger : MonoBehaviour { public Image[] targetImages; // 需合并的所有Image public Image mergedImagePrefab; // 合并后Image的预制体 public void MergeImages() { if (targetImages == null || targetImages.Length == 0) return; // 计算所有Image的总包围盒 Vector3[] corners = new Vector3[4]; targetImages[0].rectTransform.GetWorldCorners(corners); Vector2 min = corners[0]; Vector2 max = corners[2]; foreach (var img in targetImages) { img.rectTransform.GetWorldCorners(corners); min = Vector2.Min(min, corners[0]); max = Vector2.Max(max, corners[2]); } Rect totalRect = new Rect(min, max - min); // 创建RenderTexture RenderTexture rt = new RenderTexture((int)totalRect.width, (int)totalRect.height, 0); RenderTexture.active = rt; // 获取UI相机 Camera uiCamera = FindObjectOfType<Canvas>().worldCamera ?? Camera.main; // 临时隐藏目标Image foreach (var img in targetImages) img.gameObject.SetActive(false); // 渲染到RenderTexture uiCamera.targetTexture = rt; uiCamera.Render(); // 生成最终纹理 Texture2D mergedTex = new Texture2D(rt.width, rt.height); mergedTex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); mergedTex.Apply(); // 恢复相机与原Image状态 uiCamera.targetTexture = null; RenderTexture.active = null; foreach (var img in targetImages) img.gameObject.SetActive(true); // 创建合并后的Image GameObject mergedObj = Instantiate(mergedImagePrefab.gameObject, transform); Image mergedImg = mergedObj.GetComponent<Image>(); mergedImg.sprite = Sprite.Create(mergedTex, new Rect(0, 0, mergedTex.width, mergedTex.height), new Vector2(0.5f, 0.5f)); mergedImg.rectTransform.anchoredPosition = totalRect.position; mergedImg.rectTransform.sizeDelta = totalRect.size; mergedImg.color = new Color(1f, 1f, 1f, 0.5f); // 销毁原Image(可根据需求改为隐藏) foreach (var img in targetImages) Destroy(img.gameObject); // 清理临时资源 Destroy(rt); } }
方案二:Mask+自定义Shader(适合静态场景)
通过Mask将所有子Image约束在同一区域,再用自定义Shader改变混合模式,实现先绘制所有不透明内容、再统一叠加透明度的效果。
步骤说明
- 创建父GameObject,将所有需合并的Image设为其子物体。
- 给父物体添加
Mask组件,用一个覆盖所有子Image区域的Image作为遮罩。 - 编写自定义Shader,修改混合模式为
Blend One OneMinusSrcAlpha(先叠加所有RGB颜色,再统一应用透明度)。 - 将所有子Image的材质替换为该自定义材质,最后给遮罩Image设置0.5透明度。
简化Shader示例
Shader "UI/CustomOpaqueBlend" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityUI.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; }; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; v2f vert(appdata_t IN) { v2f OUT; OUT.worldPosition = IN.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.texcoord = IN.texcoord; #ifdef UNITY_HALF_TEXEL_OFFSET OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1); #endif OUT.color = IN.color * _Color; return OUT; } sampler2D _MainTex; fixed4 frag(v2f IN) : SV_Target { half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); return color; } ENDCG } } }
内容的提问来源于stack exchange,提问作者Robin
相关产品推荐
相关产品推荐

