首页 > 其他分享 >Unity的SkinnedMeshRenderer性能优化

Unity的SkinnedMeshRenderer性能优化

时间:2024-10-30 18:19:17浏览次数:6  
标签:SkinnedMeshRenderer 骨骼 网格 Skinning Unity GPU 优化 CPU

Unity支持两种主要的Skinning技术
在Unity中,Skinning主要指的是角色的蒙皮过程,这是3D动画中的一个关键步骤,用于将3D模型的网格(皮肤)附着到骨骼上,使得模型可以根据骨骼的动作进行逼真的变形。Unity支持两种主要的Skinning技术:CPU SkinningGPU Skinning

1. CPU Skinning

CPU Skinning,也称为软件蒙皮,是一种较早的技术,其中所有的蒙皮计算都是由CPU完成的。在这种方法中,每个顶点的最终位置是通过CPU计算得出的,然后将计算结果传递给GPU进行渲染。这种方法的优点是兼容性好,几乎可以在所有设备上运行,但缺点是消耗CPU资源较多,可能会影响游戏的性能,特别是在有大量动画角色时。

简而言之

基于CPU的实现是这样:每帧用多线程计算每个骨骼的Transform,然后将Mesh上的所有顶点计算出当前位置,保存在一个缓存Mesh里;然后再去渲染这个Mesh。

CPU Skinning c#代码逻辑实现

在Unity中,实现CPU Skinning通常不是直接通过C#代码完成的,因为Unity的内部渲染引擎已经处理了大部分与蒙皮相关的细节。然而,如果你想要了解如何在Unity中通过C#模拟CPU Skinning的过程,或者创建一个自定义的蒙皮系统,你可以通过编写一个脚本来手动修改网格的顶点位置,模拟蒙皮的效果。

以下是一个简化的例子,展示了如何在Unity中使用C#来实现一个基本的CPU Skinning逻辑。这个例子假设你已经有一个带有骨骼动画的角色,并且角色的网格和骨骼变换已经设置好。

using UnityEngine;

public class CPUSkinning : MonoBehaviour
{
    public SkinnedMeshRenderer skinnedMeshRenderer;
    private Mesh mesh;
    private Vector3[] originalVertices;
    private Vector3[] animatedVertices;
    private Matrix4x4[] boneMatrices;

    void Start()
    {
        mesh = skinnedMeshRenderer.sharedMesh;
        originalVertices = mesh.vertices;
        animatedVertices = new Vector3[originalVertices.Length];
        boneMatrices = new Matrix4x4[skinnedMeshRenderer.bones.Length];
    }

    void Update()
    {
        // Update bone matrices
        for (int i = 0; i < skinnedMeshRenderer.bones.Length; i++)
        {
            boneMatrices[i] = skinnedMeshRenderer.bones[i].localToWorldMatrix * skinnedMeshRenderer.sharedMesh.bindposes[i];
        }

        // Apply skinning
        for (int i = 0; i < originalVertices.Length; i++)
        {
            Vector3 vertex = originalVertices[i];
            Vector3 transformedVertex = new Vector3();

            // Simple example with one bone influence
            // In reality, you should consider multiple bone influences for each vertex
            transformedVertex = boneMatrices[0].MultiplyPoint3x4(vertex);

            animatedVertices[i] = transformedVertex;
        }

        // Update mesh vertices
        mesh.vertices = animatedVertices;
        mesh.RecalculateBounds();
    }
}

说明:

  1. 初始化: 在Start方法中,我们获取原始网格和顶点,并为每个顶点准备变换后的数组。
  2. 更新骨骼矩阵: 在每个Update调用中,我们计算每个骨骼的变换矩阵,这是通过将骨骼的localToWorldMatrix与网格的bindposes相乘得到的。
  3. 应用蒙皮: 对于每个顶点,我们应用骨骼矩阵的变换。这里的示例仅考虑了单一骨骼的影响,但实际应用中,顶点可能受到多个骨骼的影响,需要加权平均这些影响。
  4. 更新网格: 最后,我们将变换后的顶点赋值回网格,并重新计算网格的边界。

这个示例是一个非常基础的实现,仅用于教学目的。在实际游戏开发中,直接在CPU上处理蒙皮通常不是最优选择,因为这会大量占用CPU资源,降低游戏性能。如果可能,最好利用Unity的内置蒙皮支持,或者使用GPU Skinning来提高效率。
存在的问题:
消耗了很多CPU资源,对于顶点数较多的Mesh,并不是最高效的方法;同时,因为它为每个Instance都申请了一个缓存,并且这个缓存在CPU和GPU上都有一份,导致内存消耗会随着个数的增长而增长

2. GPU Skinning

GPU Skinning,也称为硬件蒙皮,是一种更现代的技术,利用GPU的并行处理能力来进行蒙皮计算。在这种方法中,蒙皮的计算过程是在GPU上执行的,通常通过顶点着色器来实现。这样可以显著减轻CPU的负担,允许更多的角色同时进行复杂的动画,从而提高整体性能。然而,这种方法对硬件的要求更高,可能在旧的或性能较低的设备上不被支持。
效果分析:方法只是把计算顶点移到了GPU上,而且这个步骤用到了OpenGL ES3.0的特性。CPU性能确实节省了下来,但是实测内存反而比基于CPU的实现占用还要高

简而言之

首先还是多线程计算每个骨骼的Transform,然后通过一个TransformFeedback,将Mesh上所有顶点的当前位置计算并保存到一个VBO中;然后再去渲染这个VBO

实现细节

在Unity中,你可以通过材质的设置来控制使用哪种Skinning方法。例如,你可以在材质的Shader中启用或禁用GPU Skinning。Unity也提供了自动选择最佳Skinning方法的功能,根据运行游戏的硬件配置自动选择CPU Skinning或GPU Skinning。

GPU Skinning

CPU端
在Unity中,当使用GPU Skinning时,大部分重型计算工作都是在GPU上完成的,而CPU端的工作主要集中在准备和传递数据给GPU。这包括设置和更新Shader中使用的骨骼变换矩阵等。下面,我将提供一个简单的C#脚本示例,展示如何在Unity中为GPU Skinning准备和传递骨骼矩阵。

C# 脚本示例

这个脚本假设你已经有一个使用GPU Skinning的Shader,并且这个Shader需要一个骨骼矩阵数组作为输入。

using UnityEngine;

public class GPUSkinningSetup : MonoBehaviour
{
    public SkinnedMeshRenderer skinnedMeshRenderer;
    private Matrix4x4[] boneMatrices;
    private MaterialPropertyBlock propertyBlock;

    void Start()
    {
        // 初始化骨骼矩阵数组和材质属性块
        boneMatrices = new Matrix4x4[skinnedMeshRenderer.bones.Length];
        propertyBlock = new MaterialPropertyBlock();
    }

    void Update()
    {
        // 更新骨骼矩阵
        for (int i = 0; i < skinnedMeshRenderer.bones.Length; i++)
        {
            boneMatrices[i] = skinnedMeshRenderer.bones[i].localToWorldMatrix * skinnedMeshRenderer.sharedMesh.bindposes[i];
        }

        // 将骨骼矩阵数组传递给材质
        propertyBlock.SetMatrixArray("_BoneMatrixArray", boneMatrices);
        skinnedMeshRenderer.SetPropertyBlock(propertyBlock);
    }
}

解释

  1. 初始化: 在Start方法中,我们初始化一个足够大的Matrix4x4数组来存储每个骨骼的变换矩阵,以及一个MaterialPropertyBlock用于优化材质属性的设置。

  2. 更新骨骼矩阵: 在每个Update调用中,我们计算每个骨骼的最终变换矩阵。这是通过将骨骼的localToWorldMatrix与网格的bindposes相乘得到的。bindposes数组包含了每个骨骼相对于网格的逆初始变换矩阵。

  3. 传递数据给GPU: 使用MaterialPropertyBlock来设置Shader属性,这里是骨骼矩阵数组。使用MaterialPropertyBlock可以避免直接修改材质本身,这样可以在多个渲染器之间共享同一个材质而不互相影响。

注意事项

  • 确保你的Shader中有一个名为_BoneMatrixArray的矩阵数组变量,且其大小足以容纳所有骨骼矩阵。
  • 这种方法的性能依赖于骨骼数量和更新频率。尽管计算是在GPU上进行,但是每帧更新大量骨骼矩阵仍然可能对CPU性能有一定影响。
  • 使用MaterialPropertyBlock可以减少对材质实例的修改,从而更好地利用Unity的批处理优化。

通过这种方式,你可以有效地在Unity中实现GPU Skinning的CPU端逻辑,优化你的角色动画处理,尤其是在处理大量复杂动画时。

性能和选择

选择哪种Skinning技术通常取决于目标平台的性能和项目的具体需求。对于需要渲染大量动画角色的高端游戏,GPU Skinning通常是更好的选择。对于目标平台较为多样或性能较低的项目,可能需要更多地依赖CPU Skinning,或者在不同的平台之间进行切换。

总之,了解这两种Skinning技术可以帮助你更好地优化你的Unity项目,确保无论在何种设备上都能提供流畅的动画表现。

gpu端

GPU Skinning主要是通过Shader程序来实现的,这意味着顶点的变换计算是在GPU上执行的,而不是在CPU上。这样可以利用GPU的高并行性来提高性能。

要在Unity中启用GPU Skinning,你主要需要关注的是Shader的编写和设置。下面我将提供一个基本的Shader示例,展示如何实现GPU Skinning。

Shader代码示例

这是一个简单的Vertex Shader示例,用于实现GPU Skinning:

Shader "Custom/SimpleGPUSkinning"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 boneWeights : BLENDWEIGHT;
                float4 boneIndices : BLENDINDICES;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4x4 _BoneMatrixArray[50]; // Adjust size according to your needs

            v2f vert (appdata v)
            {
                v2f o;
                float4x4 skinMatrix = _BoneMatrixArray[v.boneIndices[0]] * v.boneWeights[0] +
                                      _BoneMatrixArray[v.boneIndices[1]] * v.boneWeights[1] +
                                      _BoneMatrixArray[v.boneIndices[2]] * v.boneWeights[2] +
                                      _BoneMatrixArray[v.boneIndices[3]] * v.boneWeights[3];

                o.vertex = UnityObjectToClipPos(mul(skinMatrix, v.vertex));
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv);
            }
            ENDCG
        }
    }
}

解释

  1. Shader结构: 这个Shader包含了基本的纹理属性,并定义了一个简单的顶点和片段程序。
  2. 顶点数据: appdata结构体包含了顶点位置、法线、骨骼权重和骨骼索引。
  3. Skinning矩阵计算: 在顶点函数vert中,我们根据顶点的骨骼权重和骨骼索引计算出最终的Skinning矩阵。这个矩阵是通过将每个影响骨骼的变换矩阵与其对应的权重相乘,然后加总得到的。
  4. 顶点变换: 使用计算出的Skinning矩阵变换顶点位置。
  5. 纹理采样: 在片段着色器中,使用UV坐标采样纹理。

使用Shader

要在Unity中使用这个Shader,你需要:

  • 将这段代码保存为一个.shader文件。
  • 在Unity编辑器中创建一个材质,并将这个Shader赋给该材质。
  • 将材质应用到你的Skinned Mesh Renderer上。

这样设置后,Unity会自动处理GPU Skinning,你不需要在C#代码中做额外的处理。这种方法利用了GPU的高效并行计算能力,可以处理大量的顶点和复杂的骨骼动画,而对CPU的负担非常小。

性能优化

移除不需要的顶点元素
很多Mesh都包含了完整的顶点数据,包括UV、Normal、Tangent等。但是,实际使用时,又没有用到这些。对于一般的MeshRenderer,这只是损耗了内存,增大了GPU的CacheMiss数量。但是对于SkinnedMeshRenderer,这个就会造成更严重的后果。

内存上来说,因为每个实例都复制了Mesh作为缓存,并且这个数据会在显存中也有一份,实测每个Instance会占据3~5倍的Mesh数据大小。如果包含了很多多余的数据,那么这个内存量也会很大。

CPU在计算顶点数据时,因为要进行多余的数据拷贝,每帧调用DrawCall也需要上传更大的VBO,这方面也会有更大的损耗。

所以Mesh数据中,不需要的元素一定要移除掉,含有骨骼的Mesh尤其需要注意

合并DrawCall

首先,我们把Mesh数据复制N份,Combine起来:

    CombineInstance[] combine = new CombineInstance[group];

    Mesh combined;

    for (i = 0; i < group; ++i)

    {

        combine[i].mesh = mesh;

        combine[i].transform = Matrix4x4.identity;

    }

    combined = new Mesh();

    combined.CombineMeshes(combine);

然后,创建出每个实例,并且收集所有的骨骼Transform:

        for (i = 0; i < group; ++i)

        {

            var inst = (GameObject)Instantiate(tt);

            meshRenderer = inst.GetComponentInChildren<SkinnedMeshRenderer>();

            bones.AddRange(meshRenderer.bones);

            Destroy(meshRenderer.gameObject);

        }

最后,创建合并之后的单个Renderer:

            var newmesh = new GameObject();

            newmesh.AddComponent<SkinnedMeshRenderer>().sharedMesh = combined;

            meshRenderer = newmesh.GetComponent<SkinnedMeshRenderer>();

            meshRenderer.sharedMaterial = NormalMaterial;

            meshRenderer.bones = bones.ToArray();

性能分析:

N个实例Batch的话,需要额外的预处理创建一个N倍的Mesh,这会造成内存的占用。

可以各方面折中选择一个最合适的Batch数量。

动态Batch
前面的实现非常简单,有很多问题没有考虑。比如,不可见的情况下仍然会渲染,因为缺少Renderer而不能使用Animator的BasedOnRenderer,而且动态创建销毁实例的情况无法应对。这里提供一种思路可以实现动态Batch。

上面Batch之后,每个实例里面已经没有Renderer了,只有一个公共的Renderer把所有的Mesh绘制出来。这会导致单个实例的可见性无法判断。

我们可以创建一个空的Renderer,它只做一个占位,通过BoundingBox来判断是否可见,不可见的可以进行剔除。这样,BasedOnRenderer也就有一个Renderer可以用了。

                meshRenderer = gameObject.AddComponent<MeshRenderer>();

                meshRenderer.materials = new Material[0];

                meshFilter = gameObject.AddComponent<MeshFilter>();

                meshFilter.mesh = new Mesh();

        var bounds = skinnedMeshRenderer.bounds;

        bounds.center = bounds.center - skinnedMeshRenderer.transform.position;

                meshFilter.mesh.bounds = bounds;

这里,materials设为长度为0的数组,会使Unity对他只进行可见性判断,却跳过所有的渲染步骤。

创建了一个Mesh,并且取了当前的Mesh的bounds。注意,有时做起动作来,bounds会变大,导致可见性判断误差。所以这里可以考虑设置bounds的最小值,或者乘以一个大于一的系数。

这时,只要每帧去判断meshRenderer.isVisible,就可以获得每个实例的可见性。然后,对于可见的实例,将它的骨骼赋值到合并到Renderer上:

        bones.CopyTo(boneList, bones.Length* i);

对于没有凑满一个Batch大小的情况,可以用一些单个的SkinnedMeshRenderer来补齐。也可以将Batch里多出来的部分设置Scale为0,就不可见了。

还有一种比较复杂的方法,预处理时可以生成例如8个、4个、2个、1个的batch,每次都尝试使用最大的batch,然后凑不成最大batch的,就用小一点的batch。比如,14个实例,就可以用8+4+2,15个就用8+4+2+1。这种方法的坏处是,生成了更多的Mesh数据,内存占用可能会增长。

SkinnedMeshRendere 性能问题

Unity中的SkinnedMeshRenderer组件是用于渲染带有骨骼动画的网格的主要组件。虽然它非常强大和灵活,但在某些情况下,使用SkinnedMeshRenderer可能会遇到性能问题。以下是一些可能影响性能的关键方面:

  1. CPU负载:

    • 骨骼变换计算:每个骨骼的变换需要在CPU上计算,然后传递给GPU。如果角色数量多或骨骼数量多,这会显著增加CPU的负担。
    • 蒙皮计算:在CPU Skinning模式下,顶点的蒙皮变换(即根据骨骼权重和变换矩阵调整顶点位置)是在CPU上进行的,这可能会非常消耗资源。
  2. GPU负载:

    • 顶点处理:GPU Skinning虽然减轻了CPU的负担,但增加了GPU的顶点处理工作。每个顶点可能需要根据多个骨骼权重进行多次变换,这在顶点数量很多的情况下会显著增加GPU的工作量。
    • 材质和着色器复杂性:使用复杂的材质和着色器可以增强视觉效果,但也会增加GPU的负担。
  3. 内存和带宽:

    • 骨骼矩阵传输:每帧更新并传输骨骼矩阵到GPU可以消耗大量的内存带宽。
    • 网格数据:高多边形网格需要更多的内存来存储顶点数据,同时也需要更多的带宽来在CPU和GPU之间传输这些数据。
  4. 动画系统的复杂性:

    • 动画层和混合:多层动画和动画混合可以创建复杂的动作和表情,但同时也增加了计算的复杂性和资源消耗。
    • 动画状态机:复杂的动画状态机可能需要更多的计算资源来管理状态转换和同步。
  5. 批处理和渲染调用:

    • 批处理优化SkinnedMeshRenderer通常较难进行批处理优化,因为每个网格可能有不同的骨骼动画状态。这意味着即使多个角色使用相同的网格和材质,它们也可能无法合并为单个批处理调用。
    • 阴影和光照:动态阴影和复杂的光照效果(如光照图和反射)也会增加渲染负担。

优化策略

为了缓解这些性能问题,可以采取以下一些优化策略:

  • 减少骨骼数量:优化骨骼的数量,尽量减少不必要的骨骼。
  • 网格简化:减少模型的多边形数量,使用LOD(细节层次)技术来在不同的视距下渲染不同复杂度的模型。
  • 使用GPU Skinning:尽可能使用GPU Skinning来减轻CPU的负担。
  • 材质和着色器优化:优化材质和着色器,避免过度复杂的材质和高成本的着色器操作操作。
  • 动画剔除:实现动画剔除,对于视野外或距离摄像机较远的对象,降低动画更新频率或完全停止动画更新。
  • 合理使用动画层和混合:尽量减少动画层的使用,合理配置动画混合,避免不必要的复杂性。
  • 优化动画状态机:简化动画状态机的结构,减少状态数量和过渡,确保状态机的效率。
  • 内存管理:优化资源的加载和卸载,避免内存泄漏,合理管理内存使用,尤其是在移动设备上。
  • 批处理和渲染调用优化:尽管SkinnedMeshRenderer难以进行批处理,但可以通过合理的场景管理和设置,减少渲染调用的数量。例如,可以将静态背景元素与动态角色分开处理。
  • 阴影和光照优化:简化场景中的光照设置,使用更高效的阴影技术,如阴影贴图的LOD,或是使用屏幕空间阴影等技术来减少计算和渲染负担。
  • 使用更高效的数据结构:对于骨骼动画数据,使用更高效的数据结构和算法可以减少CPU和GPU的负担,例如使用压缩的骨骼动画数据或优化的骨骼矩阵更新算法。

实际案例

在实际项目中,通常需要根据具体情况选择合适的优化策略。例如,在一个大型开放世界的游戏中,可能需要特别关注动画剔除和LOD技术,以处理大量的NPC和复杂的环境。而在一个对战场景中,可能更需要优化动画状态机和减少每个角色的骨骼数量,以提高对战的流畅性。

工具和分析

Unity提供了一些工具和资源来帮助开发者分析和优化性能:

  • Profiler:Unity的Profiler工具可以帮助开发者详细了解游戏的性能瓶颈,包括CPU和GPU的使用情况,内存使用情况等。
  • Frame Debugger:可以用来逐帧分析渲染过程,帮助理解渲染过程中的各个步骤和资源消耗。
  • Unity Asset Store:提供了一些优化工具和插件,如Mesh Baker、LOD工具等,可以帮助自动化和简化一些优化过程。

通过这些工具和策略的合理使用,可以显著提高使用SkinnedMeshRenderer的Unity项目的性能,确保游戏运行流畅,提供更好的玩家体验。

SkinnedMeshRenderer进行mesh合并

在Unity中,合并使用SkinnedMeshRenderer的网格可以优化渲染性能,特别是当场景中有许多使用相同材质的角色时。合并网格可以减少绘制调用的数量,从而提高效率。下面是一个基本的步骤和示例代码,展示如何在Unity中合并使用SkinnedMeshRenderer的网格。

步骤

  1. 收集所有需要合并的SkinnedMeshRenderer组件
  2. 创建一个新的网格,将所有原始网格的顶点、三角形、UV等数据合并到这个新网格中
  3. 处理骨骼和权重:因为每个原始网格可能有不同的骨骼,需要创建一个统一的骨骼列表,并更新顶点权重,使之对应新的骨骼索引。
  4. 创建一个新的GameObject,为其添加SkinnedMeshRenderer组件,并设置合并后的网格和骨骼

示例代码

这是一个简化的示例,展示如何合并两个SkinnedMeshRenderer使用的网格。在实际应用中,可能需要根据具体情况进行调整和优化。

using UnityEngine;

public class MeshMerger : MonoBehaviour
{
    public SkinnedMeshRenderer[] meshRenderers; // Array of all SkinnedMeshRenderers to merge

    void Start()
    {
        MergeMeshes();
    }

    void MergeMeshes()
    {
        // Combine instance arrays
        CombineInstance[] combineInstances = new CombineInstance[meshRenderers.Length];
        Matrix4x4[] boneMatrices = new Matrix4x4[meshRenderers.Length];
        Transform[] newBones = new Transform[meshRenderers.Length];

        for (int i = 0; i < meshRenderers.Length; i++)
        {
            SkinnedMeshRenderer smr = meshRenderers[i];

            // For mesh
            combineInstances[i].mesh = smr.sharedMesh;
            combineInstances[i].transform = smr.transform.localToWorldMatrix;

            // For bones
            boneMatrices[i] = smr.bones[0].localToWorldMatrix; // Simplified: assuming one bone per mesh
            newBones[i] = smr.bones[0];
        }

        // Create a new SkinnedMeshRenderer
        GameObject newObject = new GameObject("CombinedMesh");
        SkinnedMeshRenderer newRenderer = newObject.AddComponent<SkinnedMeshRenderer>();
        Mesh newMesh = new Mesh();
        newMesh.CombineMeshes(combineInstances, true, true);
        newRenderer.sharedMesh = newMesh;
        newRenderer.bones = newBones;

        // Assign materials (assuming all use the same material)
        newRenderer.material = meshRenderers[0].material;
    }
}

注意事项

  • 骨骼处理:在合并网格时,处理骨骼是最复杂的部分。如果每个网格使用不同的骨骼集,你需要重新映射顶点权重到新的骨骼索引。这可能需要复杂的逻辑来确保权重正确映射。
  • 性能考虑:虽然合并网格可以减少绘制调用,但也可能增加单个批处理的复杂性。如果合并后的网格非常大,可能会影响性能。
  • 材质和纹理:确保所有合并的网格使用相同的材质,或者你需要处理多材质的情况,这可能会复杂化合并过程。

通过这种方法,你可以在Unity中有效地合并使用SkinnedMeshRenderer的网格,从而优化你的游戏或应用的渲染性能。

动态网格合并可见性问题解决方案

在Unity中,当使用SkinnedMeshRenderer进行动态网格合并后,一个常见的问题是,即使网格不可见(例如,被遮挡或在摄像机视野之外),它们仍然会进行渲染计算。这会导致不必要的性能损耗。为了解决这个问题,可以采用以下几种策略:

1. 使用摄像机的视锥剔除(Frustum Culling)

Unity默认使用视锥剔除来优化渲染性能,自动跳过那些不在摄像机视锥内的网格渲染。然而,当多个网格合并为一个大的网格时,只要合并后的网格的任何部分在视锥内,整个网格都会被渲染。为了优化这一点,你可以:

  • 分割大网格:将大网格分割成多个较小的部分,每个部分都有自己的SkinnedMeshRenderer。这样,每个部分可以独立进行视锥剔除。

2. 使用遮挡剔除(Occlusion Culling)

遮挡剔除是一种技术,用于跳过那些被其他物体遮挡的网格的渲染。Unity提供了内置的遮挡剔除系统,但它需要在场景中正确设置并进行预计算。对于动态合并的网格,你可以:

  • 动态调整遮挡剔除:根据合并网格的当前状态和位置动态更新遮挡剔除的数据。这可能需要编写额外的脚本来处理遮挡剔除的区域。

3. LOD(Level of Detail)系统

使用LOD系统可以根据摄像机与网格的距离来调整网格的复杂度。对于合并的网格,可以:

  • 实现多级LOD:为合并的网格创建不同级别的细节,当网格远离摄像机时,使用更低细节的模型。这不仅可以减少渲染的复杂性,还可以帮助减少因大网格导致的不必要渲染。

4. 手动剔除

在某些情况下,可能需要更精细的控制渲染过程。可以:

  • 编写自定义剔除逻辑:根据游戏的具体需求,实现自定义的剔除逻辑。例如,可以根据角色的状态、环境条件或其他游戏逻辑来决定是否渲染某个网格。

5. 利用GPU实例化

如果合并的网格使用相同的材质,但是由于某些原因需要保持分开(例如为了更好地利用视锥剔除),可以考虑使用GPU实例化来减少渲染开销:

  • 使用GPU Instancing:Unity支持GPU Instancing,这可以在渲染多个相同网格时显著减少CPU到GPU的绘制调用。

实施建议

在实施这些优化策略时,建议使用Unity的Profiler工具来监测每种方法对性能的具体影响。这样可以根据实际的性能数据来选择最合适的优化策略。每个项目的需求可能不同,因此最佳实践是结合多种技术来达到最佳的性能和视觉效果平衡。

为了解决SkinnedMeshRenderer动态网格合并后的可见性问题,我们可以实现一个简单的C#脚本,该脚本将根据摄像机的视锥来动态控制合并网格的渲染。这里,我们将使用Unity的GeometryUtility.TestPlanesAABB方法来检查合并网格的边界框是否在摄像机的视锥内。

以下是一个基本的实现示例:

using UnityEngine;

public class MeshVisibilityController : MonoBehaviour
{
    public Camera mainCamera; // 主摄像机
    public SkinnedMeshRenderer combinedMeshRenderer; // 合并后的SkinnedMeshRenderer

    void Update()
    {
        CheckVisibility();
    }

    void CheckVisibility()
    {
        if (mainCamera == null || combinedMeshRenderer == null)
            return;

        // 获取摄像机的视锥平面
        Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);

        // 获取合并网格的边界框
        Bounds bounds = combinedMeshRenderer.bounds;

        // 检查边界框是否在视锥内
        bool isVisible = GeometryUtility.TestPlanesAABB(planes, bounds);

        // 根据可见性设置合并网格的渲染器启用状态
        combinedMeshRenderer.enabled = isVisible;
    }
}

代码解释:

  1. 变量定义

    • mainCamera: 这是用于视锥剔除检测的摄像机。
    • combinedMeshRenderer: 这是合并后的SkinnedMeshRenderer,我们将根据可见性来启用或禁用它。
  2. Update 方法

    • 每帧调用CheckVisibility方法来检查网格的可见性。
  3. CheckVisibility 方法

    • 使用GeometryUtility.CalculateFrustumPlanes获取摄像机的视锥平面。
    • 获取合并网格的边界框。
    • 使用GeometryUtility.TestPlanesAABB检查边界框是否至少与一个视锥平面相交。
    • 根据检查结果启用或禁用SkinnedMeshRenderer,从而控制网格的渲染。

使用说明:

  • 将此脚本附加到一个GameObject上,该GameObject应该代表或包含合并后的SkinnedMeshRenderer
  • 在Inspector中,将主摄像机和合并后的SkinnedMeshRenderer分配给脚本的相应字段。

注意事项:

  • 这种方法假设合并的网格不会频繁地改变其边界框。如果网格动态变化很大(例如,非常活跃的动画),可能需要更复杂的逻辑来更新边界框。
  • 对于大型场景或多个合并网格,考虑在不同的帧上分散边界框的检查,以避免单个帧的性能峰值。

这个简单的实现提供了一个基础,可以根据具体项目的需求进行调整和优化。

标签:SkinnedMeshRenderer,骨骼,网格,Skinning,Unity,GPU,优化,CPU
From: https://blog.csdn.net/qq_33060405/article/details/143278245

相关文章