解决方案
Unity 中实现反射的方案不少,最基础的便是反射探头(Refelction Probe)和立方体贴图(Cubemap),但是反射探头(基于立方体贴图)和立方体贴图的问题也很明显:第一,无法制作出看起来真实的反射——因为反射探头是基于探头位置渲染的贴图信息,而不是真实的反射信息;第二,VR设备内,反射探头的反射信息不具备立体感,与现实不符;第三,实时渲染消耗很大。
下图中,明显可以看出基于反射探头的反射信息是错误的。(汽车等模型没有倒影,墙壁处的倒影细看是错的)
除了反射探头外,还有一种经典的方案是基于屏幕空间的反射(SSR —— Screen Space Reflection),基于这种反射的实时反射效率要比反射探头高得多,也能制作出屏幕上看起来真实的反射信息。但是,在VR设备内,它的效果是很糟糕的:第一,反射还是在平面上,无立体感;第二,SSR是基于屏幕坐标的,但VR设备内渲染计算要更复杂一些(VR设备不是简单的屏幕剪裁,而是大视角渲染,视中心剪裁,边缘变形等),导致VR设备内的反射效果完全错误。
而第三种方法就是很传统的一种方案,克隆反射——顾名思义,这里不是真的反射,而是把要反射的物体克隆出一份,放置于正确的位置,调节好旋转角度,使之产生反射的假象。这种反射也有它的问题:第一,不适用于曲面;第二,也不适用于背后有其他物体的平面;第三,如果需要被反射的物体很多,会增加渲染压力。不过,它在VR设备中是立体的,而且在实时反射上与反射探头相比,效率高得多。
针对静态物体,我们可以直接复制并将父节点Scale的Y值改为-1。但是针对动态物体,想要实现克隆反射则要稍微复杂一些。这里的思路是将动态物体的父节点发送给脚本,获取所有子节点下的Mesh及材质信息用以克隆出镜像,使镜像的位置与旋转角度随着原物体变化而变化。
先上效果图,可以看到,飞机、汽车、墙壁的反射效果都是正确的,而且是动态变化的:
我的测试项目:
InnerReflectionFloor
源码,挂载在地板上的。
using System.Collections.Generic;
using UnityEngine;
public class InnerReflectionFloor : MonoBehaviour
{
[Tooltip("Only objects with InnerDetector will be reflected by the floor")]
public List<Transform> TransList;
private List<InnerDetector> mObjList = new List<InnerDetector>();
private Vector3 mOriginPos;
private Vector3 mMirrorPos;
private Vector3 mMirrorNor;
private Vector3 mVector3Buffer;
private float mDistance;
void Start()
{
foreach (Transform tr in TransList)
{
AddObjToList(tr);
}
}
private void AddObjToList(Transform tr)
{
if (tr.GetComponent<MeshRenderer>() && tr.GetComponent<MeshRenderer>().enabled == true)
{
tr.gameObject.AddComponent<InnerDetector>();
}
if (tr.GetComponent<InnerDetector>())
{
mObjList.Add(tr.GetComponent<InnerDetector>());
}
for (int i = 0; i < tr.childCount; i++)
{
AddObjToList(tr.GetChild(i));
}
}
void Update()
{
ReflectioUpdate();
}
private void ReflectioUpdate()
{
foreach (InnerDetector id in mObjList)
{
if (id != null && id.mIfInside == true)
{
if (id.mReflection == null)
{
id.mReflection = new GameObject();
id.mReflection.AddComponent<MeshFilter>().mesh = id.gameObject.GetComponent<MeshFilter>().mesh;
id.mReflection.AddComponent<MeshRenderer>().material = id.gameObject.GetComponent<MeshRenderer>().material;
mVector3Buffer = id.transform.localScale;
mVector3Buffer.z = -mVector3Buffer.z;
id.mReflection.transform.localScale = mVector3Buffer;
}
else
{
mOriginPos = id.transform.position;
mMirrorPos = transform.position;
mMirrorNor = Vector3.up;
mDistance = Vector3.Dot((mOriginPos - mMirrorPos), mMirrorNor);
id.mReflection.transform.position = mOriginPos - 2 * mDistance * mMirrorNor;
mVector3Buffer = id.transform.eulerAngles;
id.mReflection.transform.eulerAngles = mVector3Buffer + (Vector3.forward + Vector3.up) * 180;
}
}
else
{
mObjList.Remove(id);
return;
}
}
}
}
InnerDetector
源码,放在文件夹里即可——用于确定是否参与室内反射,不过和本文主题无关,所以简化了,真实项目中需要从新调整。
using UnityEngine;
public class InnerDetector : MonoBehaviour
{
internal bool mIfInside = true;
internal GameObject mReflection = null;
void OnDisable()
{
DestroyImmediate(mReflection);
}
void OnDestroy()
{
DestroyImmediate(mReflection);
}
}
配置
只需要修改TransList
的大小,并加入相关动态物体的Transform
(或者父节点、祖节点)。
我的测试项目:
原文链接
https://blog.csdn.net/weixin_36273312/article/details/53816562