Unity支持两种主要的Skinning技术
在Unity中,Skinning主要指的是角色的蒙皮过程,这是3D动画中的一个关键步骤,用于将3D模型的网格(皮肤)附着到骨骼上,使得模型可以根据骨骼的动作进行逼真的变形。Unity支持两种主要的Skinning技术:CPU Skinning 和 GPU 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();
}
}
说明:
- 初始化: 在
Start
方法中,我们获取原始网格和顶点,并为每个顶点准备变换后的数组。 - 更新骨骼矩阵: 在每个
Update
调用中,我们计算每个骨骼的变换矩阵,这是通过将骨骼的localToWorldMatrix
与网格的bindposes
相乘得到的。 - 应用蒙皮: 对于每个顶点,我们应用骨骼矩阵的变换。这里的示例仅考虑了单一骨骼的影响,但实际应用中,顶点可能受到多个骨骼的影响,需要加权平均这些影响。
- 更新网格: 最后,我们将变换后的顶点赋值回网格,并重新计算网格的边界。
这个示例是一个非常基础的实现,仅用于教学目的。在实际游戏开发中,直接在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);
}
}
解释
-
初始化: 在
Start
方法中,我们初始化一个足够大的Matrix4x4
数组来存储每个骨骼的变换矩阵,以及一个MaterialPropertyBlock
用于优化材质属性的设置。 -
更新骨骼矩阵: 在每个
Update
调用中,我们计算每个骨骼的最终变换矩阵。这是通过将骨骼的localToWorldMatrix
与网格的bindposes
相乘得到的。bindposes
数组包含了每个骨骼相对于网格的逆初始变换矩阵。 -
传递数据给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
}
}
}
解释
- Shader结构: 这个Shader包含了基本的纹理属性,并定义了一个简单的顶点和片段程序。
- 顶点数据:
appdata
结构体包含了顶点位置、法线、骨骼权重和骨骼索引。 - Skinning矩阵计算: 在顶点函数
vert
中,我们根据顶点的骨骼权重和骨骼索引计算出最终的Skinning矩阵。这个矩阵是通过将每个影响骨骼的变换矩阵与其对应的权重相乘,然后加总得到的。 - 顶点变换: 使用计算出的Skinning矩阵变换顶点位置。
- 纹理采样: 在片段着色器中,使用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
可能会遇到性能问题。以下是一些可能影响性能的关键方面:
-
CPU负载:
- 骨骼变换计算:每个骨骼的变换需要在CPU上计算,然后传递给GPU。如果角色数量多或骨骼数量多,这会显著增加CPU的负担。
- 蒙皮计算:在CPU Skinning模式下,顶点的蒙皮变换(即根据骨骼权重和变换矩阵调整顶点位置)是在CPU上进行的,这可能会非常消耗资源。
-
GPU负载:
- 顶点处理:GPU Skinning虽然减轻了CPU的负担,但增加了GPU的顶点处理工作。每个顶点可能需要根据多个骨骼权重进行多次变换,这在顶点数量很多的情况下会显著增加GPU的工作量。
- 材质和着色器复杂性:使用复杂的材质和着色器可以增强视觉效果,但也会增加GPU的负担。
-
内存和带宽:
- 骨骼矩阵传输:每帧更新并传输骨骼矩阵到GPU可以消耗大量的内存带宽。
- 网格数据:高多边形网格需要更多的内存来存储顶点数据,同时也需要更多的带宽来在CPU和GPU之间传输这些数据。
-
动画系统的复杂性:
- 动画层和混合:多层动画和动画混合可以创建复杂的动作和表情,但同时也增加了计算的复杂性和资源消耗。
- 动画状态机:复杂的动画状态机可能需要更多的计算资源来管理状态转换和同步。
-
批处理和渲染调用:
- 批处理优化:
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
的网格。
步骤
- 收集所有需要合并的
SkinnedMeshRenderer
组件。 - 创建一个新的网格,将所有原始网格的顶点、三角形、UV等数据合并到这个新网格中。
- 处理骨骼和权重:因为每个原始网格可能有不同的骨骼,需要创建一个统一的骨骼列表,并更新顶点权重,使之对应新的骨骼索引。
- 创建一个新的
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;
}
}
代码解释:
-
变量定义:
mainCamera
: 这是用于视锥剔除检测的摄像机。combinedMeshRenderer
: 这是合并后的SkinnedMeshRenderer
,我们将根据可见性来启用或禁用它。
-
Update
方法:- 每帧调用
CheckVisibility
方法来检查网格的可见性。
- 每帧调用
-
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