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 透明效果

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

相关推荐
一个小狼娃9 小时前
Android集成Unity避坑指南
android·游戏·unity
极客柒9 小时前
Unity 协程GC优化记录
java·unity·游戏引擎
黄思搏10 小时前
Unity SpriteRenderer 进度条 Shader 实现
unity·游戏引擎
猫屋小鱼丸11 小时前
手把手教你在unity中实现一个视觉小说系统(一)
unity
国服第二切图仔15 小时前
Rust开发实战之简单游戏开发(piston游戏引擎)
开发语言·rust·游戏引擎
HahaGiver6661 天前
Unity与Android原生交互开发入门篇 - 打开Unity游戏的设置
android·unity·交互
@LYZY1 天前
Unity TextMeshPro 文本对齐方式详解
unity·游戏引擎·textmeshpro·tmp
在路上看风景1 天前
2.1 ShaderLab - 渲染状态
unity
AA陈超1 天前
虚幻引擎5 GAS开发俯视角RPG游戏 P07-06 能力输入的回调
c++·游戏·ue5·游戏引擎·虚幻
一线灵1 天前
跨平台游戏引擎 Axmol-2.9.1 发布
游戏引擎