【Unity】渲染性能开挂GPU Animation, 动画渲染合批GPU Instance

本文详细介绍了Unity中实现GPU动画的原理和优化,通过烘焙动画到Texture2D,实现GPU驱动的顶点动画,大幅提高渲染性能。支持Animator烘焙和动画切换,适用于大规模动画物体场景。在不同动画系统间进行性能对比,GPU动画在MeshRenderer和BRG合批下分别达到135 fps和202 fps,显著优于传统方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 【万人同屏方案Pro】是以GPU动画为基础实现3D、2D Spine高性能渲染,同时支持GPU Instancing或DOTS Instancing。基于Dots技术栈(Jobs&Burst)实现高性能海量单位锁敌/碰撞检测。同时提供BRG和ECSGraphics两套高性能渲染器, 传统开发方式拥有使用DOTS的渲染性能。无ECS技术门槛,无需写ECS代码,UI程序员也能直接上手写出万人同屏项目。 

 插件获取&Demo下载

Unity万人同屏集成方案ProPC店铺https://shop106471535.taobao.com/

性能/功能/红蓝对抗测试Demohttps://pan.baidu.com/s/1ML0DC8s0RkkTAraN9maltw?pwd=blue

插件视频教程

【Unity万人同屏插件】使用手册 保姆级教程 GPU动画 Jobs多线程渲染_unity 万人同屏-CSDN博客文章浏览阅读7.5k次,点赞20次,收藏52次。《万人同屏插件》是一款基于Dots技术的高性能Unity开发工具,通过GPU动画渲染和BRG/ECSGraphics多线程渲染器,实现3D/2D Spine动画的海量单位渲染(10万单位)。支持武器挂载、动画事件、骨骼信息保留及LOD优化,GPU动画兼容全平台包括WebGL。插件提供一键转换工具将Animator/Spine动画转为GPU动画,大幅降低DrawCall和包体体积。新增ECSGraphics渲染器支持无限实例、8级LOD和动态挂载点切换。配套RVO多线程避障系统可实现大规模单位寻敌与碰撞检_unity 万人同屏 https://blog.csdn.net/final5788/article/details/142005465

【万人同屏插件】实战项目mu'b

gpu动画性能PK和插件使用视频教程:

Unity低成本性能开挂 gpu动画+dots graphics万人同屏渲染 jobs rvo2 弹幕游戏海量单位索敌

GPU骨骼动画的实现:【Unity】GPU骨骼动画 渲染性能开挂 动画合批渲染 支持武器挂载-CSDN博客

GPU Instance和SRP Batcher合批渲染只对静态MeshRenerer有效,对SkinMeshRenderer无效。蒙皮动画性能堪忧,对于海量动画物体怎么解决呢?针对这个问题,GPU Animation就是一个常见又简单的解决方案。

GPU动画实现原理:

实现原理也是简单粗暴,把每一帧动画时刻SkinMeshRenderer所有的顶点坐标写入到Texture2D, 贴图UV中,U按顶点顺序保存顶点坐标,V是第几帧,然后在顶点着色器中读取所有顶点的坐标,根据时间轮流在动画帧数区间从动画Texture2D采样,这样就实现了基于GPU的顶点动画。

优化前后性能对比:

分别使用Animator(新版动画系统)、Animation(旧版动画系统)、GPU动画、BRG + GPU动画,10000个动画单位全部在相机视口内播放相同动画的帧数做比较(测试结果根据顶点个数会有波动):

AnimatorAnimation

GPU动画

(MeshRenderer)

GPU动画

(Batch Renderer Group)

帧数910135202

1. Animator动画系统,9 fps:

2. Animation(旧版动画系统),10 fps:

3. GPU动画,使用MeshRenderer渲染组件 135 fps:

4. GPU动画,使用Batch Renderer Group合批渲染 202 fps:

 GPU动画功能实现:

GPU动画原理已经明确,首先第一步就是把Animation Clip动画每一帧的顶点烘焙到Texture2D中,推荐大家参考github目前star最多的gpu动画开源方案:https://github.com/chenjd/Render-Crowd-Of-Animated-Characters

 不过,此开源方案目前仅支持把旧版Animation Clip烘焙成贴图,不支持Animator动画,并且是每个Animation Clip烘焙成一张Texture文件,对于动画切换不是很友好。

作为一个设计开发工程师这样的工作流是难以忍受的,首先需要解决以下问题:

1. 支持Animator动画烘焙。

2. 更友好的动画切换,通过修改shader参数AnimIndex来切换不同动画,并且支持设置动画速度。

3. 自动把Animator中Animation Clips放入烘焙列表,以及其它用户体验优化功能。

4. 一键生成动画贴图资源、材质球、prefab预制体。

5. 为gpu动画封装一个Amplify Shader Editor函数节点,便于用于使用和扩展自定义shader.

 工具使用工作流:

1. 支持Animator动画烘焙:

Asset Store有现成的插件,可以将各种Animation类型humanoid ⇆ generic ⇆ legacy相互转换:Animation Converter | Animation Tools | Unity Asset Store

只需用插件将Animator的动画转换为Legacy Animation,然后就可以把Legacy Animating烘焙到贴图了;

AnimationConverter.Convert(animationClips, config, out string logMessage);

自动化生成带有Animation组件的prefab,并把转换生成的Legacy Animation Clips赋值到Animation组件:

private void TryAssignAnimationClips(string clipsDir, GameObject animationPrefab, IList<AnimationClip> clips)
    {
        var animation = animationPrefab.GetComponent<Animation>();
        AnimationClip[] newClips = new AnimationClip[clips.Count];
        for (int i = 0; i < clips.Count; i++)
        {
            var clipAsset = Path.Combine(clipsDir, $"{clips[i].name}.anim");
            newClips[i] = AssetDatabase.LoadAssetAtPath<AnimationClip>(clipAsset);
        }
        AnimationUtility.SetAnimationClips(animation, newClips);
        EditorUtility.SetDirty(animationPrefab);
    }

烘焙动画,根据Animation的time,每次采样间隔时间为:animClip.length / (Mathf.CeilToInt(animClip.frameRate * animClip.length)). 即个动画帧采样一次。将当前的SkinMeshRenderer通过BakeMesh得到一个当前动画帧Mesh,然后把Mesh的vertices坐标写入Texture2D。

2. 实现动画切换

 实现用index切换动画,只需创建一个Texture2DArray, 把每个Animation Clip生成的Texture2D写入Texture2DArray,就可以通过index来切换动画贴图了。需要注意的是,Texture2DArray中Texture2D的宽高必须与Texture2DArray宽高一样。也就是说Texture2DArray的宽高需为最大子贴图的宽和高。

对于宽高填充不满的贴图,用程序以Repeat方式填充像素即可,可以有效防止动画贴图采样超过有效区域导致的顶点抖动问题。

动画贴图FilterMode使用Bilinear,线性插值可以让顶点动画更加平滑;需要注意的是,由于Biliner进行了插值过渡,在使用VertexID在贴图U轴采样时需要+0.5偏移,取两个像素的中间值。

另外gpu动画shader中还需要访问每个动画的帧数和时间长度,由于gpu动画只用到了xyz,即像素的rgb通道,可以把动画的帧数和时间长度信息直接存入动画贴图的alpha通道,这样就能在Shader读取使用了。

把多个Animation Clip烘焙到Texture2DArray并保存:

public void BakeAnimation(GameObject animationPrefab, string outputDir)
    {
        Quaternion quaternion = Quaternion.Euler(m_FixRotation);
        var baker = new GPUAnimationBaker();
        baker.SetAnimData(animationPrefab, m_TexPowerOfTwo);
        var bakedDataArr = baker.Bake(m_FixScale, quaternion);
        if (bakedDataArr == null || bakedDataArr.Count < 1)
        {
            return;
        }
        var tex2d = bakedDataArr[0].RawAnimTex;
        Texture2DArray tex2DArray = new Texture2DArray(tex2d.width, tex2d.height, bakedDataArr.Count, tex2d.format, false);
        tex2DArray.filterMode = tex2d.filterMode;
        tex2DArray.wrapMode = tex2d.wrapMode;
        for (int i = 0; i < bakedDataArr.Count; i++)
        {
            var bakeDt = bakedDataArr[i];
            tex2DArray.SetPixelData(bakeDt.RawAnimMap, 0, i);
        }
        tex2DArray.Apply();
        string fileName = Path.Combine(outputDir, $"{animationPrefab.name}_anim_tex.asset");
        AssetDatabase.CreateAsset(tex2DArray, fileName);


        string meshFileName = Path.Combine(outputDir, $"{animationPrefab.name}_mesh.asset");
        var newMesh = Instantiate(baker.AnimBakeData.SkinMesh.sharedMesh);
        var points = newMesh.vertices;
        for (int i = 0; i < points.Length; i++)
        {
            var point = points[i];
            point.Scale(m_FixScale);
            points[i] = quaternion * point;
        }
        newMesh.vertices = points;
        newMesh.RecalculateBounds();
        newMesh.RecalculateNormals();
        newMesh.RecalculateTangents();
        AssetDatabase.CreateAsset(newMesh, meshFileName);

        var newPrefabName = $"{m_Config.Prefabs[0].SourcePrefab.name}_gpu_anim";
        var newPrefab = new GameObject(newPrefabName, typeof(MeshFilter), typeof(MeshRenderer));
        newPrefab.GetComponent<MeshFilter>().mesh = AssetDatabase.LoadAssetAtPath<Mesh>(meshFileName);
        var newMat = new Material(m_Shader);
        newMat.SetTexture("_AnimTexArr", AssetDatabase.LoadAssetAtPath<Texture2DArray>(fileName));
        newMat.SetFloat("_AnimMaxLen", baker.MaxAnimLength);
        var matFileName = Path.Combine(outputDir, $"{newPrefab.name}.mat");
        AssetDatabase.CreateAsset(newMat, matFileName);

        newPrefab.GetComponent<MeshRenderer>().material = AssetDatabase.LoadAssetAtPath<Material>(matFileName);
        PrefabUtility.SaveAsPrefabAsset(newPrefab, Path.Combine(outputDir, $"{newPrefabName}.prefab"));
        DestroyImmediate(newPrefab);
    }

3. GPU动画Shader实现:

本来想用Shader Graph和Amplify Shader Editor各实现一份便于使用。但是Shader Graph Bug太离谱了,遇到几次报错,Shader编辑器无响应,导致所有节点丢失。最离谱的是uint类型不能转换为float,VertexID(uint类型)与float值参与任何运算后,会出现数据类型匹配的情况下,节点却无法连接上。WTF?

果断换用Amplify Shader Editor,就一切正常了。实现如图,待到Shader Graph修复bug可以参考Shader节点移植到Shader Graph:

用法:

使用上图自定义函数GetGPUAnimVertex从动画贴图读取顶点坐标,直接赋值到Vertex Position即可;

总结: 

GPU Animation相比传统动画可以提升10 - 20多倍的性能,它是目前海量物体同屏的最佳方案,但不是完美的,如:不支持动画之间平滑过渡;没有骨骼,也就失去了Animator的所有高级功能;不过对于海量物体来说,通常不需要极致的细节。

### Unity GPU ECS 动画烘焙教程 #### 介绍 在现代游戏发中,优化性能是一个至关重要的方面。通过将动画计算卸载到GPU并利用ECS框架,可以显著提高效率和性能。这不仅减少了CPU的工作量,还能够更好地管理大量对象的动画播放。 #### 工具概述 `VertexAnimation` 是一个专门用于Unity DOTS/ECS环境下的顶点动画烘培工具集[^1]。此工具支持Shader中的动画系统以及高效的动画存储方式,非常适大规模场景的应用需求。 #### 设置项目结构 为了实现基于GPU与ECS的高效动画烘焙,在创建新工程时应启用DOTS模板,并安装必要的包如Hybrid Renderer等。确保项目的配置符最新的最佳实践标准。 #### 准备工作 - 安装所需的依赖项:包括但不限于`com.unity.entities`, `com.unity.rendering.hybird`以及其他可能需要的支持库。 - 配置RenderPipeline以适应自定义着色器的需求。 ```csharp // Example of adding required packages via manifest.json { "dependencies": { "com.unity.entities": "latest", "com.unity.rendering.hybrid": "latest" } } ``` #### 创建动画资源 对于要转换成GPU动画的对象,先准备好传统的FBX模型及其关联的动作文件。之后可以通过特定脚本或编辑器窗口将其转化为适GPU处理的形式。 #### 编写自定义Shader 编写适用于目标平台的HLSL代码片段,这些片段将在运行期间由GPU执行完成骨骼变换或其他类型的变形操作。注意保持良好的兼容性和可移植性。 ```hlsl // HLSL snippet for vertex shader performing skinning on the GPU side. float4 TransformPosition(float4 position : POSITION) : SV_POSITION { float4 worldPos = mul(position, unity_ObjectToWorld); // Apply bone transformations here... return mul(worldPos, unity_MatrixVP); } ``` #### 实现Baking逻辑 借助于C# Job System的强大功能来异步地预计算每一帧的数据变化情况,并最终打包为静态网格体供后续渲染阶段调用。整个过程中应当充分考虑到内存占用率及时效性的平衡问题。 ```csharp using Unity.Burst; using Unity.Collections; using Unity.Jobs; [BurstCompile] struct BakeJob : IJobParallelForTransform { public NativeArray<float3> Positions; public void Execute(int index) { // Perform baking operations per frame/per object instance. Positions[index] += new float3(0f, Mathf.Sin(Time.time), 0f); } } void Start() { var jobHandle = new BakeJob { /* Initialize members */ }.Schedule(transforms.Length, 64); jobHandle.Complete(); } ``` #### 测试与调整 构建简单的测试案例验证上述流程的有效性;同时监控各项指标(比如FPS),必要时候做出相应修改直至达到预期效果为止。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eFunTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值