Unity | 渡鸦避难所-5 | 角色和摄像机之间的遮挡物半透明

1 前言

角色在地图上移动到岩石后面时,完全被岩石遮挡,玩家只能看到岩石。这逻辑看起来没问题,但并不是玩家想要看到的画面,玩家更希望关注角色的状态

为了避免角色被遮挡,可以使用 Cinemachine Collider 功能,虚拟相机会自动避开障碍物,或者将角色和摄像机之间的障碍物做半透明处理,这两种方式的原理都是利用物理射线

这里使用障碍物半透明的方案,利用物理射线,检测角色和摄像机之间指定 Layer 的障碍物,改变其透明度,角色移动后,恢复其原本的透明度

2 设置 Layer

场景中的对象类型多种多样,我们仅希望岩石、树木等环境中的物体遮挡角色时,才改变其透明度。因此需要将对象分为不同的类型,利用 Unity 的 Layer 功能可以轻松实现该需求

Layer 定义哪些游戏对象可以与不同的功能以及彼此交互。它们主要有两种用途:由摄像机用来仅渲染场景的某一部分;由光源用来仅照亮场景的某些部分。但是,层也可以供射线投射用于选择性地忽略碰撞体或创建碰撞。更多信息请参阅文档:https://docs.unity3d.com/cn/2021.2/Manual/Layers.html」

1 添加 Layer

这里偷个懒,只创建一个 Environment 层,就不再细分了

2 分配 Layer

将 Free_Forest 及其子对象均设置为 Environment 层

3 射线检测

在场景中从角色向摄像机投射一条射线,获取 Environment 层中所有命中的对象

脚本中获取 Environment 层的方式有两种

var layerMask = LayerMask.GetMask("Environment");

或者

var layerMask = 1 << LayerMask.NameToLayer("Environment");

通过角色和摄像机的位置计算出射线投射的方向和距离,利用 Physics.RaycastNonAlloc 来获取射线命中对象,相比 Physics.RaycastAll,此函数不会产生任何垃圾,毕竟在 FixedUpdate 中进行射线检测,应当尽量减少性能损耗

var size = Physics.RaycastNonAlloc(selfPosition, direction, this._raycastHits, rayDistance, layerMask);

获取命中对象中所有子节点的材质集合,和上一次命中的材质集合对比,改变其透明度

private void TransparentObjects()
{
    Vector3 selfPosition = transHead.position;
    Vector3 cameraPosition = _cameraTrans.position;
    var rayDistance = Vector3.Distance(selfPosition, cameraPosition);
    Vector3 direction = Vector3.Normalize(cameraPosition - selfPosition);
    Debug.DrawLine(selfPosition, cameraPosition, Color.red);

    var layerMask = LayerMask.GetMask("Environment");
    var size = Physics.RaycastNonAlloc(selfPosition, direction, this._raycastHits, rayDistance, layerMask);
    List<Material> materials = new List<Material>();
    for (int i = 0; i < size; i++)
    {
        var meshRenderers = this._raycastHits[i].collider.GetComponentsInChildren<MeshRenderer>();
        foreach (var variable in meshRenderers)
        {
            materials.AddRange(variable.materials);
        }
    }

    var transparentList = materials.Except(_materialList).ToList();
    var opaqueList = _materialList.Except(materials).ToList();
    foreach (var variable in transparentList)
    {
        MaterialTransparent.SetMaterialTransparent(true, variable, 0.22f);
    }

    foreach (var variable in opaqueList)
    {
        MaterialTransparent.SetMaterialTransparent(false, variable);
    }

    _materialList = materials;
}

4 更改透明度

由于性能等因素,默认情况下 3D 模型的材质是不支持更改透明度的。需要将材质的 Rendering Mode(对于内置渲染管线)或 Surface Type(对于 URP 或 HDRP)设置为 Transparent,才可以调整透明度相关的属性

这里我们需要更改的属性主要为:

通过脚本更改 Surface Type 和 Blend 属性:

public class MaterialTransparent
{
    private enum SurfaceType
    {
        Opaque,
        Transparent
    }

    private enum BlendMode
    {
        Alpha,
        Premultiply,
        Additive,
        Multiply
    }

    public static void SetMaterialTransparent(bool transparent, Material material, float alpha = 1)
    {
        if (transparent)
        {
            material.SetFloat("_Surface", (float)SurfaceType.Transparent);
            material.SetFloat("_Blend", (float)BlendMode.Alpha);
        }
        else
        {
            material.SetFloat("_Surface", (float)SurfaceType.Opaque);
        }

        SetupMaterialBlendMode(material);
        
        Color color = material.color;
        color.a = alpha;
        material.color = color;
    }

    private static void SetupMaterialBlendMode(Material material)
    {
        if (material == null)
        {
            return;
        }

        bool alphaClip = material.GetFloat("_AlphaClip") == 1;
        if (alphaClip)
        {
            material.EnableKeyword("_ALPHATEST_ON");
        }
        else
        {
            material.DisableKeyword("_ALPHATEST_ON");
        }

        SurfaceType surfaceType = (SurfaceType)material.GetFloat("_Surface");
        if (surfaceType == 0)
        {
            material.SetOverrideTag("RenderType", "");
            material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
            material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
            material.SetInt("_ZWrite", 1);
            material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
            material.renderQueue = -1;
            material.SetShaderPassEnabled("ShadowCaster", true);
        }
        else
        {
            BlendMode blendMode = (BlendMode)material.GetFloat("_Blend");
            switch (blendMode)
            {
                case BlendMode.Alpha:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                    material.SetInt("_ZWrite", 0);
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                    material.SetShaderPassEnabled("ShadowCaster", false);
                    break;
                case BlendMode.Premultiply:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                    material.SetInt("_ZWrite", 0);
                    material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                    material.SetShaderPassEnabled("ShadowCaster", false);
                    break;
                case BlendMode.Additive:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_ZWrite", 0);
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                    material.SetShaderPassEnabled("ShadowCaster", false);
                    break;
                case BlendMode.Multiply:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstColor);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                    material.SetInt("_ZWrite", 0);
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
                    material.SetShaderPassEnabled("ShadowCaster", false);
                    break;
            }
        }
    }
}

5 透明效果

最终效果如下图所示,角色走到岩石后面时,岩石呈现半透明状态

相关推荐
_oP_i9 小时前
Unity 3D 脚本中的常用函数
unity·游戏引擎
larito10 小时前
unity 高性能对象池解决方案
unity
周周的Unity小屋10 小时前
虚幻引擎Gameplay探索 Actor 之间的高效通信与交互技巧一
游戏引擎·虚幻·actor通信
charon877813 小时前
Unreal Engine 5 C++: 编辑器工具编写入门(中文解释)
c++·ue5·编辑器·游戏引擎·虚幻
TO_ZRG15 小时前
使用jenkins打包unity工程
运维·unity·jenkins
dangoxiba1 天前
【Unity学习心得】如何使用Unity制作“饥荒”风格的俯视角2.5D游戏
游戏·unity·c#·游戏引擎
cyr___1 天前
Unity教程(十六)敌人攻击状态的实现
学习·游戏·unity·游戏引擎
优梦创客2 天前
《黑神话悟空》开发框架与战斗系统解析
unity·游戏开发·黑神话悟空·战斗系统·解包
仙魁XAN2 天前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
我要吐泡泡了哦2 天前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎