【反射】Unity 3D 用于平面反射的动态克隆反射(支持VR设备)

【反射】Unity 3D 用于平面反射的动态克隆反射(支持VR设备)

解决方案

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值