Unity UGUI 文字描边与渐变
文字描边
OutLine
默认顶点数为16
将描边距离分别设置x=100,y=50,使用OutLine8 与禁用OutLine8 对比,如下图,顶点数(Verts)相差100
一个字=一张图=2个三角面=6个顶点(vertex)包括2个共用顶点
使用Text,一个文字对应4个顶点,其中2个顶点共用
使用OutLine8,相当于在Text文本后面多绘制了8个文本,此时顶点数=2*6*9=108
Outline8 Code
using System.Collections.Generic; using UGUIExtentions; namespace UnityEngine.UI { /// <summary> /// Adds an outline to a graphic using IVertexModifier. /// </summary> public class Outline8 : BaseMeshEffect { [SerializeField] private Color m_EffectColor = new Color(0f, 0f, 0f, 1f); [SerializeField] private Vector2 m_EffectDistance = new Vector2(1f, -1f); [SerializeField] private bool m_UseGraphicAlpha = true; private const float kMaxEffectDistance = 600f; protected Outline8() { } #if UNITY_EDITOR protected override void OnValidate() { effectDistance = m_EffectDistance; base.OnValidate(); } #endif /// <summary> /// Color for the effect /// </summary> public Color effectColor { get { return m_EffectColor; } set { m_EffectColor = value; if (graphic != null) graphic.SetVerticesDirty(); } } /// <summary> /// How far is the shadow from the graphic. /// </summary> public Vector2 effectDistance { get { return m_EffectDistance; } set { Mathf.Clamp(value.x, -kMaxEffectDistance, kMaxEffectDistance); Mathf.Clamp(value.y, -kMaxEffectDistance, kMaxEffectDistance); if (m_EffectDistance == value) return; m_EffectDistance = value; if (graphic != null) graphic.SetVerticesDirty(); } } /// <summary> /// Should the shadow inherit the alpha from the graphic? /// </summary> public bool useGraphicAlpha { get { return m_UseGraphicAlpha; } set { m_UseGraphicAlpha = value; if (graphic != null) graphic.SetVerticesDirty(); } } // 添加4个角的顶点 public override void ModifyMesh(VertexHelper vh) { if (!IsActive()) return; var verts = ListPool<UIVertex>.Get(); vh.GetUIVertexStream(verts); var neededCpacity = verts.Count * 5; if (verts.Capacity < neededCpacity) verts.Capacity = neededCpacity; // 顶点部分 开始 var start = 0; var end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, 0); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, 0); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, 0, effectDistance.y); start = end; end = verts.Count; ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, 0, -effectDistance.y); // 顶点部分 结束 // 顶点部分 优化写法 // 参考 https://github.com/n-yoda/unity-vertex-effects /*var original = verts.Count; var count = 0; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (!(x == 0 && y == 0)) { var next = count + original; ApplyShadowZeroAlloc(verts, effectColor, count, next, effectDistance.x * x, effectDistance.y * y); count = next; } } }*/ vh.Clear(); vh.AddUIVertexTriangleStream(verts); ListPool<UIVertex>.Release(verts); } protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y) { UIVertex vt; var neededCapacity = verts.Count + end - start; if (verts.Capacity < neededCapacity) verts.Capacity = neededCapacity; for (int i = start; i < end; ++i) { vt = verts[i]; verts.Add(vt); Vector3 v = vt.position; v.x += x; v.y += y; vt.position = v; var newColor = color; if (m_UseGraphicAlpha) newColor.a = (byte)((newColor.a * verts[i].color.a) / 255); vt.color = newColor; verts[i] = vt; } } } }
Text扩展
创建OutlineText继承Text,重写OnPopulateMesh,在内部进行描边
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class OutLineText : Text { public bool m_OutLine=true; public Color m_OutLineColor=Color.black; public float m_OutLineOffsetX=0f; public float m_OutLineOffsetY=0f; protected override void OnPopulateMesh(VertexHelper toFill) { //这里是直接复制的UGUI的Text生成定点的代码 Vector2 extent=rectTransform.rect.size; var settings= GetGenerationSettings(extent); cachedTextGenerator.Populate(this.text, settings); Rect inputRect = rectTransform.rect; // get the text alignment anchor point for the text in local space Vector2 textAnchorPivot = GetTextAnchorPivot(alignment); Vector2 refPoint = Vector2.zero; refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x); refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y); // Determine fraction of pixel to offset text mesh. Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint; // Apply the offset to the vertices IList<UIVertex> verts = cachedTextGenerator.verts; float unitsPerPixel = 1 / pixelsPerUnit; //Last 4 verts are always a new line... int vertCount = verts.Count - 4; toFill.Clear(); UIVertex[] rVertex=new UIVertex[4]; if (roundingOffset != Vector2.zero) { for (int i = 0; i < vertCount; ++i) { int tempVertsIndex = i & 3; rVertex[tempVertsIndex] = verts[i]; rVertex[tempVertsIndex].position *= unitsPerPixel; rVertex[tempVertsIndex].position.x += roundingOffset.x; rVertex[tempVertsIndex].position.y += roundingOffset.y; if (tempVertsIndex == 3) toFill.AddUIVertexQuad(rVertex); } } else { for (int i = 0; i < verts.Count-4; i++) { int tempVertsIndex = i & 3; rVertex[tempVertsIndex] = verts[i]; rVertex[tempVertsIndex].position.x += roundingOffset.x; rVertex[tempVertsIndex].position.y += roundingOffset.y; rVertex[tempVertsIndex].position *= unitsPerPixel; rVertex[tempVertsIndex].uv1 = Vector2.zero; if (m_OutLine && tempVertsIndex == 3) { ApplyShadowZeroAlloc(ref rVertex, m_OutLineColor, m_OutLineOffsetX, m_OutLineOffsetY, toFill); ApplyShadowZeroAlloc(ref rVertex, m_OutLineColor, m_OutLineOffsetX, -m_OutLineOffsetY, toFill); ApplyShadowZeroAlloc(ref rVertex, m_OutLineColor, -m_OutLineOffsetX, m_OutLineOffsetY, toFill); ApplyShadowZeroAlloc(ref rVertex, m_OutLineColor, -m_OutLineOffsetX, -m_OutLineOffsetY, toFill); toFill.AddUIVertexQuad(rVertex); } } } } private void ApplyShadowZeroAlloc(ref UIVertex[] rVertex, Color rEffectColor, float rEffectDistanceX, float rEffectDistanceY, VertexHelper rHelper) { for (int i = 0; i < rVertex.Length; i++) { Vector3 rPosition = rVertex[i].position; rPosition.x += rEffectDistanceX; rPosition.y += rEffectDistanceY; rVertex[i].position = rPosition; rVertex[i].color = rEffectColor; } rHelper.AddUIVertexQuad(rVertex); for (int i = 0; i < rVertex.Length; i++) { Vector3 rPosition = rVertex[i].position; rPosition.x -= rEffectDistanceX; rPosition.y -= rEffectDistanceY; rVertex[i].color = color; rVertex[i].position = rPosition; } } }
Shader优化
OutlineEx.shader
Shader "TSF Shaders/UI/OutlineEx" { Properties { _MainTex("Main Texture", 2D) = "white" {} _Color("Tint", Color) = (1, 1, 1, 1) // 描边颜色和宽度 _OutlineColor("Outline Color", Color) = (1, 1, 1, 1) _OutlineWidth("Outline Width", Int) = 1 _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 "OUTLINE" CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _MainTex_TexelSize; float4 _OutlineColor; int _OutlineWidth; struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float2 texcoord1 : TEXCOORD1; float2 texcoord2 : TEXCOORD2; fixed4 color : COLOR; }; struct v2f { float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; float2 uvOriginXY : TEXCOORD1; float2 uvOriginZW : TEXCOORD2; fixed4 color : COLOR; }; v2f vert(appdata IN) { v2f o; o.vertex = UnityObjectToClipPos(IN.vertex); o.texcoord = IN.texcoord; o.uvOriginXY = IN.texcoord1; o.uvOriginZW = IN.texcoord2; o.color = IN.color * _Color; return o; } // 检查点是否在给定矩形内 fixed IsInRect(float2 pPos, float4 pClipRect) { // step函数a>b返回0,否则返回1 pPos = step(pClipRect.xy, pPos) * step(pPos, pClipRect.zw); return pPos.x * pPos.y; } // 根据圆的参数方程计算采样坐标,使用alpha值进行颜色混合 fixed SampleAlpha(int pIndex, v2f IN) { const fixed sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 }; const fixed cosArray[12] = { 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 }; float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth; return IsInRect(pos, float4(IN.uvOriginXY, IN.uvOriginZW)) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w; } fixed4 frag(v2f IN) : SV_Target { fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; if (_OutlineWidth > 0) { color.w *= IsInRect(IN.texcoord, float4(IN.uvOriginXY, IN.uvOriginZW)); half4 val = half4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0); val.w += SampleAlpha(0, IN); val.w += SampleAlpha(1, IN); val.w += SampleAlpha(2, IN); val.w += SampleAlpha(3, IN); val.w += SampleAlpha(4, IN); val.w += SampleAlpha(5, IN); val.w += SampleAlpha(6, IN); val.w += SampleAlpha(7, IN); val.w += SampleAlpha(8, IN); val.w += SampleAlpha(9, IN); val.w += SampleAlpha(10, IN); val.w += SampleAlpha(11, IN); val.w = clamp(val.w, 0, 1); color = (val * (1.0 - color.a)) + (color * color.a); color.a = saturate(color.a); color.a *= IN.color.a; } return color; } ENDCG } } }
OutlineEx.cs
//———————————————————————————————————————————— // OutlineEx.cs // // Created by Chiyu Ren on 2018/9/12 23:03:51 //———————————————————————————————————————————— using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; namespace TooSimpleFramework.UI { /// <summary> /// UGUI描边 /// </summary> public class Outline : BaseMeshEffect { public Color OutlineColor = Color.white; [Range(0, 6)] public int OutlineWidth = 0; public Shader OutlineShader; private static List<UIVertex> m_VetexList = new List<UIVertex>(); protected override void Start() { base.Start(); var shader = OutlineShader != null ? OutlineShader : Shader.Find("TSF Shaders/UI/OutlineEx"); base.graphic.material = new Material(shader); // 修改additionalShaderChannels属性,将uv1和uv2传入shader 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 OnValidate() { base.OnValidate(); if (base.graphic.material != null) { this._Refresh(); } } #endif private void _Refresh() { 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) { 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); // 为每个顶点设置新的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); // 应用设置后的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, 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; uv += pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1); uv += pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1); pVertex.uv0 = uv; // 原始UV框 pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y); pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w); return pVertex; } 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 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)); } } }
文字渐变
Gradient.cs 由上到下渐变
// 渐变效果 using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; [AddComponentMenu("UI/Effects/Gradient", 13)] public class Gradient : BaseMeshEffect { [SerializeField] private Color32 m_TopColor = Color.white; [SerializeField] private Color32 m_BottomColor = Color.black; public Color32 topColor { get { return m_TopColor; } set { m_TopColor = value; } } public Color32 bottomColor { get { return m_BottomColor; } set { m_BottomColor = value; } } public override void ModifyMesh(VertexHelper vh) { if (!this.IsActive()) return; List<UIVertex> vertexList = new List<UIVertex>(); vh.GetUIVertexStream(vertexList); ModifyVertices(vertexList); vh.Clear(); vh.AddUIVertexTriangleStream(vertexList); } public void ModifyVertices(List<UIVertex> vertexList) { if (!IsActive() || vertexList.Count <= 0) { return; } for (int i = 0; i < vertexList.Count;) { float bottomY = vertexList[i].position.y; float topY = bottomY; float dis = 1f; for (int k = 1; k < 6; k++) { float y = vertexList[k + i].position.y; if (y > topY) { topY = y; } else if (y < bottomY) { bottomY = y; } } dis = topY - bottomY; for (int k = 0; k < 6; k++) { UIVertex vertText = vertexList[k + i]; vertText.color = Color32.Lerp(m_BottomColor, m_TopColor, (vertText.position.y - bottomY) / dis); vertexList[k + i] = vertText; } i += 6; } } }
TextGradient.cs 支持横向,纵向,左上至右下,左下至右上渐变
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(Text))] public class TextGradient : BaseMeshEffect { public enum GradientDirection { Horizontal, Vertical,LeftUpToRightDown,LeftDownToRightUp } [SerializeField] //定义序列化属性,一会的Editor中需要通过定义的这个属性找到该字段 public Color32 topColor = Color.white;//顶部颜色 [SerializeField] public Color32 bottomColor = Color.black;//底部颜色 [SerializeField] public GradientDirection gradientDirection;//渐变方向 [SerializeField] public bool useEffect = false;//是否使用阴影 [SerializeField] public Color effectColor = new Color(0f, 0f, 0f, 0.5f);//阴影颜色 [SerializeField] public Vector2 effectDistance = new Vector2(1f, -1f);//阴影偏移 private const int DefautlVertexNumPerFont = 6;//顶点数 List<UIVertex> vertexBuffers = new List<UIVertex>(); public Mesh mesh = null; /// <summary> /// 给顶点着色 /// </summary> /// <param name="vertexList"></param> /// <param name="index"></param> /// <param name="color"></param> private void ModifyVertexColor(List<UIVertex> vertexList, int index, Color color) { UIVertex temp = vertexList[index]; temp.color = color; vertexList[index] = temp; } //修改网格时调用 public override void ModifyMesh(VertexHelper vh) { if (!IsActive()) { return; } vh.GetUIVertexStream(vertexBuffers);//获取顶点 Debug.Log(vertexBuffers.Count); //在Inspector中显示网格 if (mesh == null) { mesh = new Mesh(); } vh.FillMesh(mesh); int count = vertexBuffers.Count; if (count > 0) { /**给顶点着色( 这里需要明白顶点的顺序 ) * 5-0 ---- 1 * | \ | * | \ | * | \ | * | \ | * 4-----3-2 **/ for (int i = 0; i < count; i += DefautlVertexNumPerFont) { //分别设置每个顶点的颜色 switch (gradientDirection) { case GradientDirection.Horizontal: ModifyVertexColor(vertexBuffers, i, topColor); ModifyVertexColor(vertexBuffers, i + 1, topColor); ModifyVertexColor(vertexBuffers, i + 2, bottomColor); ModifyVertexColor(vertexBuffers, i + 3, bottomColor); ModifyVertexColor(vertexBuffers, i + 4, bottomColor); ModifyVertexColor(vertexBuffers, i + 5, topColor); break; case GradientDirection.Vertical: ModifyVertexColor(vertexBuffers, i, topColor); ModifyVertexColor(vertexBuffers, i + 1, bottomColor); ModifyVertexColor(vertexBuffers, i + 2, bottomColor); ModifyVertexColor(vertexBuffers, i + 3, bottomColor); ModifyVertexColor(vertexBuffers, i + 4, topColor); ModifyVertexColor(vertexBuffers, i + 5, topColor); break; case GradientDirection.LeftUpToRightDown: ModifyVertexColor(vertexBuffers, i, topColor); ModifyVertexColor(vertexBuffers, i + 1, bottomColor); ModifyVertexColor(vertexBuffers, i + 2, topColor); ModifyVertexColor(vertexBuffers, i + 3, topColor); ModifyVertexColor(vertexBuffers, i + 4, bottomColor); ModifyVertexColor(vertexBuffers, i + 5, topColor); break; case GradientDirection.LeftDownToRightUp: ModifyVertexColor(vertexBuffers, i, bottomColor); ModifyVertexColor(vertexBuffers, i + 1, topColor); ModifyVertexColor(vertexBuffers, i + 2, bottomColor); ModifyVertexColor(vertexBuffers, i + 3, bottomColor); ModifyVertexColor(vertexBuffers, i + 4, topColor); ModifyVertexColor(vertexBuffers, i + 5, bottomColor); break; default: break; } } } if (useEffect)//是否使用阴影(如果不需要阴影功能可以这部分代码删掉) { //扩充一倍的顶点容量 var neededCapacity = vertexBuffers.Count * 2; if (vertexBuffers.Capacity < neededCapacity) vertexBuffers.Capacity = neededCapacity; for (int i = 0, cnt = vertexBuffers.Count; i < cnt; ++i) { var vt = vertexBuffers[i]; vertexBuffers.Add(vt); Vector3 v = vt.position; v.x += effectDistance.x; v.y += effectDistance.y; vt.position = v; vt.color = effectColor; vertexBuffers[i] = vt; } } vh.Clear(); //这个方法向VertexHelper中批量增加三角形顶点数据,参数的长度必须是三的倍数 vh.AddUIVertexTriangleStream(vertexBuffers); } /// <summary> /// 在Scene中显示顶点 /// </summary> void OnDrawGizmos() { Gizmos.color = Color.red;//设置颜色 for (int i = 0; i < vertexBuffers.Count; i++) { //把mesh顶点转为世界坐标 Vector3 targetPosition = transform.TransformPoint(vertexBuffers[i].position); Gizmos.DrawSphere(targetPosition, 5f); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)