[GEngine]相机角色中间物体透明(BetweenTransparent)

/**********************************************************
* 两者中间对象透明组件,一般来说挂在主角上,监视者为主
* 摄像机。剧情动画时可挂在动画主角上。组件特性:反向检
* 测、透明shader替换(shader替换暂时做统一替换,如果需
* 要,可以添加组件,然后让对象自定义替换shader)、缓存
* 池、频率优化、GC优化等
* *********************************************************/

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

namespace GEngine
{
	public class BetweenTransparent : MonoBehaviour 
	{		
		/// <summary>
		/// 透明对象材质信息
		/// </summary>
		public class MatInfo
		{
			/// <summary>
			/// 材质
			/// </summary>
			public Material mat;
			/// <summary>
			/// 材质原始shader
			/// </summary>
			public Shader oldShader;
			/// <summary>
			/// 是否已还原shader,优化shader替换性能损失
			/// </summary>
			public bool revertedShader = true;
			/// <summary>
			/// 材质原始颜色
			/// </summary>
			public Color oldColor;
			/// <summary>
			/// 材质当前颜色,加速读取材质颜色和设置shader颜色
			/// </summary>
			public Color currentColor;
			
			/// <summary>
			/// 重置信息
			/// </summary>
			public void ReSet()
			{
				mat = null;
				oldShader = null;
				revertedShader = true;
				oldColor = Color.clear;
				currentColor = Color.clear;
			}
		}
		
		/// <summary>
		/// 透明信息
		/// </summary>
		public class Transparent
		{
			/// <summary>
			/// 渲染组件
			/// </summary>
			public Renderer render;
			/// <summary>
			/// 是否要透明
			/// </summary>
			public bool shouldTransparent;
			/// <summary>
			/// 材质信息
			/// </summary>
			public List<MatInfo> matInfoList = new List<MatInfo>();
		}

		/// <summary>
		/// 监视者,通常是主摄像机
		/// </summary>
		public Transform monitor;

		/// <summary>
		/// 处理频率,多少帧处理一次,调节可优化性能, <= 1时为每帧处理一次
		/// </summary>
		public int frequency = 5;

		/// <summary>
		/// 透明对象的最小透明度
		/// </summary>
		public float minAlpha = 0;

		/// <summary>
		/// 透明动画速度
		/// </summary>
		public float speed = 1f;

		/// <summary>
		/// 射线碰撞检测层,一般来说是非地面的场景元素,如树、房子等
		/// </summary>
		public LayerMask layermask = 1;

		/// <summary>
		/// 待替换的透明Shader
		/// </summary>
		public string replaceShader;

		/// <summary>
		/// 反向检测,主要用在相机背对主角等情况
		/// </summary>
		public bool reverseCheck = true;

		/// <summary>
		/// 最大缓存个数
		/// </summary>
		public int maxPoolCount = 10;

	#if UNITY_EDITOR
		public bool rayDebug = false;
		public Color rayColor = Color.red;
	#endif

		/// <summary>
		/// 透明处理列表
		/// </summary>
		List<Transparent> transparentList =new List<Transparent>();

		/// <summary>
		/// MatInfo缓存池,值为是否被使用
		/// </summary>
		Dictionary<MatInfo, bool> matInfoPool =	new Dictionary<MatInfo, bool>();

		#region  //帧循环处理,减少GC
		Transform trans;
		Ray ray = new Ray();
		Vector3 direction;
		float distance;

		RaycastHit[] hits;
		RaycastHit hit;
		Transparent tp;
		Renderer render;

		Shader  tranparentShader;

		int frameCount;
		#endregion //帧循环处理,减少GC

		void Awake()
		{
			if(monitor == null)
			{
				Camera mainCamera = Camera.main;
				if(mainCamera != null)
					monitor = mainCamera.transform;
			}

			trans = transform;

			if(!string.IsNullOrEmpty(replaceShader))
			{
				tranparentShader = Shader.Find(replaceShader);
			}
		}

		//避开主逻辑循环Update,防止逻辑中设置材质和颜色
		void LateUpdate()
		{
			if(monitor == null)
				return;

#if UNITY_EDITOR
			if(rayDebug)
			{
				Debug.DrawLine(trans.position, monitor.position, rayColor);
			}
#endif

			//小于等于1则每帧检测
			if(frequency  <= 1)
			{
				ProcessTransarent();
			}
			else
			{
				frameCount++;
				if(frameCount > frequency)
				{
					frameCount = 0;
					ProcessTransarent();
				}
				//不需要执行碰撞检测时,仍需要更新透明度
				else
				{
					UpdateAlpha();
				}
			}
		}

		/// <summary>
		/// 处理透明对象检测
		/// </summary>
		void ProcessTransarent()
		{
			//先把透明列表中的都设置为不透明,待检测完毕后再设置是否要透明
			for(int i = 0; i < transparentList.Count; )
			{
				tp = transparentList[i];
				//如果render不存在,可能逻辑上已经移除了则剔除
				if(tp.render == null)
				{
					for(int k = 0; k < tp.matInfoList.Count; k++)
					{
						SetFreeMatInfo(tp.matInfoList[k], false);
					}
					transparentList.RemoveAt(i);
				}
				else
				{
					transparentList[i].shouldTransparent = false;
					i++;
				}
			}

			//反向检测,从监视者角度检测
			if(reverseCheck)
			{				
				direction = trans.position - monitor.position;
				//监视者到主角的向量点乘监视者正方向,如果值小于0则为背对主角
				//实际面向需要根据项目情况制定方案,一般来说规则是模型正面朝正方向
				if(Vector3.Dot(direction, monitor.forward) < 0)
				{
					//反向情况下还原透明度
					UpdateAlpha();
					return;
				}
			}

			//主角(挂载该脚本的对象)与监视者间的对象检测,从主角向监视者方向检测
			//射线长度为到监视者位置(准确的话如果是摄像机应为前裁切面,一般对象为碰撞或模型外表面)
			ray.origin = trans.position;
			direction = monitor.position - trans.position;
			distance = direction.magnitude;
			ray.direction =direction.normalized;
			//这里可以做碰撞Layer筛选
			hits = Physics.RaycastAll(ray, distance, layermask.value);

			for(int i = 0; i < hits.Length; i++)
			{
				hit = hits[i];
				render = hit.collider.renderer;
				if(render != null)
				{
					//如果还在透明列表中的,则标记为透明,不做其他处理
					tp = GetTransparent(render);
					if(tp != null)
					{
						tp.shouldTransparent = true;
						continue;
					}

					//没有材质的也不做处理,这里不能用共享材质,否则可能导致记录透明度错误和误修改,以及Shader不能还原等
					Material[] mats = render.materials;
					if(mats.Length < 0)
						continue;

					//新添加一个透明对象
					tp = new Transparent();
					for(int j = 0; j < mats.Length; j++)
					{
						Material mat = mats[j];
						if(mat == null)
							continue;

						//记录下原始透明度,以备还原
						MatInfo matInfo = null;
						GetFreeMatInfo(ref matInfo);
						matInfo.mat = mat;
						matInfo.oldColor = mat.color;
						matInfo.currentColor = matInfo.oldColor;

						//支持Shader替换并且有Shader时才替换
						//默认是否恢复Shader为true,只有被替换才设置为false
						if(!string.IsNullOrEmpty(replaceShader) && tranparentShader != null)
						{
							matInfo.oldShader = mat.shader;
							matInfo.revertedShader = false;
							mat.shader = tranparentShader;
						}

						tp.matInfoList.Add(matInfo);
					}

					//材质全为空也不用添加了
					if(tp.matInfoList.Count > 0)
					{
						for(int k = 0; k < tp.matInfoList.Count; k++)
						{
							SetFreeMatInfo(tp.matInfoList[k], true);
						}
						tp.render = render;
						tp.shouldTransparent = true;
						transparentList.Add(tp);
					}
				}
			}

			UpdateAlpha();
		}

		/// <summary>
		/// 更新Alpha
		/// </summary>
		void UpdateAlpha()
		{
			//更新颜色
			for(int i = 0; i < transparentList.Count;)
			{
				tp = transparentList[i];
				for(int j = 0; j < tp.matInfoList.Count; j++)
				{
					MatInfo matInfo = tp.matInfoList[j];
					if(tp.shouldTransparent)
					{
						//透明度还需要增加
						if(matInfo.currentColor.a > minAlpha)
						{
							matInfo.currentColor.a = Mathf.Max(minAlpha, matInfo.currentColor.a - Time.deltaTime * speed);
							matInfo.mat.SetColor("_Color", matInfo.currentColor);
						}
					}
					else
					{
						//还未还原到原始透明度
						if(matInfo.currentColor.a < matInfo.oldColor.a)
						{
							matInfo.currentColor.a = Mathf.Min(matInfo.oldColor.a, matInfo.currentColor.a + Time.deltaTime * speed);
							matInfo.mat.SetColor("_Color", matInfo.currentColor);
						}

						//如果shader被替换,恢复后需要还原
						if(matInfo.currentColor.a >= matInfo.oldColor.a && !matInfo.revertedShader)
						{
							matInfo.revertedShader = true;
							matInfo.mat.shader = matInfo.oldShader;
						}
					}
				}

				if(!tp.shouldTransparent )
				{				
					//不需要透明对象材质全还原后从透明队列移除
					bool revert = true;
					for(int j = 0; j < tp.matInfoList.Count; j++)
					{
						MatInfo matInfo = tp.matInfoList[j];
						if(matInfo.currentColor.a < matInfo.oldColor.a)
						{
							revert = false;
							break;
						}
					}

					if(revert)
					{
						for(int k = 0; k < tp.matInfoList.Count; k++)
						{
							SetFreeMatInfo(tp.matInfoList[k], false);
						}
						transparentList.RemoveAt(i);
					}
					else
					{
						i++;
					}
				}
				else
				{
					i++;
				}
			}
		}

		//检测是否已在透明列表
		Transparent GetTransparent(Renderer render)
		{
			for(int i = 0; i < transparentList.Count; i++)
			{
				Transparent tp = transparentList[i];
				if(tp.render == render)
					return tp;
			}

			return null;
		}

		/// <summary>
		/// 获取缓存材质信息
		/// </summary>
		/// <returns><c>true</c>, 如果找到闲置缓存则赋值,并返回, <c>false</c> 否则重新New对象,如果缓存未满则同时加入缓存,并返回</returns>
		/// <param name="matInfo">待输出引用</param>
		bool GetFreeMatInfo(ref MatInfo matInfo)			//需要ref 否则new对象结束生命周期后被释放,引用对象为null,或采用引用对象返回模式
		{
			foreach(MatInfo info in matInfoPool.Keys)
			{
				if(!matInfoPool[info])
				{
					matInfo = info;
					matInfo.ReSet();
					return true;
				}
			}
			
			matInfo = new MatInfo();
			if(matInfoPool.Count < maxPoolCount)
				matInfoPool.Add(matInfo, false);
			
			return false;
		}
		
		/// <summary>
		/// 设置材质缓存信息状态
		/// </summary>
		/// <param name="matInfo">待设置材质信息</param>
		/// <param name="usedState">是否被使用</param>
		void SetFreeMatInfo(MatInfo matInfo, bool usedState)
		{
			if(matInfo == null)
				return;
			
			if(matInfoPool.ContainsKey(matInfo))
				matInfoPool[matInfo] = usedState;
		}
		
		/// <summary>
		/// 清理缓存池,释放引用
		/// </summary>
		public void CleanCachePool()
		{
			matInfoPool.Clear();
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值