Unity利用MaskableGraphic类画自定义UI图形,并且对顶点设置UV坐标,然后用贴图映射效果

目的

文章解决的目的

1、起初目的是因为XChart的3D图表没有提供美化3D环形圆柱饼图的效果;

目标效果和当下的效果

2、下面是我们UI设计的效果。
在这里插入图片描述
3、这是默认画出的效果
在这里插入图片描述
4、目前达到的效果
在这里插入图片描述
在这里插入图片描述

分析

UIShader来实现

1、搜索了很多效果实现方案,其中知乎作者的UIShader效果中找到了一个扫光效果
https://zhuanlan.zhihu.com/p/399478677

2、但是经过测试,扫光效果对XChart生成的Mesh并没有影响,于是我想到了先用贴图叠加的方式写Shader

// Properties
_Tex("Tex", 2D) = "white" {}//叠加颜色

//Pass
sampler2D _Tex;//自己的
float4 _Tex_ST;

//Vert //这是把新贴图的UV映射到UI上
OUT.texcoord[0] = IN.texcoord;
//OUT.texcoord[0] = TRANSFORM_TEX(IN.texcoord, _MainTex);
OUT.texcoord[1] = TRANSFORM_TEX(IN.texcoord.xy, _Tex);

//frag
half4 color = tex2D(_MainTex,IN.texcoord[0]) * IN.color * _Color;//这是原来UI的
fixed4 col = tex2D(_Tex, IN.texcoord[1]);//它的问题在于没有UV
color.rgb*=col.rgb*_Factor;//额外图片叠加
color.a*=(0.1+col.a);//确保遇到透明的部位不是绝对透明
return color;

最终问题所在

3、经过测试发现是因为XChart利用继承MaskableGraphic 重写OnPopulateMesh实现图形绘制时候吧所有UV都设置到了一个点Vector.zero,导致贴图无法顺利叠加。
4、下图分析如何设置顶点UV坐标地址
在这里插入图片描述

实施

抄了一遍XChart的实现绘制代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(CanvasRenderer))]//MaskableGraphic必须依赖CanvasRenderer
public class MyPainter : MaskableGraphic
{

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        var currentVertCount = vh.currentVertCount;
        int count = 250;
        Vector3 center = new Vector3(0, -80, 0);
        float hig = 30;
        float startAngle = 0;
        float endAngle = 356;
        var diff = (endAngle - startAngle) / count;
        float inw = 200;
        float inh = 130;
        float outw = 240;
        float outh = 156;
        var isLeft = false;
        Color startSideColor = new Color(0, 1, 0, 1);
        Color endSideColor = new Color(0, 0,1, 1);
        Color sideColor = new Color(1, 0, 0, 1);
        Color color_w = new Color(1, 1, 1, 1);
        var index = 1;
        Debug.Log(string.Format("count:{0} ,center{1} ,hig {2} diff{3} inw{4} inh{5}  outw{6} outh{7} ",
                             count, center, hig, diff, inw, inh, outw, outh));
        for (int i = 0; i <= count; i++)
        {
            var angle = startAngle + i * diff;
            var rad = (360 - angle) * Mathf.Deg2Rad;
            var sin = Mathf.Sin(rad);
            var cos = Mathf.Cos(rad);
            var ix = center.x + inw * cos;
            var iy = center.y + inh * sin;
            var ox = center.x + outw * cos;
            var oy = center.y + outh * sin;
            var ipb = new Vector3(ix, iy);
            var ipt = new Vector2(ix, iy + hig);
            var opb = new Vector3(ox, oy);
            var opt = new Vector2(ox, oy + hig);
            isLeft = opb.x < center.x;
            if (i == 0)
            {
                //slice.topPolygons.Add(ipt);
                //slice.topPolygons.Add(opt);
                //slice.sidePolygons.Add(opb);
                //slice.sidePolygons.Add(opt);
                vh.AddVert(ipb, Color.red, Vector2.zero); //0
                vh.AddVert(ipt, Color.blue, Vector2.zero); // 1
                vh.AddVert(opb, Color.green, Vector2.zero); // 2
                vh.AddVert(opt, Color.yellow, Vector2.zero); // 3
            }
            vh.AddVert(ipb, color_w, new Vector2(i,190)); //4  //圆环上部内壁下方  //0  blue
            vh.AddVert(ipt, color_w, new Vector2(i, 250)); //5 //圆环上部内壁上方  //0  yellow
            vh.AddVert(ipt, color_w, new Vector2(i, 0)); //6 //圆环 整个上面内环  //1

            vh.AddVert(opb, color_w, new Vector2(i, 75)); //7 圆环 外壁下环   //2   red
            vh.AddVert(opt, color_w, new Vector2(i, 135)); //8//圆环 外壁上环  //2  green
            vh.AddVert(opt, color_w, new Vector2(i, 60)); //9  //圆环 整个上面外环 //1
            index = currentVertCount + (i * 6) + 4;
            if (i == 0)
            {
                if (isLeft &&true /*startSide*/)
                {
                    //slice.startOrEndPolygons.Add(ipb);
                    //slice.startOrEndPolygons.Add(ipt);
                    //slice.startOrEndPolygons.Add(opt);
                    //slice.startOrEndPolygons.Add(opb);
                    vh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 3); // startSide: 0 1 3
                    vh.AddTriangle(currentVertCount, currentVertCount + 3, currentVertCount + 2); //startSide: 1 5 6
                }
            }
            else
            {
                //inside
                if (angle >= 180 && angle <= 360)
                {
                    vh.AddTriangle(index, index + 1, index - 5); // inside: 10 11 5
                    vh.AddTriangle(index, index - 5, index - 6); // inside: 10 5 4
                }
                //top
                vh.AddTriangle(index - 4, index - 1, index + 2); // top: 6 9 12
                vh.AddTriangle(index + 2, index - 1, index + 5); // top: 12 9 15
                                                                 // //outside
                if (angle >= 0 && angle <= 180)
                {
                    vh.AddTriangle(index + 3, index + 4, index - 2); // outside: 13 14 8
                    vh.AddTriangle(index + 3, index - 2, index - 3); // outside: 13 8 7
                }
            }
            if (i == count)
            {
                //slice.topPolygons.Add(opt);
                //slice.topPolygons.Add(ipt);
                //slice.sidePolygons.Add(opt);
                //slice.sidePolygons.Add(opb);
                if (!isLeft &&true /*endSide*/)
                {
                    //slice.startOrEndPolygons.Add(ipb);
                    //slice.startOrEndPolygons.Add(ipt);
                    //slice.startOrEndPolygons.Add(opt);
                    //slice.startOrEndPolygons.Add(opb);
                    vh.AddVert(ipb, endSideColor, Vector2.zero); //22            //截面积 左下角
                    vh.AddVert(ipt, endSideColor, Vector2.zero); //23            //截面积 左上角
                    vh.AddVert(opb, endSideColor, Vector2.zero); //24            //截面积 右下角
                    vh.AddVert(opt, endSideColor, Vector2.zero); //25                    //截面积 右上角
                    vh.AddTriangle(index + 6, index + 7, index + 9); // endSide: 22 23 25  //一个三角形顺时针画
                    vh.AddTriangle(index + 6, index + 9, index + 8); // endSide: 22 25 24 //都是顺时针画  好像是优先画和之前画的相邻的边
                }
            }
        }

    }
}

Shader源代码

Shader "UGUI/UIShaderTexAdd"
{
	Properties
	{
		[PerRendererData] _MainTex("MainTex", 2D) = "white" {}
				 _Color("Tint", Color) = (1,1,1,1)

		[Header(Stencil)]
		_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
		_Factor("_Factor",Float)=1
		
		
		_Width("宽度",float)=1// 6 7 消融和扫光都用
		[HDR]_Brightness("扫光颜色",Color)=(1,1,1,1) //扫光用
		_Softness("柔和度",float)=1//扫光用
		_Gloss("光泽度",float)=1//扫光用
		_Rotation("旋转度",float)=15//扫光用
		
		
		_Tex("Tex", 2D) = "white" {}//叠加颜色
		_ColorMask("Color Mask", Float) = 15  //_ColorMask:设置颜色通道写入遮罩。写入 ColorMask 0 可关闭对所有颜色通道的渲染。
				[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0  //_UseUIAlphaClip是否用clip来进行不显示 。[Toggle(UNITY_UI_ALPHACLIP)], 如果Toggle是true , 则自动#define UNITY_UI_ALPHACLIP ,可以用#ifdef进行预处理。
	}

		SubShader
		{
			Tags
			{
				"Queue" = "Transparent"
				"IgnoreProjector" = "True"
				"RenderType" = "Transparent"
				"PreviewType" = "Plane"
				"CanUseSpriteAtlas" = "True"
			}

			Stencil
			{
				Ref[_Stencil] //_Stencil 模板测试相关属性,Unity会自动修改参数,UI可以使用模板测试进行不显示。
				Comp[_StencilComp]
				Pass[_StencilOp]
				ReadMask[_StencilReadMask]
				WriteMask[_StencilWriteMask]
			}
				Cull Off   //关闭剔除
			Lighting Off
			ZWrite Off  //关闭深度写人
			ZTest[unity_GUIZTestMode]
			Blend SrcAlpha OneMinusSrcAlpha
			ColorMask[_ColorMask]

			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				#pragma multi_compile __ UNITY_UI_ALPHACLIP

				#include "UnityCG.cginc"
				#include "UnityUI.cginc"

				struct appdata_t
				{
					float4 vertex   : POSITION;
									float4 color    : COLOR;
					float2 texcoord : TEXCOORD0;
					UNITY_VERTEX_INPUT_INSTANCE_ID
				};
					struct v2f
				{
					float4 vertex   : SV_POSITION;
					float4 color    : COLOR;
					half2 texcoord[2]  : TEXCOORD0;
					UNITY_VERTEX_OUTPUT_STEREO
				};

				sampler2D _MainTex;
				float4 _MainTex_TexelSize;
							float4 _Color;
				float _Factor;

				float _Width;//消融 扫光
				float4 _Brightness;//扫光用
				float _Softness;//扫光
				float _Gloss;//扫光用
				float _Rotation;//扫光用
			
				sampler2D _Tex;//自己的
				float4 _Tex_ST;
				
				v2f vert(appdata_t IN)
				{
					v2f OUT;
					UNITY_SETUP_INSTANCE_ID(IN);
					UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

					OUT.vertex = UnityObjectToClipPos(IN.vertex);
							
								OUT.color = IN.color;
					OUT.texcoord[0] = IN.texcoord;
					//OUT.texcoord[0] = TRANSFORM_TEX(IN.texcoord, _MainTex);
                    OUT.texcoord[1] = TRANSFORM_TEX(IN.texcoord.xy, _Tex);
					return OUT;
				}

				//=================UV旋转算法
				float2x2 Rotation(half Angle)
		       {
		            Angle=UNITY_PI/180*Angle;
		            half cosAngle = cos(Angle);
		            half sinAngle = sin(Angle);
		            float2x2 rot = float2x2(cosAngle,-sinAngle,sinAngle,cosAngle);
		            return rot;
		        }
			
				fixed4 frag(v2f IN) : SV_Target
				{
					half4 color = tex2D(_MainTex,IN.texcoord[0]) * IN.color * _Color;

					fixed4 col = tex2D(_Tex, IN.texcoord[1]);//它的问题在于没有UV
					//=========================================================


					//=================7 扫描光  可以用贴图或代码得到扫光线,叠加在纹理贴图上,用_Factor控制扫光位置。

					float2 shinyUv=mul(Rotation(_Rotation),IN.texcoord[0]-0.5);

					fixed nomalizedPos = shinyUv.x;
					half location = _Factor * 2 - 0.5;
					half normalized = 1 - saturate(abs((nomalizedPos - location) / _Width));
					half shinePower = smoothstep(0, _Softness, normalized);
					half3 reflectColor = lerp(fixed3(1,1,1), color.rgb * 7, _Gloss);
					color.rgb += color.a * (shinePower / 2) * _Brightness * reflectColor*_Color;
					//=========================================================
					
					color.rgb*=col.rgb*_Factor;//额外图片叠加
					color.a*=(0.1+col.a);

					//更多  https://zhuanlan.zhihu.com/p/399478677
					return color;
				}
			ENDCG
			}
		}
}
 //加入颜色叠加
 fixed4 ifFlag= step(color,fixed4(0.5,0.5,0.5,0.5));//叠加
 fixed4 C=ifFlag*color*blCol*2+(1-ifFlag)*(1-(1-color)*(1-blCol)*2);//叠加
 return C;

在这里插入图片描述

<think>好的,我现在需要帮助用户解决如何在Unity自定义顶点组成一个扇形模型的问题。用户之前询问过关于ShaderGraph实现扇形变换,现在转向顶点创建,可能是想从更底层理解模型的构建。 首先,我得确认用户的需求。他们可能想通过代码动态生成扇形,而不是使用Shader。常见应用比如技能范围指示器、雷达扫描或者UI元素。需要详细说明顶点、三角形索引的计算方法,以及如何在Unity中实现。 接下来,考虑用户的背景。可能有一定的Unity基础,但对网格生成不太熟悉。需要分步骤讲解,确保每一步都清晰。需要涵盖顶点位置计算、UV展开、三角形索引生成,以及可能的优化技巧和常见问题。 需要解释极坐标到笛卡尔坐标的转换,因为扇形是圆形的一部分。角度范围和半径是关键参数。顶点的数量由分段数决定,分段越多越平滑,但性能消耗也大。 然后,如何设置三角形索引。扇形的中心点连接各个边顶点,形成三角形。注意索引的顺序,避免背面剔除问题。 还要提到UV映射,可能用户希望纹理正确贴图,需要将极坐标转换为UV坐标,或者简单的径向映射。 最后,可能需要提醒内存优化,比如使用List<Vector3>临时存储顶点,再赋值给数组。以及重置网格的重要性,避免旧数据影响。 常见问题比如顶点顺序错误导致的面片翻转,半径和角度单位是否正确(弧度还是角度),以及如何验证生成的网格是否正确,比如在Scene视图中查看。 可能用户还会需要动态调整参数,比如实时改变角度或半径,这时候需要重新生成网格,可以封装成方法。 总结步骤:计算顶点位置,设置UV,生成三角形索引,处理网格数据,附加到MeshFilter,最后优化和调试。</think>在Unity中通过代码自定义生成扇形网格模型,可以通过以下步骤实现: --- ### **一、核心原理** 扇形的本质是**由中心点向外辐射的三角形组合**,关键要素: 1. **顶点计算**:基于极坐标系生成顶点坐标 2. **三角形索引**:连接中心点与边缘顶点形成三角面 3. **UV展开**:将极坐标转换为0-1纹理坐标 --- ### **二、完整实现步骤** #### **1. 参数定义** ```csharp public float radius = 1.0f; // 扇形半径 public float angle = 90.0f; // 扇形角度(单位:度) public int segments = 20; // 分段数(控制圆弧平滑度) public Vector3 center = Vector3.zero; // 圆心位置 ``` #### **2. 顶点计算** ```csharp List<Vector3> vertices = new List<Vector3>(); vertices.Add(center); // 添加中心点 float startAngle = -angle / 2; // 起始角度 float angleStep = angle / segments; for(int i = 0; i <= segments; i++){ float currentAngle = startAngle + i * angleStep; Vector3 pos = new Vector3( Mathf.Cos(currentAngle * Mathf.Deg2Rad), 0, Mathf.Sin(currentAngle * Mathf.Deg2Rad) ) * radius; vertices.Add(pos + center); } ``` #### **3. 三角形索引生成** ```csharp List<int> triangles = new List<int>(); for(int i = 1; i <= segments; i++){ triangles.Add(0); // 中心点 triangles.Add(i); // 当前顶点 triangles.Add(i + 1); // 下一顶点 } ``` #### **4. UV展开(简单径向映射)** ```csharp List<Vector2> uvs = new List<Vector2>(); uvs.Add(new Vector2(0.5f, 0.5f)); // 中心UV foreach(Vector3 vert in vertices.Skip(1)){ Vector2 uv = new Vector2( (vert.x - center.x) / (2 * radius) + 0.5f, (vert.z - center.z) / (2 * radius) + 0.5f ); uvs.Add(uv); } ``` #### **5. 生成Mesh** ```csharp Mesh mesh = new Mesh(); mesh.SetVertices(vertices); mesh.SetTriangles(triangles, 0); mesh.SetUVs(0, uvs); mesh.RecalculateNormals(); GetComponent<MeshFilter>().mesh = mesh; ``` --- ### **三、高级优化技巧** 1. **动态更新**:封装成`GenerateSector()`方法,参数变化时调用 2. **内存优化**:预分配List容量 ```csharp vertices.Capacity = segments + 2; triangles.Capacity = segments * 3; ``` 3. **法线计算**:手动设置法线向量提高性能 4. **双面渲染**:复制顶点并反转三角形顺序 --- ### **四、参数对照表** | 参数 | 作用 | 典型值 | |------|-----|-------| | `radius` | 控制扇形半径 | 1.0-5.0 | | `angle` | 控制扇形张角 | 30°-360° | | `segments` | 控制圆弧精度 | 20-60 | | `center.y` | 控制垂直高度 | 地形适配 | --- ### **五、常见问题排查** 1. **面片不可见**:检查法线方向是否正确,添加`mesh.RecalculateNormals()` 2. **三角面撕裂**:确认顶点索引未越界,验证`triangles`最大值不超过顶点数 3. **UV拉伸**:改用极坐标UV映射 ```csharp float u = currentAngle / 360.0f; float v = radius / maxRadius; ``` --- ### **六、完整组件代码** ```csharp [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class SectorGenerator : MonoBehaviour { // 参数配置 [Range(10, 360)] public float angle = 90.0f; [Range(0.1f, 10f)] public float radius = 2.0f; [Range(3, 60)] public int segments = 30; void Start() => GenerateSector(); public void GenerateSector() { Mesh mesh = new Mesh(); List<Vector3> verts = new List<Vector3>(); List<int> tris = new List<int>(); List<Vector2> uvs = new List<Vector2>(); // 顶点计算(同上步骤2) // 三角形生成(同上步骤3) // UV展开(同上步骤4) mesh.Clear(); mesh.SetVertices(verts); mesh.SetTriangles(tris, 0); mesh.SetUVs(0, uvs); mesh.RecalculateNormals(); GetComponent<MeshFilter>().mesh = mesh; } #if UNITY_EDITOR void OnValidate() { if(Application.isPlaying) GenerateSector(); } #endif } ``` --- ### **七、应用场景** 1. **游戏技能指示器**:动态调整angle参数实现技能范围可视化 2. **雷达扫描效果**:配合Shader实现旋转扫描 3. **数据可视化**:用于饼状图生成 4. **VR交互区域**:定义可交互的扇形区域 实际使用时建议结合`LineRenderer`绘制边界线,或添加`MeshCollider`实现物理交互。通过修改顶点Y值可创建3D扇形空间(如圆锥区域检测)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歌德之殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值