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

发布时间:2024年01月02日

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:控制材质是否支持透明度,更多信息,请参阅文档: 「https://docs.unity3d.com/cn/Packages/com.unity.render-pipelines.high-definition@7.4/manual/Surface-Type.html」

    • Opaque:模拟没有光线穿透的全实体材质。

    • Transparent:模拟光线可以穿透的半透明材质,例如透明塑料或玻璃。选择 Transparent 会在 Surface Options 部分中显示更多属性,还会显示一个额外的 Transparency Inputs 部分

  • Blend:确定 GPU 如何将片元着色器的输出与渲染目标进行合并,更多信息请参阅文档: 「https://docs.unity3d.com/cn/current/Manual/SL-Blend.html」

通过脚本更改 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 透明效果

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

文章来源:https://blog.csdn.net/u010799737/article/details/135346401
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。