物体放大屏幕Dither特效方法记录

该文章详细记录了在Unity3D中创建Dither特效的方法,包括使用材质显示Dither效果,通过SynchronizedRot组件保持原物体与特效物体的朝向同步。重点讲述了如何利用quad片片模拟线渲染效果,实现物体间的连接特效,以及在处理3D世界坐标和屏幕坐标转换中的关键代码。最后展示了测试效果。
摘要由CSDN通过智能技术生成

物体放大屏幕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);
	}
}

这个核心是不要厌烦三维世界坐标和屏幕坐标之间的反复转换,呵呵。

最终测试效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值