Unity引擎在UI上渲染粒子播放

  大家好,我是阿赵。
  在UI上面显示粒子特效,如果把粒子系统直接拖到Canvas里面,会存在很多问题,比如层级问题、裁剪问题等。这里分享一种用MaskableGraphic和UIVertex来显示粒子特效的方法。

一、 MaskableGraphic和UIVertex简单显示原理

1、简单例子

  在介绍MaskableGraphic和UIVertex是什么之前,先来运行一段代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class UIVertexTest : MaskableGraphic
{private UIVertex[] _quad = new UIVertex[4];// Start is called before the first frame updateprivate new IEnumerator Start(){_quad[0] = UIVertex.simpleVert;_quad[0].color = Color.green;_quad[0].uv0 = new Vector2(0, 0);_quad[0].position = new Vector3(-100, 0);_quad[1] = UIVertex.simpleVert;_quad[1].color = Color.red;_quad[1].uv0 = new Vector2(0, 1);_quad[1].position = new Vector3(-100, 200);_quad[2] = UIVertex.simpleVert;_quad[2].color = Color.black;_quad[2].uv0 = new Vector2(1, 1);_quad[2].position = new Vector3(100, 200);_quad[3] = UIVertex.simpleVert;_quad[3].color = Color.blue;_quad[3].uv0 = new Vector2(1, 0);_quad[3].position = new Vector3(100, 0);yield return null;}// Update is called once per framevoid Update(){}protected override void OnPopulateMesh(VertexHelper vh){vh.Clear();vh.AddUIVertexQuad(_quad);}}

  在Canvas里面新建一个空的GameObject,然后把脚本挂上去,运行,会得到这样的结果:
在这里插入图片描述

  可以试试层叠或是用Mask做裁剪,发现都没有问题:
在这里插入图片描述
在这里插入图片描述

2、代码解释

  这里用到了几个东西,是要说明一下的:

1. MaskableGraphic

  可以留意到,上面的代码的类并不是继承MonoBehaviour,而是继承了MaskableGraphic。
  MaskableGraphic 继承自 Graphic,并通过 RectMask2D 和 Mask 实现 “可遮罩的图形”。如果觉得复杂,可以简单这么理解,继承MaskableGraphic 的类,挂在Canvas下的对象上时,  这个对象会变成一个类似Image的对象,可以在上面绘制自己想要的东西。
在这里插入图片描述

  继承了MaskableGraphic 之后,一般来说就会出现这些参数了,比如颜色、材质球、是否可以成为射线的目标、是否可遮罩等。是不是和Image很像?

2. UIVertex

  在理解UIVertex之前,要先对Vertex有所了解。一个3D模型能显示出来,最基础需要2点:
(1) 顶点,比如一个三角形需要3个顶点,顶点包括一般包括坐标、颜色、UV坐标之类的信息。
(2) 索引,为什么三个点能构成一个三角形,是因为有索引,比如三个顶点1、2、3,组成了一个  三角形,那么如果是四个顶点1、2、3、4,可以组成2个三角形,可能是1、2、3一组,1、3、4一组。
  这上面的顶点,就是Vertex了。那么UIVertex就很好理解了,它也是顶点,但只是用在UI上的顶点,更具体一点的,就是在MaskableGraphic这个“画纸”上面绘制图形的顶点。
在这里插入图片描述

  具体看一下UIVertex这个类,里面包含的参数并不多,有顶点的位置、法线方向、切线方向、颜色,还有uv0-uv3这么4组UV坐标。
  有了顶点信息,我们就可以自己绘制图形了。

3. OnPopulateMesh

  这个方法是MaskableGraphic里面的方法,我们可以通过override重写它的逻辑。这个方法调用的时机是MaskableGraphic里面的顶点发生改变时,具体一点,比如OnEnable、需要重新生成顶点、改变顶点的颜色、改变MaskableGraphic使用的材质球,之类。
  如果想在没有顶点改变的情况下也调用这个方法,可以通过调用SetAllDirty()方法,也会强制的执行OnPopulateMesh方法。
  这个例子里面,由于我只是绘制一个矩形,也不需要修改,所以我并没有调用SetAllDirty方法,也就是说,除了在一开始的时候绘制了一次矩形,后面实际上这个方法是不会再次调用,除非我手动去修改颜色和材质球。

4. VertexHelper

  作为OnPopulateMesh方法的传入参数VertexHelper是管理了当前MaskableGraphic 这张“画纸”上面的所有顶点。
在这里插入图片描述

  看一下VertexHelper所提供的方法,可以看出,我们可以对MaskableGraphic 添加点、添加三角形、添加四边形、添加整个网格、清理等操作。
  我这个例子,使用了AddUIVertexQuad方法,也就是添加一个四边形。一个四边形是由4个顶点组成2个三角形实现的。每次调用OnPopulateMesh方法的时候,需要先把VertexHelper调用Clear方法清空一下,再重新添加顶点或者三角形,不然会重复添加,越来越多。

二、 通过获取大小说明position的计算

  上面的例子比较简单,赋予了四个顶点固定的UV坐标,不同的颜色,然后position是相对于GameObject自己的坐标的相对坐标,写死了宽度是-100到100,高度是0到200,所以整个矩形是往上偏的。
  这次稍微做复杂一点点,我需要读取RectTransform里面的大小来改变四个顶点的位置,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class UIVertexTest : MaskableGraphic
{private UIVertex[] _quad = new UIVertex[4];// Start is called before the first frame updateprivate new IEnumerator Start(){UpdateQuad();yield return null;}private void UpdateQuad(){Rect rect = gameObject.GetComponent<RectTransform>().rect;_quad[0] = UIVertex.simpleVert;_quad[0].color = Color.green;_quad[0].uv0 = new Vector2(0, 0);_quad[0].position = new Vector3(rect.center.x - rect.width / 2, rect.center.y - rect.height / 2, 0);_quad[1] = UIVertex.simpleVert;_quad[1].color = Color.red;_quad[1].uv0 = new Vector2(0, 1);_quad[1].position = new Vector3(rect.center.x - rect.width / 2, rect.center.y + rect.height / 2, 0);_quad[2] = UIVertex.simpleVert;_quad[2].color = Color.black;_quad[2].uv0 = new Vector2(1, 1);_quad[2].position = new Vector3(rect.center.x + rect.width / 2, rect.center.y + rect.height / 2, 0);_quad[3] = UIVertex.simpleVert;_quad[3].color = Color.blue;_quad[3].uv0 = new Vector2(1, 0);_quad[3].position = new Vector3(rect.center.x + rect.width / 2, rect.center.y - rect.height / 2, 0);}// Update is called once per framevoid Update(){}protected override void OnPopulateMesh(VertexHelper vh){      vh.Clear();UpdateQuad();vh.AddUIVertexQuad(_quad);}
}

  现在改变RectTransform里面的宽高
在这里插入图片描述

  可以看到绘制出来的矩形也跟着变化了。
在这里插入图片描述

这次的代码修改主要有:

  1. 把组装顶点的方法从start里面提取出来,封了一个UpdateQuad方法
  2. 通过获取RectTransform的rect,来计算顶点的实际位置。

三、 材质贴图的应用

  只是显示顶点颜色有点单调,这次试试绘制图片。由于之前组建四个顶点的时候,就已经设置了uv0,这个uv0是根据顶点的位置设置了从0,0到1,1四个角的uv坐标,所以把图片赋予进去,按道理是可以直接把图片完整铺满整个矩形的。
  这里需要注意,由于是绘制在UI上的图片,所以需要配合着UI类型的Shader才能正确显示。这里我写了一个最简单的显示图片采用的UI类shader:
在这里插入图片描述

  直接把材质球拖进去就行:
在这里插入图片描述

  图片就能正常显示了。
在这里插入图片描述

  当然,一般我们不会这样拖材质球去使用,所以我在代码里面暴露一个材质球参数,用于修改材质球。

public Material curMat;

  然后在Update里面,调用一个CheckMatChange的方法,检查当前的MaskableGraphic的material如果不等于我指定的材质球,就会设置材质球。material是MaskableGraphic本身的变量,不需要额外声明的。

void Update()
{
CheckMatChange();
}

private void CheckMatChange()
{
if(material!=curMat)
{
material = curMat;
}
}

  还有一个值得注意的地方是,我这里也没有调用SetAllDirty(),但只要修改材质球,就立刻生效了。这是因为,材质球改变,也是调用OnPopulateMesh方法的条件之一,所以不需要其他操作,单纯修改材质球,就已经会调用一次OnPopulateMesh了。
完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class UIVertexTest : MaskableGraphic
{private UIVertex[] _quad = new UIVertex[4];public Material curMat;// Start is called before the first frame updateprivate new IEnumerator Start(){UpdateQuad();yield return null;}private void UpdateQuad(){Rect rect = gameObject.GetComponent<RectTransform>().rect;_quad[0] = UIVertex.simpleVert;_quad[0].color = Color.green;_quad[0].uv0 = new Vector2(0, 0);_quad[0].position = new Vector3(rect.center.x - rect.width / 2, rect.center.y - rect.height / 2, 0);_quad[1] = UIVertex.simpleVert;_quad[1].color = Color.red;_quad[1].uv0 = new Vector2(0, 1);_quad[1].position = new Vector3(rect.center.x - rect.width / 2, rect.center.y + rect.height / 2, 0);_quad[2] = UIVertex.simpleVert;_quad[2].color = Color.black;_quad[2].uv0 = new Vector2(1, 1);_quad[2].position = new Vector3(rect.center.x + rect.width / 2, rect.center.y + rect.height / 2, 0);_quad[3] = UIVertex.simpleVert;_quad[3].color = Color.blue;_quad[3].uv0 = new Vector2(1, 0);_quad[3].position = new Vector3(rect.center.x + rect.width / 2, rect.center.y - rect.height / 2, 0);}// Update is called once per framevoid Update(){CheckMatChange();}private void CheckMatChange(){if(material!=curMat){material = curMat;}}protected override void OnPopulateMesh(VertexHelper vh){      vh.Clear();UpdateQuad();vh.AddUIVertexQuad(_quad);}
}

四、 模拟粒子

  通过上面的说明,按道理应该大概了解了怎样通过UIVertex在MaskableGraphic上绘制图形了,接下来就进入主题:

1、 简单模拟粒子

先上代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static UnityEngine.ParticleSystem;[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class UIVertexTest : MaskableGraphic
{private UIVertex[] _quad = new UIVertex[4];public Material curMat;public ParticleSystem particleSys;private ParticleSystemRenderer particleSysRender;private ParticleSystem.MainModule mainModule;private Particle[] particles;// Start is called before the first frame updateprivate new IEnumerator Start(){if(particleSys == null){particleSys = gameObject.GetComponent<ParticleSystem>();if(particleSys!=null){particleSysRender = particleSys.GetComponent<ParticleSystemRenderer>();particleSysRender.enabled = false;mainModule = particleSys.main;}}yield return null;}private void UpdateQuad(ParticleSystem ps, Particle p){Color curCol = p.GetCurrentColor(ps);float size = p.GetCurrentSize(ps) * 0.5f;Vector2 position = (mainModule.simulationSpace == ParticleSystemSimulationSpace.Local ? p.position : ps.transform.InverseTransformPoint(p.position));float scale = canvas.gameObject.GetComponent<RectTransform>().localScale.x;position /= scale;size /= scale;_quad[0] = UIVertex.simpleVert;_quad[0].color = curCol;_quad[0].uv0 = new Vector2(0, 0);_quad[0].position = new Vector3(position.x - size, position.y - size);_quad[1] = UIVertex.simpleVert;_quad[1].color = curCol;_quad[1].uv0 = new Vector2(0, 1);_quad[1].position = new Vector3(position.x - size, position.y + size);_quad[2] = UIVertex.simpleVert;_quad[2].color = curCol;_quad[2].uv0 = new Vector2(1, 1);_quad[2].position = new Vector3(position.x + size, position.y + size);_quad[3] = UIVertex.simpleVert;_quad[3].color = curCol; _quad[3].uv0 = new Vector2(1, 0);_quad[3].position = new Vector3(position.x + size, position.y - size);}// Update is called once per framevoid Update(){CheckMatChange();
CheckParticle();}private void CheckMatChange(){if(material!=curMat){material = curMat;}}private void CheckParticle(){if (particleSys != null){SetAllDirty();}}protected override void OnPopulateMesh(VertexHelper vh){      vh.Clear();if (particleSys == null){return;}if(particles == null){particles = new Particle[500];}int count = particleSys.GetParticles(particles);if(count == 0){return;}for(int i = 0;i<count;i++){Particle p = particles[i];UpdateQuad(particleSys, p);vh.AddUIVertexQuad(_quad);}}
}

  现在把一个简单的粒子系统放在Canvas下面,然后把脚本挂到粒子系统上。给它赋予一个UI类的shader。这时候运行,可以看到粒子系统上面的renderer是关闭状态的:
在这里插入图片描述

  但UI上面却看到了粒子效果:
在这里插入图片描述

  而且这个粒子可以裁剪、可以随意重叠:
在这里插入图片描述

在这里插入图片描述

接下来说一下原理:

  1. 从粒子系统里面,获取粒子的数量,然后做一个循环,每一个粒子绘制一个四边形,然后计算粒子的大小和位置。
  2. 为了怕绘制得太多,所以获取粒子的时候,数组最大值只设置了500:
    particles = new Particle[500];
    当然,这样做可能也不太好,因为如果粒子系统本身发射的粒子太多,只获取500个可能表现会有问题,所以在设计粒子的时候,可以在粒子系统上面设置一下粒子的最大发射数量,保证粒子在有限数量内,效果是正常的。
  3. 计算粒子四个顶点的位置
    每个粒子的位置和当前大小是可以获得的,但位置需要乘以自身的Transform的逆矩阵来计算
Vector2 position = (mainModule.simulationSpace == ParticleSystemSimulationSpace.Local ? p.position : ps.transform.InverseTransformPoint(p.position));
  1. 粒子的大小问题
    由于粒子是放在Canvas里面的,所以实际上它是经过了Canvas的缩放的。所以需要获取Canvas的缩放,然后给计算的顶点做一个缩放

2、 模拟粒子高级效果

  刚才的粒子模拟,是最基础的,每个粒子都是只计算了位移和缩放,没有计算旋转,还有,没有支持粒子系统本身的一些特殊效果,比如序列帧的播放功能。下面补全一下:

1. 考虑旋转

修改UpdateQuad方法,把position的设置放在最后面,判断一下旋转,并做一个偏移:

    private void UpdateQuad(ParticleSystem ps, Particle p){Color curCol = p.GetCurrentColor(ps);float size = p.GetCurrentSize(ps) * 0.5f;float rotation = -p.rotation * Mathf.Deg2Rad;float rotation90 = rotation + Mathf.PI / 2;Vector2 position = (mainModule.simulationSpace == ParticleSystemSimulationSpace.Local ? p.position : ps.transform.InverseTransformPoint(p.position));float scale = canvas.gameObject.GetComponent<RectTransform>().localScale.x;position /= scale;size /= scale;_quad[0] = UIVertex.simpleVert;_quad[0].color = curCol;_quad[0].uv0 = new Vector2(0, 0);_quad[1] = UIVertex.simpleVert;_quad[1].color = curCol;_quad[1].uv0 = new Vector2(0, 1);_quad[2] = UIVertex.simpleVert;_quad[2].color = curCol;_quad[2].uv0 = new Vector2(1, 1);_quad[3] = UIVertex.simpleVert;_quad[3].color = curCol;_quad[3].uv0 = new Vector2(1, 0);if (rotation == 0){Vector4 posOffset = new Vector4();posOffset.x = position.x - size;posOffset.y = position.y - size;posOffset.z = position.x + size;posOffset.w = position.y + size;_quad[0].position = new Vector3(posOffset.x,posOffset.y);_quad[1].position = new Vector3(posOffset.x, posOffset.w);_quad[2].position = new Vector3(posOffset.z, posOffset.w);_quad[3].position = new Vector3(posOffset.z, posOffset.y);}else{Vector2 right = new Vector2(Mathf.Cos(rotation), Mathf.Sin(rotation)) * size;Vector2 up = new Vector2(Mathf.Cos(rotation90), Mathf.Sin(rotation90)) * size;_quad[0].position = position - right - up;_quad[1].position = position - right + up;_quad[2].position = position + right + up;_quad[3].position = position + right - up;}
}

2. 考虑序列帧动画

  粒子系统本身自带播放序列帧的功能的,比如我做了这么一张序列帧图:
在这里插入图片描述

  然后在Texture Sheet Animation里面指定一下横竖列的格子数量,按道理粒子系统就会从1到16那样按顺序播放序列帧动画:
在这里插入图片描述

在代码里面,要这样模拟:

    private void UpdateQuad(ParticleSystem ps, Particle p){Color curCol = p.GetCurrentColor(ps);float size = p.GetCurrentSize(ps) * 0.5f;float rotation = -p.rotation * Mathf.Deg2Rad;float rotation90 = rotation + Mathf.PI / 2;Vector2 position = (mainModule.simulationSpace == ParticleSystemSimulationSpace.Local ? p.position : ps.transform.InverseTransformPoint(p.position));float scale = canvas.gameObject.GetComponent<RectTransform>().localScale.x;position /= scale;size /= scale;Vector4 particleUV = imageUV;//计算序列帧动画每一个粒子的uvif(ps.textureSheetAnimation.enabled == true){TextureSheetAnimationModule textureSheetAnimation = ps.textureSheetAnimation;float frameProgress = 1 - (p.remainingLifetime / p.startLifetime);if (textureSheetAnimation.frameOverTime.curveMin != null){frameProgress = textureSheetAnimation.frameOverTime.curveMin.Evaluate(1 - (p.remainingLifetime / p.startLifetime));}else if (textureSheetAnimation.frameOverTime.curve != null){frameProgress = textureSheetAnimation.frameOverTime.curve.Evaluate(1 - (p.remainingLifetime / p.startLifetime));}else if (textureSheetAnimation.frameOverTime.constant > 0){frameProgress = textureSheetAnimation.frameOverTime.constant - (p.remainingLifetime / p.startLifetime);}frameProgress = Mathf.Repeat(frameProgress * textureSheetAnimation.cycleCount, 1);int frame = 0;int textureSheetAnimationFrames = textureSheetAnimation.numTilesX * textureSheetAnimation.numTilesY;Vector2 textureSheetAnimationFrameSize = new Vector2(1f / textureSheetAnimation.numTilesX, 1f / textureSheetAnimation.numTilesY);switch (textureSheetAnimation.animation){case ParticleSystemAnimationType.WholeSheet:frame = Mathf.FloorToInt(frameProgress * textureSheetAnimationFrames);break;case ParticleSystemAnimationType.SingleRow:frame = Mathf.FloorToInt(frameProgress * textureSheetAnimation.numTilesX);int row = textureSheetAnimation.rowIndex;//                    if (textureSheetAnimation.useRandomRow) { // FIXME - is this handled internally by rowIndex?//                        row = Random.Range(0, textureSheetAnimation.numTilesY, using: particle.randomSeed);//                    }frame += row * textureSheetAnimation.numTilesX;break;}frame %= textureSheetAnimationFrames;particleUV.x = (frame % textureSheetAnimation.numTilesX) * textureSheetAnimationFrameSize.x;particleUV.y = 1-Mathf.FloorToInt(frame / textureSheetAnimation.numTilesX) * textureSheetAnimationFrameSize.y- textureSheetAnimationFrameSize.y;particleUV.z = particleUV.x + textureSheetAnimationFrameSize.x;particleUV.w = particleUV.y + textureSheetAnimationFrameSize.y;}_quad[0] = UIVertex.simpleVert;_quad[0].color = curCol;_quad[0].uv0 = new Vector2(particleUV.x, particleUV.y);_quad[1] = UIVertex.simpleVert;_quad[1].color = curCol;_quad[1].uv0 = new Vector2(particleUV.x, particleUV.w);_quad[2] = UIVertex.simpleVert;_quad[2].color = curCol;_quad[2].uv0 = new Vector2(particleUV.z, particleUV.w);_quad[3] = UIVertex.simpleVert;_quad[3].color = curCol;_quad[3].uv0 = new Vector2(particleUV.z, particleUV.y);if (rotation == 0){Vector4 posOffset = new Vector4();posOffset.x = position.x - size;posOffset.y = position.y - size;posOffset.z = position.x + size;posOffset.w = position.y + size;_quad[0].position = new Vector3(posOffset.x,posOffset.y);_quad[1].position = new Vector3(posOffset.x, posOffset.w);_quad[2].position = new Vector3(posOffset.z, posOffset.w);_quad[3].position = new Vector3(posOffset.z, posOffset.y);}else{Vector2 right = new Vector2(Mathf.Cos(rotation), Mathf.Sin(rotation)) * size;Vector2 up = new Vector2(Mathf.Cos(rotation90), Mathf.Sin(rotation90)) * size;_quad[0].position = position - right - up;_quad[1].position = position - right + up;_quad[2].position = position + right + up;_quad[3].position = position + right - up;}}

同样是修改UpdateQuad方法。这次要修改的是设置顶点的UV0的过程。每一个粒子,都可以通过获得它的生命周期,然后计算应该播放到序列图的第几帧的图片,然后根据这个第几帧,再计算出当前的例子的UV坐标应该是什么。
这里有个规则的问题,uv坐标从左下角开始是(0,0),右上角是(1,1),但我的序列图四左上角开始是1,右下角是16,所以在计算y坐标的时候,我做了一个反转的操作:
particleUV.y = 1-Mathf.FloorToInt(frame / textureSheetAnimation.numTilesX) * textureSheetAnimationFrameSize.y- textureSheetAnimationFrameSize.y;
这时候播放,会看到序列帧的图片正常的播放了:
在这里插入图片描述

同样是可以任意重叠和裁剪的。

完整:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static UnityEngine.ParticleSystem;[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
public class UIVertexTest : MaskableGraphic
{private UIVertex[] _quad = new UIVertex[4];public Material curMat;public ParticleSystem particleSys;private ParticleSystemRenderer particleSysRender;private ParticleSystem.MainModule mainModule;private Particle[] particles;private Vector4 imageUV = new Vector4(0, 0, 1, 1);// Start is called before the first frame updateprivate new IEnumerator Start(){if(particleSys == null){particleSys = gameObject.GetComponent<ParticleSystem>();if(particleSys!=null){particleSysRender = particleSys.GetComponent<ParticleSystemRenderer>();particleSysRender.enabled = false;mainModule = particleSys.main;}}yield return null;}private void UpdateQuad(ParticleSystem ps, Particle p){Color curCol = p.GetCurrentColor(ps);float size = p.GetCurrentSize(ps) * 0.5f;float rotation = -p.rotation * Mathf.Deg2Rad;float rotation90 = rotation + Mathf.PI / 2;Vector2 position = (mainModule.simulationSpace == ParticleSystemSimulationSpace.Local ? p.position : ps.transform.InverseTransformPoint(p.position));float scale = canvas.gameObject.GetComponent<RectTransform>().localScale.x;position /= scale;size /= scale;Vector4 particleUV = imageUV;//计算序列帧动画每一个粒子的uvif(ps.textureSheetAnimation.enabled == true){TextureSheetAnimationModule textureSheetAnimation = ps.textureSheetAnimation;float frameProgress = 1 - (p.remainingLifetime / p.startLifetime);if (textureSheetAnimation.frameOverTime.curveMin != null){frameProgress = textureSheetAnimation.frameOverTime.curveMin.Evaluate(1 - (p.remainingLifetime / p.startLifetime));}else if (textureSheetAnimation.frameOverTime.curve != null){frameProgress = textureSheetAnimation.frameOverTime.curve.Evaluate(1 - (p.remainingLifetime / p.startLifetime));}else if (textureSheetAnimation.frameOverTime.constant > 0){frameProgress = textureSheetAnimation.frameOverTime.constant - (p.remainingLifetime / p.startLifetime);}frameProgress = Mathf.Repeat(frameProgress * textureSheetAnimation.cycleCount, 1);int frame = 0;int textureSheetAnimationFrames = textureSheetAnimation.numTilesX * textureSheetAnimation.numTilesY;Vector2 textureSheetAnimationFrameSize = new Vector2(1f / textureSheetAnimation.numTilesX, 1f / textureSheetAnimation.numTilesY);switch (textureSheetAnimation.animation){case ParticleSystemAnimationType.WholeSheet:frame = Mathf.FloorToInt(frameProgress * textureSheetAnimationFrames);break;case ParticleSystemAnimationType.SingleRow:frame = Mathf.FloorToInt(frameProgress * textureSheetAnimation.numTilesX);int row = textureSheetAnimation.rowIndex;//                    if (textureSheetAnimation.useRandomRow) { // FIXME - is this handled internally by rowIndex?//                        row = Random.Range(0, textureSheetAnimation.numTilesY, using: particle.randomSeed);//                    }frame += row * textureSheetAnimation.numTilesX;break;}frame %= textureSheetAnimationFrames;particleUV.x = (frame % textureSheetAnimation.numTilesX) * textureSheetAnimationFrameSize.x;particleUV.y = 1-Mathf.FloorToInt(frame / textureSheetAnimation.numTilesX) * textureSheetAnimationFrameSize.y- textureSheetAnimationFrameSize.y;particleUV.z = particleUV.x + textureSheetAnimationFrameSize.x;particleUV.w = particleUV.y + textureSheetAnimationFrameSize.y;}_quad[0] = UIVertex.simpleVert;_quad[0].color = curCol;_quad[0].uv0 = new Vector2(particleUV.x, particleUV.y);_quad[1] = UIVertex.simpleVert;_quad[1].color = curCol;_quad[1].uv0 = new Vector2(particleUV.x, particleUV.w);_quad[2] = UIVertex.simpleVert;_quad[2].color = curCol;_quad[2].uv0 = new Vector2(particleUV.z, particleUV.w);_quad[3] = UIVertex.simpleVert;_quad[3].color = curCol;_quad[3].uv0 = new Vector2(particleUV.z, particleUV.y);if (rotation == 0){Vector4 posOffset = new Vector4();posOffset.x = position.x - size;posOffset.y = position.y - size;posOffset.z = position.x + size;posOffset.w = position.y + size;_quad[0].position = new Vector3(posOffset.x,posOffset.y);_quad[1].position = new Vector3(posOffset.x, posOffset.w);_quad[2].position = new Vector3(posOffset.z, posOffset.w);_quad[3].position = new Vector3(posOffset.z, posOffset.y);}else{Vector2 right = new Vector2(Mathf.Cos(rotation), Mathf.Sin(rotation)) * size;Vector2 up = new Vector2(Mathf.Cos(rotation90), Mathf.Sin(rotation90)) * size;_quad[0].position = position - right - up;_quad[1].position = position - right + up;_quad[2].position = position + right + up;_quad[3].position = position + right - up;}}// Update is called once per framevoid Update(){CheckMatChange();
CheckParticle();}private void CheckMatChange(){if(material!=curMat){material = curMat;}}private void CheckParticle(){if (particleSys != null){SetAllDirty();}}protected override void OnPopulateMesh(VertexHelper vh){      vh.Clear();if (particleSys == null){return;}if(particles == null){particles = new Particle[500];}int count = particleSys.GetParticles(particles);if(count == 0){return;}for(int i = 0;i<count;i++){Particle p = particles[i];UpdateQuad(particleSys, p);vh.AddUIVertexQuad(_quad);}}
}

五、存在问题

  这个方案也不是完美无缺的,暂时来说,我发现一些问题,比如:
1、一般特效里面除了粒子系统,还有拖尾等效果,我这个例子里面没有对拖尾进行计算,其实方法相同,也是获得它当前的拖尾的网格,然后绘制就行
2、粒子系统里面存在很多不同的参数设置,我上面的计算不一定很完整,可能会漏一些东西,需要发现的时候再补充
3、MaskableGraphic里面只设置一个材质球,这个材质球必须是用UI类型的Shader才能正常显示,所以粒子自带的shader不能直接使用,要经过改造。所谓的UI类型shader,比如是这样的:

Shader "UIVertexTex"
{Properties{[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}_Color ("Tint", Color) = (1,1,1,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_TextureSample0("Texture Sample 0", 2D) = "white" {}[HideInInspector] _texcoord( "", 2D ) = "white" {}}SubShader{LOD 0Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" }Stencil{Ref [_Stencil]ReadMask [_StencilReadMask]WriteMask [_StencilWriteMask]CompFront [_StencilComp]PassFront [_StencilOp]FailFront KeepZFailFront KeepCompBack AlwaysPassBack KeepFailBack KeepZFailBack Keep}Cull OffLighting OffZWrite OffZTest [unity_GUIZTestMode]Blend SrcAlpha OneMinusSrcAlphaColorMask [_ColorMask]Pass{Name "Default"CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma target 3.0#include "UnityCG.cginc"#include "UnityUI.cginc"#pragma multi_compile __ UNITY_UI_CLIP_RECT#pragma multi_compile __ UNITY_UI_ALPHACLIPstruct 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;half2 texcoord  : TEXCOORD0;float4 worldPosition : TEXCOORD1;UNITY_VERTEX_INPUT_INSTANCE_IDUNITY_VERTEX_OUTPUT_STEREO};uniform fixed4 _Color;uniform fixed4 _TextureSampleAdd;uniform float4 _ClipRect;uniform sampler2D _MainTex;uniform sampler2D _TextureSample0;uniform float4 _TextureSample0_ST;SamplerState sampler_TextureSample0;v2f vert( appdata_t IN  ){v2f OUT;UNITY_SETUP_INSTANCE_ID( IN );UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);UNITY_TRANSFER_INSTANCE_ID(IN, OUT);OUT.worldPosition = IN.vertex;OUT.worldPosition.xyz +=  float3( 0, 0, 0 ) ;OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);OUT.texcoord = IN.texcoord;OUT.color = IN.color * _Color;return OUT;}fixed4 frag(v2f IN  ) : SV_Target{float2 uv_TextureSample0 = IN.texcoord.xy * _TextureSample0_ST.xy + _TextureSample0_ST.zw;float4 tex2DNode1 = tex2D( _TextureSample0, uv_TextureSample0 );clip( 0.0 );float4 appendResult3 = (float4(( tex2DNode1 + tex2DNode1 ).rgb , tex2DNode1.a));half4 color = appendResult3;#ifdef UNITY_UI_CLIP_RECTcolor.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);#endif#ifdef UNITY_UI_ALPHACLIPclip (color.a - 0.001);#endifreturn color;}ENDCG}}

它要包含UI的蒙版设置,包含UnityUI.cginc,并且做了Rect和Alpha的裁剪

  所以并没有像想象中那么简单,美术特效随便做一个粒子特效,然后挂个脚本,就能随便用在UI上面,最起码,要对Shader进行一定的改造,才能在UI上面正常显示的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/349550.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

r语言数据分析案例25-基于向量自回归模型的标准普尔 500 指数长期预测与机制分析

一、背景介绍 2007 年的全球经济危机深刻改变了世界经济格局&#xff0c;引发了一系列连锁反应&#xff0c;波及各大洲。经济增长停滞不前&#xff0c;甚至在某些情况下出现负增长&#xff0c;给出口导向型发展中国家带来了不确定性。实体经济受到的冲击尤为严重&#xff0c;生…

[qt] qt程序打包以及docker镜像打包

目录 一 环境准备: 1.1 qt环境 1.2 linuxdeplouqt打包工具 二 qt包发布: 2.1 搜索链接库 2.2 应用程序APP打包 2.3 发布 三 docker镜像包发布 3.1 环境准备 3.2 镜像生产脚本 3.3 加载镜像并运行docker容器 四 补充 4.1 时间不同步问题解决 一 环境准备: qt环境l…

2024-06-07 Unity 编辑器开发之编辑器拓展8 —— Scene 窗口拓展

文章目录 1 Handles 类1.1 Scene 响应函数1.2 自定义窗口中监听 Scene1.3 Handles 常用 API2.2.1 颜色控制2.2.2 文本2.2.3 线段2.2.4 虚线2.2.5 圆弧2.2.6 圆2.2.7 立方体2.2.8 几何体2.2.9 移动、旋转、缩放2.2.10 自由移动 / 旋转 2 Scene 窗口中显示 GUI3 HandleUtility4 G…

用Python代码锁定Excel单元格以及行和列

Excel能够帮助用户高效地组织数据&#xff0c;还支持复杂的公式计算和数据分析。而随着团队协作的日益频繁&#xff0c;保护数据的准确性和完整性变得尤为重要。在Excel表格中&#xff0c;我们可以通过锁定特定的单元格或区域&#xff0c;防止对单元格内容进行随意修改&#xf…

C++面向对象程序设计 - 命名空间

命名空间是ANSI C引入的可以由用户命名的作用域&#xff0c;用来处理程序中常见的同名冲突。 在C语言中定义了三个层次的作用域&#xff0c;即文件&#xff08;编译单元&#xff09;、函数和复合语句。C又引入了类作用域&#xff0c;类是出现在文件内的。在不同的作用域中可以定…

14.shell awk数组

awk数组 awk数组awk数组示例Nginx日志分析 awk数组 1.什么是awk数组 数组其实也算是变量,传统的变量只能存储一个值,但数组可以存储多个值 2.awk数组应用场景 通常用来统计、比如:统计网站访问TOP10、网站url访问TOP10等等 3.awk数组统计技巧 1.在awk中,使用数组时,不仅可以…

ceisum只聚合效果展示

忙于开发三维引擎的扩展功能&#xff0c;实在时间太少了&#xff0c;仓促截几张图&#xff0c;看一下聚合效果。 1.聚合又文字标签 四种效果&#xff1a;如下 2.聚合无文字标签

Docker(一)-认识Docker

1.docker理念 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build,Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装&#xff0c;分发&#xff0c;部署&#xff0c;运行等生命周期的管理&#xff0c;使用户的应用及其运行环境能够做到”…

jenkins使用注意问题

1.在编写流水线时并不知道当前处在哪个目录&#xff0c;导致名使用不当&#xff0c;以及文件位置不清楚 流水线任务默认路径是&#xff0c;test4_mvn为jenkins任务名 [Pipeline] sh (hide)pwd /var/jenkins_home/workspace/test4_mvn maven任务也是&#xff0c;看来是一样的…

CV每日论文--2024.6.14

1、ICE-G: Image Conditional Editing of 3D Gaussian Splats 中文标题&#xff1a;ICE-G&#xff1a;3D 高斯斑点的图像条件编辑 简介&#xff1a;近年来,出现了许多技术来创建高质量的3D资产和场景。然而,当涉及到这些3D对象的编辑时,现有方法要么速度慢、要么牺牲质量,要么…

数组(C语言)(详细过程!!!)

目录 数组的概念 一维数组 sizeof计算数组元素个数 二维数组 C99中的变⻓数组 数组的概念 数组是⼀组相同类型元素的集合。 数组分为⼀维数组和多维数组&#xff0c;多维数组⼀般比较多见的是二维数组。 从这个概念中我们就可以发现2个有价值的信息&#xff1a;(1)数…

flask_sqlalchemy时间缓存导致datetime.now()时间不变问题

问题是这样的&#xff0c;项目在本地没什么问题&#xff0c;但是部署到服务器过一阵子发现&#xff0c;这个时间会在某一刻定死不变。 重启uwsgi后&#xff0c;发现第一条数据更新到了目前最新时间&#xff0c;过了一会儿再次发送也变了时间&#xff0c;但是再过几分钟再发就会…

软件测试--Mysql快速入门

文章目录 软件测试-mysql快速入门sql主要划分mysql常用的数据类型sql基本操作常用字段的约束&#xff1a;连接查询mysql内置函数存储过程视图事务索引 软件测试-mysql快速入门 sql主要划分 sql语言主要分为&#xff1a; DQL&#xff1a;数据查询语言&#xff0c;用于对数据进…

基于Verilog表达的FSM状态机

基于Verilog表达的FSM状态机 1 FSM1.1 Intro1.2 Why FSM?1.3 How to do 在这里聚焦基于Verilog的三段式状态机编程&#xff1b; 1 FSM 1.1 Intro 状态机是一种代码实现功能的范式&#xff1b;一切皆可状态机&#xff1b; 状态机编程四要素&#xff1a;– 1.状态State&#…

通用大模型与垂直大模型:双轨并进的人工智能未来

在人工智能(AI)的浩瀚宇宙中&#xff0c;大模型以其强大的学习能力和广泛的适用性&#xff0c;正逐步成为推动技术进步和产业革新的核心动力。在这股浪潮中&#xff0c;通用大模型与垂直大模型如同两颗璀璨的星辰&#xff0c;各自散发着独特的光芒&#xff0c;共同照亮了AI发展…

STL入门指南:从容器到算法的完美结合

目录 ​编辑 一、什么是STL 二、STL的版本 三、STL的六大组件 1. 容器&#xff08;Containers&#xff09;&#xff1a; 2. 算法&#xff08;Algorithms&#xff09;&#xff1a; 3. 迭代器&#xff08;Iterators&#xff09;&#xff1a; 4. 仿函数&#xff08;Functo…

中国算力基础设施“第一阵营”变局?

2024年6月IDC最新数据显示&#xff0c;2024年第一季度&#xff0c;联想服务器跃升至中国市场份额第三位。中国算力基础设施“第一阵营”正生变局。 在去年服务器本地化品牌联想问天发布之后&#xff0c;联想就发出了向国内服务器市场冲锋的信号。如今仅一年&#xff0c;就进入…

工业4.0下的PLC进化论:ARMxy计算机如何重塑自动化

智能物流系统的高效与精准成为企业竞争力的关键。在这个背景下&#xff0c;传统的PLC系统因其固有的局限性&#xff0c;如扩展性差、系统封闭等&#xff0c;开始显得力不从心。ARMxy工业计算机作为新一代的PLC替代方案&#xff0c;凭借其低功耗、高性能以及高度的灵活性&#x…

Android Studio历史版本

android studio的历史版本

自然语言处理领域的重大挑战:解码器 Transformer 的局限性

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…