| | |
| | | public int OutlineWidth = 10; |
| | | |
| | | private static List<UIVertex> m_VetexList = new List<UIVertex>(); |
| | | // 材质池:Key为"颜色_宽度"格式的字符串,Value为对应的材质; 减少合批问题,又能保持不同的描边;共用材质会同时改变参数 |
| | | private static Dictionary<string, Material> m_MaterialPool = new Dictionary<string, Material>(); |
| | | |
| | | static Shader m_tmpShader; |
| | | private static Shader m_Shader |
| | | protected override void Awake() |
| | | { |
| | | get |
| | | base.Awake(); |
| | | |
| | | if (base.graphic) |
| | | { |
| | | if (m_tmpShader == null) |
| | | if (base.graphic.material == null || base.graphic.material.shader.name != "TSF Shaders/UI/OutlineEx") |
| | | { |
| | | m_tmpShader = Shader.Find("TSF Shaders/UI/OutlineEx"); |
| | | // #if UNITY_EDITOR |
| | | |
| | | var texMaterial = ResManager.Instance.LoadAsset<Material>("Materials", "OutlineMat"); |
| | | if (texMaterial != null) |
| | | { |
| | | base.graphic.material = texMaterial; |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError("没有找到材质OutlineMat.mat"); |
| | | } |
| | | // #else |
| | | // var shader = Shader.Find("TSF Shaders/UI/OutlineEx"); |
| | | // base.graphic.material = new Material(shader); |
| | | // #endif |
| | | } |
| | | return m_tmpShader; |
| | | |
| | | if (base.graphic.canvas) |
| | | { |
| | | var v1 = base.graphic.canvas.additionalShaderChannels; |
| | | var v2 = AdditionalCanvasShaderChannels.TexCoord1; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | v2 = AdditionalCanvasShaderChannels.TexCoord2; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | v2 = AdditionalCanvasShaderChannels.TexCoord3; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | v2 = AdditionalCanvasShaderChannels.Tangent; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | v2 = AdditionalCanvasShaderChannels.Normal; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | } |
| | | this._Refresh(); |
| | | } |
| | | } |
| | | |
| | | protected override void Start() |
| | | { |
| | | base.Start(); |
| | | |
| | | // 使用材质池获取或创建材质 |
| | | string key = GetMaterialKey(OutlineColor, OutlineWidth); |
| | | if (!m_MaterialPool.TryGetValue(key, out Material material)) |
| | | { |
| | | material = new Material(m_Shader); |
| | | material.SetColor("_OutlineColor", this.OutlineColor); |
| | | material.SetInt("_OutlineWidth", this.OutlineWidth); |
| | | m_MaterialPool[key] = material; |
| | | } |
| | | base.graphic.material = material; |
| | | |
| | | var v1 = base.graphic.canvas.additionalShaderChannels; |
| | | var v2 = AdditionalCanvasShaderChannels.TexCoord1; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | v2 = AdditionalCanvasShaderChannels.TexCoord2; |
| | | if ((v1 & v2) != v2) |
| | | { |
| | | base.graphic.canvas.additionalShaderChannels |= v2; |
| | | } |
| | | |
| | | this._Refresh(); |
| | | } |
| | | |
| | | |
| | | #if UNITY_EDITOR |
| | | //在编辑器下打开也刷新下 |
| | | // protected override void OnEnable() |
| | | // { |
| | | // base.OnEnable(); |
| | | // this._Refresh(); |
| | | // } |
| | | |
| | | protected override void OnValidate() |
| | | { |
| | | base.OnValidate(); |
| | | |
| | | if (base.graphic.material != null) |
| | | { |
| | | if (base.graphic.material.shader.name != "TSF Shaders/UI/OutlineEx") |
| | | { |
| | | var texMaterial = ResManager.Instance.LoadAsset<Material>("Materials", "OutlineMat"); |
| | | if (texMaterial != null) |
| | | { |
| | | base.graphic.material = texMaterial; |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError("没有找到材质OutlineMat.mat"); |
| | | } |
| | | //var shader = Shader.Find("TSF Shaders/UI/OutlineEx"); |
| | | //base.graphic.material = new Material(shader); |
| | | } |
| | | this._Refresh(); |
| | | } |
| | | } |
| | |
| | | |
| | | private void _Refresh() |
| | | { |
| | | // 检查当前材质是否与所需属性匹配,如果不匹配则从池中获取或创建新材质 |
| | | string key = GetMaterialKey(OutlineColor, OutlineWidth); |
| | | Material material; |
| | | if (!m_MaterialPool.TryGetValue(key, out material)) |
| | | /*if (base.graphic.material.GetInt("_OutlineWidth") != this.OutlineWidth || base.graphic.material.GetColor("_OutlineColor") != this.OutlineColor) |
| | | { |
| | | // 如果池中没有对应的材质,创建新材质 |
| | | var shader = Shader.Find("TSF Shaders/UI/OutlineEx"); |
| | | material = new Material(shader); |
| | | m_MaterialPool[key] = material; |
| | | } |
| | | |
| | | if (material == null) |
| | | { |
| | | // 防范材质被删的情况,创建新材质 |
| | | material = new Material(m_Shader); |
| | | m_MaterialPool[key] = material; |
| | | } |
| | | |
| | | material.SetColor("_OutlineColor", this.OutlineColor); |
| | | material.SetInt("_OutlineWidth", this.OutlineWidth); |
| | | |
| | | // 更新图形材质 |
| | | base.graphic.material = material; |
| | | base.graphic.material.SetColor("_OutlineColor", this.OutlineColor); |
| | | base.graphic.material.SetInt("_OutlineWidth", this.OutlineWidth); |
| | | }*/ |
| | | base.graphic.SetVerticesDirty(); |
| | | } |
| | | |
| | | |
| | | public override void ModifyMesh(VertexHelper vh) |
| | | { |
| | | vh.GetUIVertexStream(m_VetexList); |
| | | |
| | | this._ProcessVertices(); |
| | | |
| | | vh.Clear(); |
| | | vh.AddUIVertexTriangleStream(m_VetexList); |
| | | } |
| | | |
| | | |
| | | private void _ProcessVertices() |
| | | { |
| | | for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3) |
| | | public override void ModifyMesh(VertexHelper vh) |
| | | { |
| | | var v1 = m_VetexList[i]; |
| | | var v2 = m_VetexList[i + 1]; |
| | | var v3 = m_VetexList[i + 2]; |
| | | // 计算原顶点坐标中心点 |
| | | // |
| | | var minX = _Min(v1.position.x, v2.position.x, v3.position.x); |
| | | var minY = _Min(v1.position.y, v2.position.y, v3.position.y); |
| | | var maxX = _Max(v1.position.x, v2.position.x, v3.position.x); |
| | | var maxY = _Max(v1.position.y, v2.position.y, v3.position.y); |
| | | var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f; |
| | | // 计算原始顶点坐标和UV的方向 |
| | | // |
| | | Vector2 triX, triY, uvX, uvY; |
| | | Vector2 pos1 = v1.position; |
| | | Vector2 pos2 = v2.position; |
| | | Vector2 pos3 = v3.position; |
| | | if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right)) |
| | | > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right))) |
| | | { |
| | | triX = pos2 - pos1; |
| | | triY = pos3 - pos2; |
| | | uvX = v2.uv0 - v1.uv0; |
| | | uvY = v3.uv0 - v2.uv0; |
| | | } |
| | | else |
| | | { |
| | | triX = pos3 - pos2; |
| | | triY = pos2 - pos1; |
| | | uvX = v3.uv0 - v2.uv0; |
| | | uvY = v2.uv0 - v1.uv0; |
| | | } |
| | | // 计算原始UV框 |
| | | // |
| | | var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0); |
| | | var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0); |
| | | var uvOrigin = new Vector4(uvMin.x, uvMin.y, uvMax.x, uvMax.y); |
| | | vh.GetUIVertexStream(m_VetexList); |
| | | |
| | | // 为每个顶点设置新的Position和UV,并传入原始UV框 |
| | | v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); |
| | | v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); |
| | | v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin); |
| | | this._ProcessVertices(); |
| | | |
| | | // 获取 RectMask2D 的边界 |
| | | // 确保描边顶点扩展不会超出 RectMask2D 的边界 |
| | | |
| | | var mask = GetComponentInParent<RectMask2D>(); |
| | | if (mask != null) |
| | | { |
| | | // 将局部坐标转换为屏幕坐标 |
| | | |
| | | Transform uiTransform = this.transform; |
| | | Vector3 worldPos1 = uiTransform.TransformPoint(v1.position); |
| | | Vector3 worldPos2 = uiTransform.TransformPoint(v2.position); |
| | | Vector3 worldPos3 = uiTransform.TransformPoint(v3.position); |
| | | |
| | | Vector2 screenPos1 = CameraManager.uiCamera.WorldToScreenPoint(worldPos1); |
| | | Vector2 screenPos2 = CameraManager.uiCamera.WorldToScreenPoint(worldPos2); |
| | | Vector2 screenPos3 = CameraManager.uiCamera.WorldToScreenPoint(worldPos3); |
| | | |
| | | |
| | | |
| | | // 确保描边顶点扩展不会超出 RectMask2D 的边界 |
| | | var clipRect = mask.canvasRect; |
| | | //clipRect是中心点为坐标0,0,转成屏幕的 |
| | | var maskMinX = Screen.width / 2 + clipRect.xMin; |
| | | var maskMaxX = Screen.width / 2 + clipRect.xMax; |
| | | var maskMinY = Screen.height / 2 + clipRect.yMin; |
| | | var maskMaxY = Screen.height / 2 + clipRect.yMax; |
| | | |
| | | // //screenPos1 超出clipRect范围不显示 |
| | | bool isout = false; |
| | | if (screenPos3.x < maskMinX || screenPos3.x > maskMaxX || screenPos3.y < maskMinY || screenPos3.y > maskMaxY) |
| | | { |
| | | isout = true; |
| | | } |
| | | else if (screenPos2.x < maskMinX || screenPos2.x > maskMaxX || screenPos2.y < maskMinY || screenPos2.y > maskMaxY) |
| | | { |
| | | isout = true; |
| | | } |
| | | else if (screenPos1.x < maskMinX || screenPos1.x > maskMaxX || screenPos1.y < maskMinY || screenPos1.y > maskMaxY) |
| | | { |
| | | isout = true; |
| | | } |
| | | if (isout) |
| | | { |
| | | v1.position = new Vector3(10000, 10000, 10000); |
| | | v2.position = new Vector3(10000, 10000, 10000); |
| | | v3.position = new Vector3(10000, 10000, 10000); |
| | | } |
| | | |
| | | // screenPos1.x = Mathf.Clamp(screenPos1.x, maskMinX, maskMaxX); |
| | | // screenPos1.y = Mathf.Clamp(screenPos1.y, maskMinY, maskMaxY); |
| | | // screenPos2.x = Mathf.Clamp(screenPos2.x, maskMinX, maskMaxX); |
| | | // screenPos2.y = Mathf.Clamp(screenPos2.y, maskMinY, maskMaxY); |
| | | // screenPos3.x = Mathf.Clamp(screenPos3.x, maskMinX, maskMaxX); |
| | | // screenPos3.y = Mathf.Clamp(screenPos3.y, maskMinY, maskMaxY); |
| | | |
| | | // // 将屏幕坐标转换为局部坐标 |
| | | // RectTransformUtility.ScreenPointToLocalPointInRectangle(uiTransform.GetComponent<RectTransform>(), screenPos1, CameraManager.uiCamera, out Vector2 localPos1); |
| | | // RectTransformUtility.ScreenPointToLocalPointInRectangle(uiTransform.GetComponent<RectTransform>(), screenPos2, CameraManager.uiCamera, out Vector2 localPos2); |
| | | // RectTransformUtility.ScreenPointToLocalPointInRectangle(uiTransform.GetComponent<RectTransform>(), screenPos3, CameraManager.uiCamera, out Vector2 localPos3); |
| | | // v1.position = localPos1; |
| | | // v2.position = localPos2; |
| | | // v3.position = localPos3; |
| | | } |
| | | |
| | | |
| | | // 应用设置后的UIVertex |
| | | m_VetexList[i] = v1; |
| | | m_VetexList[i + 1] = v2; |
| | | m_VetexList[i + 2] = v3; |
| | | vh.Clear(); |
| | | vh.AddUIVertexTriangleStream(m_VetexList); |
| | | } |
| | | } |
| | | |
| | | |
| | | private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth, |
| | | Vector2 pPosCenter, |
| | | Vector2 pTriangleX, Vector2 pTriangleY, |
| | | Vector2 pUVX, Vector2 pUVY, |
| | | Vector4 pUVOrigin) |
| | | { |
| | | // Position |
| | | var pos = pVertex.position; |
| | | var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth; |
| | | var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth; |
| | | pos.x += posXOffset; |
| | | pos.y += posYOffset; |
| | | pVertex.position = pos; |
| | | // UV |
| | | var uv = pVertex.uv0; |
| | | private void _ProcessVertices() |
| | | { |
| | | for (int i = 0, count = m_VetexList.Count - 3; i <= count; i += 3) |
| | | { |
| | | var v1 = m_VetexList[i]; |
| | | var v2 = m_VetexList[i + 1]; |
| | | var v3 = m_VetexList[i + 2]; |
| | | // 计算原顶点坐标中心点 |
| | | // |
| | | var minX = _Min(v1.position.x, v2.position.x, v3.position.x); |
| | | var minY = _Min(v1.position.y, v2.position.y, v3.position.y); |
| | | var maxX = _Max(v1.position.x, v2.position.x, v3.position.x); |
| | | var maxY = _Max(v1.position.y, v2.position.y, v3.position.y); |
| | | var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f; |
| | | // 计算原始顶点坐标和UV的方向 |
| | | // |
| | | Vector2 triX, triY, uvX, uvY; |
| | | Vector2 pos1 = v1.position; |
| | | Vector2 pos2 = v2.position; |
| | | Vector2 pos3 = v3.position; |
| | | if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right)) |
| | | > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right))) |
| | | { |
| | | triX = pos2 - pos1; |
| | | triY = pos3 - pos2; |
| | | uvX = v2.uv0 - v1.uv0; |
| | | uvY = v3.uv0 - v2.uv0; |
| | | } |
| | | else |
| | | { |
| | | triX = pos3 - pos2; |
| | | triY = pos2 - pos1; |
| | | uvX = v3.uv0 - v2.uv0; |
| | | uvY = v2.uv0 - v1.uv0; |
| | | } |
| | | // 计算原始UV框 |
| | | var uvMin = _Min(v1.uv0, v2.uv0, v3.uv0); |
| | | var uvMax = _Max(v1.uv0, v2.uv0, v3.uv0); |
| | | //OutlineColor 和 OutlineWidth 也传入,避免出现不同的材质球 |
| | | var col_rg = new Vector2(OutlineColor.r, OutlineColor.g); //描边颜色 用uv3 和 tangent的 zw传递 |
| | | var col_ba = new Vector4(0,0,OutlineColor.b, OutlineColor.a); |
| | | var normal = new Vector3(0, 0, OutlineWidth); //描边的宽度 用normal的z传递 |
| | | |
| | | // 为每个顶点设置新的Position和UV,并传入原始UV框 |
| | | v1 = _SetNewPosAndUV(v1, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax); |
| | | v1.uv3 = col_rg; |
| | | v1.tangent = col_ba; |
| | | v1.normal = normal; |
| | | v2 = _SetNewPosAndUV(v2, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax); |
| | | v2.uv3 = col_rg; |
| | | v2.tangent = col_ba; |
| | | v2.normal = normal; |
| | | v3 = _SetNewPosAndUV(v3, this.OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax); |
| | | v3.uv3 = col_rg; |
| | | v3.tangent = col_ba; |
| | | v3.normal = normal; |
| | | |
| | | |
| | | // 应用设置后的UIVertex |
| | | // |
| | | m_VetexList[i] = v1; |
| | | m_VetexList[i + 1] = v2; |
| | | m_VetexList[i + 2] = v3; |
| | | } |
| | | } |
| | | |
| | | |
| | | private static UIVertex _SetNewPosAndUV(UIVertex pVertex, int pOutLineWidth, |
| | | Vector2 pPosCenter, |
| | | Vector2 pTriangleX, Vector2 pTriangleY, |
| | | Vector2 pUVX, Vector2 pUVY, |
| | | Vector2 pUVOriginMin, Vector2 pUVOriginMax) |
| | | { |
| | | // Position |
| | | var pos = pVertex.position; |
| | | var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth; |
| | | var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth; |
| | | pos.x += posXOffset; |
| | | pos.y += posYOffset; |
| | | pVertex.position = pos; |
| | | // UV |
| | | var uv = pVertex.uv0; |
| | | |
| | | |
| | | uv += new Vector4((pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1)).x, (pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1)).y, 0, 0); |
| | | uv += new Vector4((pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1)).x, (pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1)).y, 0, 0); |
| | | pVertex.uv0 = uv; |
| | | // 原始UV框 |
| | | pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y); |
| | | pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w); |
| | | |
| | | pVertex.uv0 = uv; |
| | | |
| | | return pVertex; |
| | | } |
| | | pVertex.uv1 = pUVOriginMin; //uv1 uv2 可用 tangent normal 在缩放情况 会有问题 |
| | | pVertex.uv2 = pUVOriginMax; |
| | | |
| | | return pVertex; |
| | | } |
| | | |
| | | |
| | | private static float _Min(float pA, float pB, float pC) |
| | | { |
| | | return Mathf.Min(Mathf.Min(pA, pB), pC); |
| | | } |
| | | private static float _Min(float pA, float pB, float pC) |
| | | { |
| | | return Mathf.Min(Mathf.Min(pA, pB), pC); |
| | | } |
| | | |
| | | |
| | | private static float _Max(float pA, float pB, float pC) |
| | | { |
| | | return Mathf.Max(Mathf.Max(pA, pB), pC); |
| | | } |
| | | private static float _Max(float pA, float pB, float pC) |
| | | { |
| | | return Mathf.Max(Mathf.Max(pA, pB), pC); |
| | | } |
| | | |
| | | |
| | | private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC) |
| | | { |
| | | return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y)); |
| | | } |
| | | private static Vector2 _Min(Vector2 pA, Vector2 pB, Vector2 pC) |
| | | { |
| | | return new Vector2(_Min(pA.x, pB.x, pC.x), _Min(pA.y, pB.y, pC.y)); |
| | | } |
| | | |
| | | |
| | | private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC) |
| | | { |
| | | return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y)); |
| | | } |
| | | private static Vector2 _Max(Vector2 pA, Vector2 pB, Vector2 pC) |
| | | { |
| | | return new Vector2(_Max(pA.x, pB.x, pC.x), _Max(pA.y, pB.y, pC.y)); |
| | | } |
| | | |
| | | |
| | | // 生成材质池的键 |
| | | private string GetMaterialKey(Color color, int width) |
| | | { |
| | | // 使用颜色的RGBA值和宽度生成唯一键 |
| | | return string.Format("{0}_{1}_{2}_{3}_{4}", |
| | | color.r, color.g, color.b, color.a, width); |
| | | } |
| | | } |