/**********************************************************
* 两者中间对象透明组件,一般来说挂在主角上,监视者为主
* 摄像机。剧情动画时可挂在动画主角上。组件特性:反向检
* 测、透明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();
}
}
}
[GEngine]相机角色中间物体透明(BetweenTransparent)
最新推荐文章于 2022-04-06 09:11:08 发布