Unity - 人物对象的 LOD 管理


Unity 没有内置的人物角色 LOD 管理

参考了 Unity 论坛上的某个帖子:Level of Detail management (LOD)

虽然 Unity 没有内置的角色 LOD 管理
在这里插入图片描述

但是我们可以自己根据距离来切换网格
在这里插入图片描述


可以自己写脚本按距离来控制

直接参考:https://pastebin.com/clone/K1S4Zjh0

using UnityEngine;
using System;
using System.Collections.Generic;

//[ExecuteInEditMode]
public class CharacterLOD : MonoBehaviour
{
    public Mesh LOD0;
    public Mesh LOD1;
    public Mesh LOD2;
    public Mesh LOD3;

    public List<Material> LOD_0_Materials;
    public List<Material> LOD_1_Materials;
    public List<Material> LOD_2_Materials;
    public List<Material> LOD_3_Materials;

    public float lod0Dist = 10f;
    public float lod1Dist = 20f;
    public float lod2Dist = 30f;
    public float lod3Dist = 50f;
    public float lodTimer = 0.3f;

    public Camera camera1;
    private bool foundCamera;
    public int currLOD;
    private float currTimer;
    

    public void Start()
    {
        GetCamera();
        currLOD = 0;
    }

    void Update()
    {
        if (foundCamera == false)
            GetCamera();

        //Update LODs Timer
        currTimer -= Time.deltaTime;
        if (currTimer <= 0.0f)
        {
            UpdateMeshLOD();

            currTimer = lodTimer; //Reset Timer
        }
    }

    public void UpdateMeshLOD()
    {
        if (camera1 == null) return;

        Vector3 camPos1 = camera1.transform.position;

        if ((transform.position - camPos1).sqrMagnitude < (lod0Dist * QualitySettings.lodBias) * (lod0Dist * QualitySettings.lodBias)) //LOD 0
        {
            if (currLOD != 0)
            {
                SetLowestLODMesh(QualitySettings.maximumLODLevel);
                SetLODMaterials(QualitySettings.maximumLODLevel);
                currLOD = 0;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod1Dist * QualitySettings.lodBias) * (lod1Dist * QualitySettings.lodBias)) //LOD 1
        {
            if (currLOD != 1)
            {
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 1);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 1));
                currLOD = 1;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod2Dist * QualitySettings.lodBias) * (lod2Dist * QualitySettings.lodBias)) //LOD 2
        {
            if (currLOD != 2)
            {
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 2);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 2));
                currLOD = 2;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod3Dist * QualitySettings.lodBias) * (lod3Dist * QualitySettings.lodBias)) //LOD 3
        {
            if (currLOD != 3)
            {
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 3);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 3));
                currLOD = 3;
                return;
            }
        }
    }

    public void SetLODMaterials(int lod)
    {
        Material[] currMats;
        bool wasSuccess = false;

        switch (lod)
        {
            case 0: //LOD 0
                if (LOD_0_Materials.Count > 0)
                {
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_0_Materials.Count];
                    for (var x = 0; x < LOD_0_Materials.Count; x++)
                    {
                        currMats[x] = LOD_0_Materials[x];
                        if (LOD_0_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_0_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    } 
                }
                break;
            case 1:
                if (LOD_1_Materials.Count > 0)
                {
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_1_Materials.Count];
                    for (var x = 0; x < LOD_1_Materials.Count; x++)
                    {
                        currMats[x] = LOD_1_Materials[x];
                        if (LOD_1_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_1_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
            case 2:
                if (LOD_2_Materials.Count > 0)
                {
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_2_Materials.Count];
                    for (var x = 0; x < LOD_2_Materials.Count; x++)
                    {
                        currMats[x] = LOD_2_Materials[x];
                        if (LOD_2_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_2_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
            case 3:
                if (LOD_3_Materials.Count > 0)
                {
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_3_Materials.Count];
                    for (var x = 0; x < LOD_3_Materials.Count; x++)
                    {
                        currMats[x] = LOD_3_Materials[x];
                        if (LOD_3_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_3_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
        }

        if (wasSuccess)
            Debug.Log("[CharacterLOD] " + this.transform.root.name + " Swapped LOD Mats to: " + lod);
    }

    public int GetLowestLODMats(int desired)
    {
        if (desired >= 3)
        {
            if (LOD_3_Materials.Count > 0)
                return 3;
            if (LOD_2_Materials.Count > 0)
                return 2;
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 2)
        {
            if (LOD_2_Materials.Count > 0)
                return 2;
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 1)
        {
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 0)
        {
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        return 0;
    }

    public void SetLowestLODMesh(int desired)
    {
        SkinnedMeshRenderer mf1 = GetComponent<SkinnedMeshRenderer>();
        if (desired >= 3)
        {
            if (LOD3 != null)
                mf1.sharedMesh = LOD3;
            if (LOD2 != null)
                mf1.sharedMesh = LOD2;
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 2)
        {
            if (LOD2 != null)
                mf1.sharedMesh = LOD2;
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 1)
        {
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 0)
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
    }

    public void GetCamera()
    {
        try
        {
            camera1 = Camera.main;
            foundCamera = true;
        }
        catch (Exception e)
        {
            Debug.Log("[CharacterLOD] Couldn't find Main Camera: " + e.Message);
        }
    }
}

扩展、及其问题

上面按距离的方式来处理的话会有一些问题

比如说再 FPS 游戏中就不太好用

因为游戏会有倍镜

而倍镜一般时调整 FOV 来处理的

再使用 LODGroup 中处理的时按屏幕高度比例来处理的话,这种制作时没有问题的

但时 LODGROUP 的方式不设置对 SkinnedMeshRenderer 做处理

因为 SkinnedMeshRenderer 内部有动画信息的状态

所以只能写脚本切换 Mesh 即可,动画骨骼信息不要动他就可以了

然后配上 自己按 FOV 的数值来换算一下,让 FOV 不同大小下也可以比较正常的显示 SknnedMeshRnederer 的 LOD


还有另一个问题,以上这些方式都不能切换骨骼结构

因为如果一个模型骨骼结构很复杂,也时消耗比较大的

但是理论上,我们可以再写一个:再切换 LOD时,可以将前一个LOD的 Animator 状态和 AnimationClip.playTime 同步一下到当前的 LOD 即可,但是不保证会不会抖动,得看 API 得封装够不够好


另一种更好的方式

  • 首先得有 带骨骼蒙皮的 模型,和 或对应的 LOD1, LOD2
  • 直接给父级添加 LODGroup ,设置好每一级的 LOD renderers 就OK了
  • 可以无缝LOD切换

References

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue-Unity-WebGL 是一个具有极高可扩展性和灵活性的 Unity3D web 端开发框架,它将 Vue.js 与 Unity Web Player 和 WebGL 等技术相结合,为开发者提供了最佳的解决方案。 Vue-Unity-WebGL 框架具有很高的兼容性和易用性,开发者可以更加灵活地应用该框架来定制自己的项目。由于该框架具备了许多优秀的特性,如自适应布局、多平台支持等,使得开发者可以轻松地实现用户体验和开发效率的提升。此外,Vue-Unity-WebGL 框架不仅提供了可视化开发工具,还提供了完整的运行环境,为开发者提供了优秀的开发体验。 Vue-Unity-WebGL 框架的另一个重要特点是其大量的插件与扩展功能,这些插件和扩展可以为项目的开发和管理提供坚强的技术支持。比如,通过 vue-router 可以控制路由,Vue-Unity-WebGL 可以协作处理组件数据和 Unity3D 渲染等复杂的操作,而 Vuex 则可以使开发者方便地处理应用数据流和组件状态的管理。这些插件和扩展功能极大地提高了 Vue-Unity-WebGL 框架的可扩展性和灵活性,使得开发者可以更加容易地进行定制。 综上所述,Vue-Unity-WebGL 框架是一个快速、可靠且强大的解决方案,这使得开发者能够轻松地编写出高质量的 Unity3D web 应用程序。该框架具有大量的功能,实现可扩展性、灵活性、易用性和可维护性,比其他框架更具有竞争力。在未来的发展中,Vue-Unity-WebGL 框架将会被更多的开发者喜爱和应用,并在技术社区中拥有更广泛的影响力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值