【Unity万人同屏插件】使用手册 保姆级教程 GPU动画 Jobs多线程渲染

【万人同屏插件】

基于Dots技术,高性能实现3D、2D Spine渲染、海量单位锁敌/碰撞检测。同时通过自定义BRG渲染器绕过对Entities包的依赖,也就是不用写ECS代码即可拥有Entities Graphics的高性能渲染,使用传统开发方式BRG接管Renderer组件。

可以大幅提升PC或移动平台的游戏帧数,GPU动画同样支持WebGL,对于开发小游戏也有着巨大性能提升。同时,转换为GPU动画后可以大幅减少动画文件大小,可用作包体大小优化。

适用于海量单位游戏项目突破技术门槛,同时也适用于任何需要做性能优化的项目。例如:割草、类吸血鬼、RTS、SLG、弹幕游戏,以及海量树木、植被、建筑渲染等。

Dots正式版发布于Unity2022.3 LTS版,因此强烈建议使用Unity 2022.3.x及以上版本,插件同样支持最新版Unity 6.

功能/性能测试Demo下载

https://pan.baidu.com/s/1ML0DC8s0RkkTAraN9maltw?pwd=blueicon-default.png?t=O83Ahttps://pan.baidu.com/s/1ML0DC8s0RkkTAraN9maltw?pwd=blue

获取【万人同屏插件】

首页-游戏开发资源商店-淘宝网PC店铺icon-default.png?t=O83Ahttps://shop106471535.taobao.com/

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

测试环境

系统:Windows 11

Unity:Unity 2022.3.45f1

CPU: i7-13700KF

GPU: RTX 3070 8G

GPU动画插件(支持3D\2D Spine):

兼容性

纯Shader实现的GPU动画,超高性能。插件支持Unity全平台,兼容WebGL,可用于H5/小游戏开发。支持动画事件、保留骨骼信息(动态获得骨骼节点位置/旋转/缩放)、2D Spine半透物体渲染顺序、动画平滑融合过渡(Pro)、LOD生成(Pro)。

实现原理

由于Animator或Spine动画都是CPU计算,因此存在性能瓶颈、并且蒙皮动画无法合批渲染,DrawCall大幅降低帧数(以性能测试demo为例,1w个Animator仅有8 fps)。

GPU动画是把Animator转换为GPU计算, Shader中播放动画。使用MeshRenderer或BRG渲染,自动合批渲染,大幅降低DrawCall。以性能测试demo为例, 1w个GPU动画117 fps; 而使用BRG渲染GPU动画,帧数高达800 fps

一、3D动画转GPU动画

1. GPU动画转换工具界面

通过Unity顶部菜单Game Framework->GPU Animation->GPU Animation Converter打开工具界面

 GPU动画转换工具面板:

①拖入需要转换的Animator/Animation预制体;

②动画模式:支持GPU骨骼动画和顶点动画;推荐骨骼动画,功能更强大;

③合并网格:合并Mesh降低DC, 对于Spine动画将进行排序,确保半透物体渲染排序正确;仅支持共用相同材质的网格合并,对于不同材质、多张贴图的物体,可通过三方插件Mesh Baker先合并材质/贴图,再转换GPU动画。

④Animator相关设置,默认值即可。 如是否冻结根骨骼位置/旋转等;

⑤选择GPU动画shader, Spine动画要选择2D shader;也可生成GPU动画后再切换材质shader;

⑥需要烘焙到GPU动画贴图的Animation Clips 工具默认添加Animator中引用的所有Animation Clip;通过列表的索引值切换动画状态;

⑦把需要运行时获取transform值的骨骼节点拖到此处 转换为GPU动画后仍能实时获取到骨骼transform值;  此功能可使转换为GPU动画后骨骼信息不丢失,比如可以实时获取头部、手部位置;

2. GPU动画工具用法

①把Animator或Animation动画角色预制体拖入场景;

②把拖入场景的预制体拖入GPU动画转换工具的Input栏;

③拖动Animation Clips列表元素,组织好自己的动画索引对应的动画(可选);

④把需要保留骨骼transform信息的节点拖到Record Bones列表;

⑤点击Convert按钮,转换为GPU动画;

3. GPU动画资源

转换后会生成以下GPU动画文件:

①动画贴图。记录着动画数据;

②GPU动画Prefab。将原动画Prefab中的SkinnedMeshRenderer转换为了MeshRenderer,同时拥有动画播放功能和自动合批渲染;

③GPU动画帧事件,用于GPU动画播放时触发动画事件。工具默认会把Animation Clip文件中添加Event事件转换为GPU动画帧事件;

④GPU动画材质,由于是纯shader实现,因此可以不依赖任何脚本,仅修改材质属性切换动画;

⑤GPU动画Mesh,Mesh中寄存了某些信息,以便GPU动画Shader读取使用;

4. 自定义GPU动画Shader 

 插件使用Shader GraphAmplify Shader Editor封装好了函数节点,只需添加GPU动画节点连接顶点位置、顶点法线即可轻松实现自定义Shader

使用Amplify Shader Editor
使用Shader Graph

对于使用BRG渲染需要向Shader传递参数的情况:

由于每个渲染节点在GPU中都有一块连续的内存,以高效访问。对于自定义Shader添加的每个渲染物体实例需要单独设置的shader字段,需要修改BatchRendererComponent代码,参考_ClipId进行扩展参数。

例如:新增一个Vector4类型作为预留参数以供传递数据:

0. 重新计算单个渲染物体占用内存:

由于新增了一个Vector4字段,因此内存新增了4个float大小,需修改BRGUtility.kBytesPerInstance大小:

每个渲染物占GPU内存:两个4x3矩阵 + BaseColor(4个float) + ClipId(4个float) + 新增Vector4字段(4个float):

public static readonly int kBytesPerInstance = kSizeOfPackedMatrix * 2 + kSizeOfFloat4 * 3;

1. 定义Shader Property字段名:

int objectToWorldID = Shader.PropertyToID("unity_ObjectToWorld");
int worldToObjectID = Shader.PropertyToID("unity_WorldToObject");
int colorID = Shader.PropertyToID("_BaseColor");
int clipIdID = Shader.PropertyToID("_ClipId");
int userVec4ID = Shader.PropertyToID("_UserdataVec4");//新增vector4字段

2. 计算新增字段的起始内存:

...
int clipIdIndex = colorIndex + batchInstanceCount;
int userdataVec4Index = clipIdIndex + batchInstanceCount;
var drawBatch = new SrpBatch
{
    ...
    clipIdIdx = clipIdIndex,
    userdataVec4Idx = userdataVec4Index
};

3. 重新计算metadata地址:

if (UseConstantBuffer)
{
    ...
    metadataValues[3] = CreateMetadataValue(clipIdID, (clipIdIndex - offsetInFloat4) * kSizeOfFloat4, true);
    metadataValues[4] = CreateMetadataValue(userVec4ID, (userdataVec4Index - offsetInFloat4) * kSizeOfFloat4, true);
}
else
{
    ...
    metadataValues[3] = CreateMetadataValue(clipIdID, clipIdIndex * kSizeOfFloat4, true);
    metadataValues[4] = CreateMetadataValue(userVec4ID, userdataVec4Index * kSizeOfFloat4, true);
}

4. 新增字段上传GPU:

修改UploadParallelJob代码,Ouput数据追加一行:

Output[srpBatch.userdataVec4Idx + j] = renderer.userdataVec4;

二、2D Spine转GPU动画

实际上,只需要把Spine转换为Animator,就可以通过标题一的步骤把Animator转换为GPU动画

2d spine动画帧数提升数十倍?spine转gpu动画 2d spine动画 10w单位

1. Spine转Animator工具

①通过Unity顶部菜单栏 GameFramework->GPU Animation->Spine to Animator打开工具

②把Spine文件拖到工具,点击Bake按钮开始转换

2. Spine转GPU动画

 使用GPU Animation Converter把步骤1 Spine转换出的Animator预制体转换为GPU动画。

 注意:Spine通常是2D半透明物体,因此需要把GPU动画Shader切换为2D的Shader(如:GPUAnimation/2D/GPUBonesAnim2D)

三、GPU Animation LOD工具 [PRO版功能]

1. 自动生成减面LODs

根据LOD 0 Mesh生成LOD 1~4简化Mesh; 

①此工具依赖三方插件MantisLOD,用于自动简化Mesh面数,同时尽量保留Mesh轮廓。安装MantisLOD后,解除代码GPUAnimationLODCreator.cs第一行注释即可使用功能:

#define ENABLE_MANTISLOD //安装MantisLOD插件后解除此行注释
...
#if ENABLE_MANTISLOD
using MantisLOD;
using MantisLODEditor;
#endif

②GameFramework->GPU Animation->GPU Animation LOD打开工具

2. 使用已有LOD Mesh生成(可选)

除了使用工具自动生成减面LOD Mesh, 当然也可以根据已有的Mesh直接生成LODs Mesh

四、武器挂载、动画事件、骨骼获取

武器挂载 帧事件 骨骼获取 视频教程

1. 武器挂载

①把武器作为GPU动画角色的子节点,修改材质shader为GPUAnimation/GPUAttachBoneLit,并将主角的动画贴图赋值到材质;

②通过修改武器材质的AttachBoneIndex来设置武器绑定到哪个骨骼

说明: 这里AttachBoneIndex对应的就是GPU动画工具界面RecordBones记录的骨骼节点索引

2. 动画事件

 转换GPU动画时会自动生成动画事件文件,如下图:

 默认情况下,工具是从Animation Clip文件读取动画事件,以key, value形式保存。key是触发帧,value是事件名。用户可直接修改此文件增删事件。

①. GPU动画帧事件用法:

使用GPUAnimation脚本,设置好Event Data:

 GPU动画事件实例代码:

using Cysharp.Threading.Tasks;
using GPUAnimation.Runtime;
using UnityEngine;

public class GPUAnimEventDemo : MonoBehaviour
{
    GPUAnimation.Runtime.GPUAnimation m_GPUAnim;

    private void Start()
    {
        m_GPUAnim = GetComponent<GPUAnimation.Runtime.GPUAnimation>();

        //添加GPU动画事件监听
        m_GPUAnim.Events.AddListener(OnGPUAnimEventCallback);

        m_GPUAnim.GPUAnimMaterial.SetVector("_ClipId", new Vector4(4, Time.time, 0, 0));
    }
    private void OnDestroy()
    {
        m_GPUAnim.Events.RemoveListener(OnGPUAnimEventCallback);
    }
    /// <summary>
    /// GPU动画事件回调
    /// </summary>
    /// <param name="clipIndex">动画index</param>
    /// <param name="frame">触发帧</param>
    /// <param name="eventName">事件名</param>
    private void OnGPUAnimEventCallback(int clipIndex, int frame, string eventName)
    {
        if (eventName == "DamageTrigger") //挥拳动画事件触发时在拳头位置发射小球
        {
            var boneData = GPUAnimationUtility.GetAttachBoneTransform(m_GPUAnim.GPUAnimMaterial, 1);//获取右手当前动画帧的位置
            GameObject ball = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            ball.transform.localScale = Vector3.one * 0.2f;
            ball.transform.position = m_GPUAnim.transform.position + boneData.Position;

            _ = UniTask.Delay(1000).ContinueWith(() =>
            {
                Destroy(ball);
            });
            
        }
    }
}

②. BRG多线程渲染下GPU动画帧事件用法

为了应对海量单位下动画事件触发依然流畅,BRG组件专门提供了jobs多线程实现的事件触发检测函数:

public bool TryTriggerGPUAnimationEvents(out NativeQueue<GPUAnimTriggerInfo> triggerResults)

 代码示例:

 在Update中每帧调用,以批量检测动画事件触发

/// <summary>
/// 查询已经触发的GPU动画事件
/// </summary>
private void GPUAnimTriggerUpdate()
{
    if (BatchRendererComponent.Instance.TryTriggerGPUAnimationEvents(out var triggerResults))
    {
        while (triggerResults.TryDequeue(out var result))
        {
            if (m_Players.TryGetValue(result.AgentId, out BRG_Player unit))
            {
                //触发某个单位的GPU动画事件
                unit.OnGPUAnimationEvent(result.ResIndex, result.ClipIndex, result.TriggerAtFrame);
            }
        }
        triggerResults.Dispose();
    }
}

3. 骨骼获取

用于GPU动画运行时,实时获取某个骨骼节点的Transform信息;例如,角色持枪射击,子弹应从枪口位置创建发射(市面上其它GPU动画插件,转换GPU动画后骨骼信息全部丢失,无法获取枪口位置)。

GPUAnimationUtility类提供了多种获取当前GPU动画帧某个骨骼的transform信息的接口

例如,获取RecordBones记录的索引为1的骨骼transform信息:

GPUAnimationUtility.GetAttachBoneTransform(GPUAnimMaterial, 1);

 BRG多线程渲染接口同样提供了封装好的接口:

BatchRendererComponent.Instance.GetAnimationAttachBone(m_RendererNodeId, boneIndex)

五、动画平滑过渡、融合 [PRO版功能]

直接把GPU动画材质的Shader切换为支持平滑过渡的Shader即可,如:GPUAnimation/GPUBonesAnimMergeLit

ClipId:该参数用于切换动画,对于不支持动画融合的Shader, x是动画index, y是开始播放的时间(Time.time);

对于支持动画融合的Shader,z是上个动画的index,w是上个动画的播放时间,x是要切换到的动画index,y是动画第一帧开始的时间,也就是不算融合时间(Time.time + 动画过渡消耗时间)

AnimTransDration:动画平滑过渡(融合)速度

GPU动画使用限制

尽管已经实现大量GPU动画功能,但依然比不上高级动画系统的功能,如IK、Avatar Mask是不支持的。

由于GPU动画贴图储存动画信息有限,目前支持记录Transform信息和GameObject的显示隐藏。对于3D动画完全足够。但对于2D Spine动画,有时会记录特殊的关键帧,如修改颜色、修改贴图等,这些GPU动画是不支持的,但仍可以通过显示隐藏实现, 例如,皮肤A切换到皮肤B,可以通过隐藏皮肤A,显示皮肤B以实现换装功能。

BRG多线程渲染(BatchRenderComponent):

使用Entities Graphics的底层接口BatchRendererGroup,自己组织内存、显存,利用Dots技术栈的高性能,使用Jobs多线程提交渲染数据到GPU,通过合批一次渲染大量单位,从而巨大提升渲染性能。

渲染10万个3D模型, 帧数148 fps

性能表现 

使用BRG组件渲染1w个GPU动画人物,800 fps

使用MeshRenderer渲染1w个GPU动画人物,117 fps:

直接Animator(SkinnedMeshRenderer)渲染1w个GPU动画人物, 10 fps:

一、注册要渲染的物体

 首先要把需要用BRG组件渲染的物体,添加到RendererResources列表。

1. Mesh, Material:把GPU动画转换工具生成的GPU动画文件(材质、Mesh)添加到Renderer Resources列表;

2. Capacity:该物体最大渲染数量,为了追求极致性能,BRG将根据最大渲染数量分配一片定长内存,以避免渲染单位数量变化导致重新分配存储拷贝数据造成性能消耗。

3. GPU Anim Event Data: GPU动画事件文件, 若该物体无动画事件可不设置;

4. Lod Distances:x,y,z,w分别为LOD 0 ~ 4各个等级的距离;距离越近显示的模型面数越高。此功能需勾选Enable LOD; 

5. 开启LOD功能:勾选Enable LOD, 然后把GPU Animation LOD工具生成合并后的LODs Mesh作为RendererResource的Mesh即可;

二、BatchRendererComponent接口用法

1. 添加一个物体

m_RendererId = BatchRendererComponent.Instance.AddRenderer(resId, pos, rotation, scale);

 其中,resId就是Renderer Resources列表中注册的渲染物的索引index;

返回值为RendererNodeId,可通过RendererNodeId设置渲染物的属性,如切换GPU动画:

BatchRendererComponent.Instance.SetRendererClipId(m_RendererId, animIndex);

2. 移除一个物体

BatchRendererComponent.Instance.RemoveRenderer(m_RendererId);

RVO多线程避让

Unity Dots 10万人同屏RVO避障是一种什么体验? 3D人物带动画,不使用ECS

1. 添加RVO单位

AddAgent()会返回一个RVOAgent对象,可通过此对象设置各种rvo参数

m_RVO = RVOComponent.Instance.AddAgent(position);

2. 移除RVO单位

RVOComponent.Instance.RemoveAgent(m_RVO);

3. RVO属性功能 

avoidWeight:避让权重,比如高级兵不给小兵让路,小兵需要给高级兵让路,以避免高级兵被小兵挡住的情况。只需设置avoidWeight,取值0-1,值越小越不让路。

searchRadius:寻敌半径

searchCount:寻敌数量,==1,单体攻击;>1, 群体攻击

targetPosition:移动目标点

camp:单位所属阵营,数据类型是位枚举,所以支持单个单位多个阵营(以满足用户特殊需要)

searchTag:标记自己的标签,数据类型是位枚举,支持标记多个标签;此标签配合ignoreSearchTag使用,用于精准控制寻敌过滤目标;例如:敌方阵营有个飞行单位,我方阵营的近战单位虽然属于敌对阵营,但近战单位不可能攻击到高空飞行目标。

ignoreSearchTag:寻敌时忽略掉ignoreSearchTag位枚举中包含的searchTag,用于精准控制筛选寻敌

collisionEnabled:rvo单位之间是否碰撞,为false时rvo之间将会重叠穿模

navigationEnabled:rvo单位是否自动移动,设为false后,即可通过rvo的position来随意设置位置;例如:击飞效果,单位受到攻击后被击飞,就可以通过设置navigationEnabled为false,然后自行设置position实现击飞效果,落地后再设置navigationEnabled为true即可从击飞落地位置重新移动。

4. 障碍物

①添加圆形障碍物:把CircleObstacle脚本挂到障碍物GameObject上即可

②添加举行障碍物:把BoxObstacle脚本挂到障碍物GameObject上即可

③添加边界障碍物:把EdgeObstacle脚本挂到障碍物GameObject上即可,通过面板参数添加边界顶点位置

④扩展自定义障碍物:自定义类继承ShapeObstacleBase,重写方法即可

海量单位多线程寻敌/碰撞检测:

Unity弹幕游戏, RVO红蓝对抗 割草游戏 海量单位高性能索敌攻击方案压测

方案是通过Jobs多线程批量查询单位,支持查询最近单位、范围内单位等,通过RVOComponent提供的接口查询

具体使用方法可参考插件提供的demo, EnemiesSearchDemo.cs

1. 查询最近单位

SearchAgentsNearest:查询最近的单位

2. 查询范围内目标

SearchAgentsWithin:查询半径内的目标,返回数量为rvo的searchCount,也支持指定个数

3. 查询范围内多目标(AOE) 

var ids = rvo.SearchAgentsWithin(tempQueryPoints, damageRadius, maxDamageCount);
for (int i = 0; i < ids.Length; i += maxDamageCount)
{
    int indexOffset = i * maxDamageCount;
    for (int j = 0; j < maxDamageCount; j++)
    {
        int agentId = ids[indexOffset + j];
        if (agentId <= 0) break;

        if (m_Players.ContainsKey(agentId))
        {
            m_Players[agentId].ApplyDamage(100);
        }
    }
}

注意事项:

一、BRG跨平台兼容性

BRG兼容性可参考官方文档:Unity - Manual: Getting started with BatchRendererGroupicon-default.png?t=O83Ahttps://docs.unity3d.com/2022.3/Documentation/Manual/batch-renderer-group-getting-started.html

渲染管线:目前支持URP、HDRP、自定义SRP

兼容平台:目前不支持WebGL

  • Windows using DirectX 11
  • Windows using DirectX 12
  • Windows using Vulkan
  • Universal Windows Platform
  • Linux using Vulkan
  • macOS using Metal
  • iOS
  • Android (Vulkan and OpenGL ES 3.x)
  • PlayStation 4
  • PlayStation 5
  • Xbox One
  • Xbox Series X and Xbox Series S
  • Nintendo Switch
  • Google Stadia

二、打包设置

1. URP Rendering Path选择Forward+ (Forward+才能发挥BRG最大性能)

2. BatchRendererGroup Shader变体选择 Keep All

3. 打包后GPU Spine动画渲染顺序异常

BatchRendererComponent在Unity6下开启渲染排序(Sorting Position)后,编辑器下渲染顺序正常,打包后渲染顺序错误:

猜测这是BatchRendererGroup的bug,当项目只使用一个场景,没有任何场景切换时就会复现这个bug;

解决方法:使用两个场景,例如:场景A是空场景, 场景B是游戏场景,游戏开始后Additional方式加载场景B或者切换到场景B,这样渲染顺序就不会出现异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值