物体放大屏幕Dither特效方法记录。
首先是用于显示Dither效果的材质:
其次使用来使Dither特效物体与原始物体同步的组件,目前是只同步了朝向:
using UnityEngine;
public class SynchronizedRot : MonoBehaviour
{
public Transform trSyncRot;
void Update()
{
if (transform) transform.localRotation = trSyncRot.localRotation;
}
}
重头戏是对原始物体的复制以及原始物体和Dither特效物体之间的连接特效,这个连接特效原本是打算使用LineRender组件,但是始终不理想,最后使用了quad片片,通过调整这个片片的点来实现连接效果:
using System.Collections;
using UnityEngine;
public class SynchronizedDither : MonoBehaviour
{
public Transform trSource;
[SerializeField]
Vector3 posShow;
[SerializeField]
float scaleMulti = 1;
[SerializeField]
Material matDither;
Renderer[] _sourceRenders;
Renderer[] sourceRenders { get { if (_sourceRenders == null) _sourceRenders = trSource.GetComponentsInChildren<Renderer>(); return _sourceRenders; } }
GameObject _objDither;
GameObject objDither { get { if (!_objDither) _objDither = new GameObject("Dither"); return _objDither; } }
Renderer[] _ditherRenders;
Renderer[] ditherRenders { get { if (_ditherRenders == null) _ditherRenders = objDither.GetComponentsInChildren<Renderer>(); return _ditherRenders; } }
MeshFilter _rayFilter;
MeshFilter rayFilter { get { if (!_rayFilter) _rayFilter = GetComponent<MeshFilter>(); return _rayFilter; } }
void Start()
{
StartCoroutine(AddDitherDelay());
}
void Update()
{
if (trSource)
{
if (_objDither)
{
objDither.transform.rotation = trSource.rotation;
//
UpdateRay();
}
}
}
IEnumerator AddDitherDelay()
{
yield return new WaitUntil(delegate { return trSource; });
objDither.transform.SetParent(transform);
objDither.transform.position = posShow;
objDither.transform.localScale = trSource.localScale * scaleMulti;
MeshFilter filter = trSource.GetComponent<MeshFilter>();
if (filter)
{
MeshFilter filterDither = objDither.AddComponent<MeshFilter>();
filterDither.sharedMesh = filter.sharedMesh;
MeshRenderer renderDither = objDither.AddComponent<MeshRenderer>();
renderDither.material = matDither;
SynchronizedRot syncRot = objDither.AddComponent<SynchronizedRot>();
syncRot.trSyncRot = trSource;
}
AddObj(trSource, objDither.transform);
}
void AddObj(Transform tranSource, Transform tranDiter)
{
for (int i = 0; i < tranSource.childCount; i++)
{
Transform child = tranSource.GetChild(i);
GameObject objDither = new GameObject("Dither");
objDither.transform.SetParent(tranDiter);
MeshFilter filter = child.GetComponent<MeshFilter>();
if (filter)
{
MeshFilter filterDither = objDither.AddComponent<MeshFilter>();
filterDither.sharedMesh = filter.sharedMesh;
MeshRenderer renderDither = objDither.AddComponent<MeshRenderer>();
renderDither.material = matDither;
}
//
objDither.transform.localPosition = child.localPosition;
objDither.transform.localRotation = child.localRotation;
objDither.transform.localScale = child.localScale;
//
SynchronizedRot syncRot = objDither.AddComponent<SynchronizedRot>();
syncRot.trSyncRot = child;
AddObj(child, objDither.transform);
}
}
void UpdateRay()
{
Bounds sourceBounds;
GetRendersBounds(sourceRenders, out sourceBounds);
Bounds ditherBounds;
GetRendersBounds(ditherRenders, out ditherBounds);
//
Vector3 direction = ditherBounds.center - sourceBounds.center;
//
Vector3 sourceDown;
Vector3 sourceUp;
GetMeshPos(sourceBounds, direction, out sourceDown, out sourceUp);
//
Vector3 ditherDown;
Vector3 ditherUp;
GetMeshPos(ditherBounds, direction, out ditherDown, out ditherUp);
//
Vector3[] vTarget = new Vector3[4];
vTarget[0] = transform.InverseTransformPoint(sourceDown);
vTarget[1] = transform.InverseTransformPoint(ditherDown);
vTarget[2] = transform.InverseTransformPoint(sourceUp);
vTarget[3] = transform.InverseTransformPoint(ditherUp);
Vector3[] vertices = rayFilter.mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
vertices[i] = Vector3.Lerp(vertices[i], vTarget[i], Time.deltaTime * 3);
}
rayFilter.mesh.vertices = vertices;
}
void GetRendersBounds(Renderer[] renders, out Bounds bounds)
{
Vector3 posMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3 posMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
foreach (Renderer render in renders)
{
posMin = Vector3.Min(posMin, render.bounds.min);
posMax = Vector3.Max(posMax, render.bounds.max);
}
bounds = new Bounds();
bounds.center = (posMin + posMax) * 0.5f;
bounds.size = posMax - posMin;
}
void GetMeshPos(Bounds bounds, Vector3 direction, out Vector3 sideA, out Vector3 sideB)
{
Vector3 min = bounds.min;
Vector3 max = bounds.max;
Vector3[] vs = new Vector3[8];
//保存bounds8个点的位置
vs[0] = min;
vs[1] = max;
vs[2] = new Vector3(min.x, min.y, max.z);
vs[3] = new Vector3(min.x, max.y, min.z);
vs[4] = new Vector3(max.x, min.y, min.z);
vs[5] = new Vector3(min.x, max.y, max.z);
vs[6] = new Vector3(max.x, min.y, max.z);
vs[7] = new Vector3(max.x, max.y, min.z);
//
Camera cam = Camera.main;
//bounds中心的位置
Vector3 scrBoundsCenter = cam.WorldToScreenPoint(bounds.center);
//bounds中心指向处的位置
Vector3 scrBoundsAddDirection = cam.WorldToScreenPoint(bounds.center + direction);
//屏幕坐标的指向
Vector2 scrDirection = scrBoundsAddDirection - scrBoundsCenter;
//与屏幕坐标指向垂直的指向
Vector2 scrProject = new Vector2(scrDirection.y, -scrDirection.x);
//定义一侧距离屏幕指向直线最远的点
Vector3 scrPosA = cam.WorldToScreenPoint(min);
//定义另一侧距离屏幕指向直线最远的点
Vector3 scrPosB = scrPosA;
//一侧最大的dot
float dotMax = float.MinValue;
//另一侧最大的dot
float dotMin = float.MaxValue;
foreach (Vector3 v in vs)
{
Vector3 scrPos = cam.WorldToScreenPoint(v);
Vector2 scrDir = scrPos - scrBoundsCenter;
float dot = Vector2.Dot(scrDir, scrProject);
if (dot > 0)
{
if (dotMax < dot)
{
dotMax = dot;
scrPosA = scrPos;
}
}
else
{
if (dotMin > dot)
{
dotMin = dot;
scrPosB = scrPos;
}
}
}
sideA = cam.ScreenToWorldPoint(scrPosA);
sideB = cam.ScreenToWorldPoint(scrPosB);
}
}
这个核心是不要厌烦三维世界坐标和屏幕坐标之间的反复转换,呵呵。
最终测试效果: