Unity粒子特效裁剪(基于NGUI的shader裁剪实现)
这几天总结一下之前遇到的有意思的问题,之前遇到的时候没时间总结,现在就总结一下。
首先说一下关于特效的裁剪应用的场景主要是scrollview中,当scrollview中的物体超出UIPannel的范围NGUI会对超出的部分的UI进行裁切,其原理是将UIPannel的裁剪区域传递给其自带裁剪shader对超出区域的像素透明度设为完全透明,但是并不会作用于粒子特效,所以会出现拖拽UI控件至超出边界的时候UI被裁减但是特效依然存在。具体大概是这样:
(新的电脑没有装NGUI,就先用UGUI搭一下。。。)可以看到当UI超出边界的时候挂在UI下的粒子特效并没有是随着UI裁剪。
那么就说一下解决思路把,首先在脚本中拿到当前的Pannel的裁剪区域,通过裁剪区域算出上下左右四个边界值,再乘以分辨率的缩放传递给shader处理。在shader中的处理就很简单了,顶点着色器就是变换顶点至世界坐标,在片元处理器中比较顶点位置是否在裁剪区域,如果不再就不渲染。
c#代码如下 :
using System;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(UIPanel))]
public class ParticleSystemClipper : MonoBehaviour
{
UIPanel m_targetPanel=null;
private UIRoot _uiRoot=null;
bool inited = false;
Vector4 _clipArea;
void Init()
{
if(inited)
{
return;
}
m_targetPanel = GetComponent<UIPanel>();
if (m_targetPanel == null)
throw new ArgumentNullException("Cann't find the right UIPanel");
if (m_targetPanel.clipping != UIDrawCall.Clipping.SoftClip)
throw new InvalidOperationException("Don't need to clip");
_uiRoot = NGUITools.FindInParents<UIRoot>(gameObject);
if (_uiRoot == null)
_uiRoot = DlgMgr.Instance.uiRoot;
}
Vector4 CalcClipArea()
{
var clipRegion = m_targetPanel.finalClipRegion;
Vector4 nguiArea = new Vector4()
{
x = clipRegion.x - clipRegion.z / 2,
y = clipRegion.y - clipRegion.w / 2,
z = clipRegion.x + clipRegion.z / 2,
w = clipRegion.y + clipRegion.w / 2
};
Vector3 worldClipXY = m_targetPanel.transform.TransformPoint(nguiArea.x, nguiArea.y, 0);
Vector3 worldClipZW = m_targetPanel.transform.TransformPoint(nguiArea.z, nguiArea.w, 0);
return new Vector4()
{
x = worldClipXY.x ,
y = worldClipXY.y ,
z = worldClipZW.x ,
w = worldClipZW.y
};
}
ParticleSystem[] _particleAry = null;
public void ExecuteClip(bool reCaulClipRegion=false)
{
Init();
_particleAry = this.GetComponentsInChildren<ParticleSystem>();
if (_particleAry == null || _particleAry.Length == 0)
{
return;
}
if (!inited)
{
//控制粒子的缩放
float designWidth = 1280;//开发时分辨率宽
float designHeight = 720;//开发时分辨率高
float designScale = designWidth / designHeight;
float scaleRate = (float)Screen.width / (float)Screen.height;
float scaleFactor = scaleRate / designScale;
for (int i = 0; i < _particleAry.Length; i++)
{
ParticleSystem ps = _particleAry[i];
ps.transform.localScale *= scaleFactor;
}
}
if (!inited || reCaulClipRegion)//在初始化或者需要时刻更新的的时候计算裁剪区
{
_clipArea = CalcClipArea();
}
inited = true;
for (int i = 0; i < _particleAry.Length; i++)
{
ParticleSystem ps = _particleAry[i];
Material mat = ps.GetComponent<Renderer>().material;
if (!mat.shader.name.Contains("Clip"))
{
string name = mat.shader.name + "Clip";
mat.shader = ShaderMgr.Instance.Find(name);
}
mat.SetVector("_Clip", _clipArea);
}
}
}
shader代码如下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Miyoo/Particles/AdditiveClip" {
Properties {
_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex ("Particle Texture", 2D) = "white" {}
_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
_Clip ("Clip", Vector) = (0,0,1,1)
//Curved World
[CurvedWorldLabel] V_CW_Label_UnityDefaults("Curved World Optionals", float) = 0
[Toggle] V_CW_PARTICLE_SYSTEM ("Use With Particle System", Float) = 0
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "Reflection" = "RenderReflectionTransparentBlend" "CurvedWorldTag"="Particles/Additive" "CurvedWorldNoneRemoveableKeywords"="" "CurvedWorldAvailableOptions"=" " "PreviewType"="Plane"}
Blend SrcAlpha One
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_particles
#include "UnityCG.cginc"
#pragma multi_compile V_CW_PARTICLE_SYSTEM_OFF V_CW_PARTICLE_SYSTEM_ON
#include "../../VacuumShaders/Curved World/Shaders/cginc/CurvedWorld_Base.cginc"
sampler2D _MainTex;
fixed4 _TintColor;
fixed4 _Clip;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
half2 worldPos : TEXCOORD1;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD2;
#endif
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o);
V_CW_TransformPoint(v.vertex);
o.vertex = UnityObjectToClipPos(v.vertex);
#ifdef SOFTPARTICLES_ON
o.projPos = ComputeScreenPos (o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
#endif
o.color = v.color;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
return o;
}
sampler2D_float _CameraDepthTexture;
float _InvFade;
fixed4 frag (v2f i) : SV_Target
{
bool inArea = i.worldPos.x >= _Clip.x && i.worldPos.x <= _Clip.z && i.worldPos.y >= _Clip.y && i.worldPos.y <= _Clip.w;
if(!inArea)
{
return fixed4(0,0,0,0);
}
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif
fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
return col;
}
ENDCG
}
}
}
}