Unity DOTS《群体战斗弹幕游戏》核心技术分析之3D角色动画

  最近DOTS发布了正式的版本, 我们来分享现在流行基于群体战斗的弹幕类游戏,实现的核心原理。今天给大家介绍大规模战斗群体3D角色的动画如何来实现。

DOTS 对角色动画支持的局限性

  截止到Unity DOTS发布的版本1.0.16,目前还是无法很好的支持3D角色动画。在DOTS 的baker过程种,不支持常见的动画组件,包括: Animation组件,基于状态机的Animator, 以及人形动画。同时DOTS在节点Baker成Entity的过程种只支持MeshRenderer组件,不支持SkinnedMeshRenderer组件(SkinnedMeshRenderer组件很多转换过来后显示效果不正确)。所以DOTS要支持3D角色动画就变得非常的麻烦。目前主流的方案就两个:

  1. 3D帧动画模式: 角色创作的时候使用MeshRender+材质不带动画,然后将动画组件每帧每个顶点的采样数据Bake到一张动画纹理,将这个纹理关联到材质里面。这样当物体Baker的时候,就可以正常地将MeshRender+材质转成Entity。在Shader中从动画纹理里读取动画每一帧,每个节点的模型坐标,再传递给渲染流程,把动画渲染出来。这样Entity就可以支持动画了。

  2. 将SkinnedMeshRender +动画做成ECS模式,然后Baker出来,这种Unity目前没有直接开放出来,有一些第三方的插件可以支持。

  总之,如果要用DOTS基于ECS来播放3D角色动画,目前Unity DOTS版本(1.0.16)是不直接支持的。

《群体战斗弹幕游戏》应该使用哪种动画系统

  《群体战斗弹幕游戏》这类游戏应该使用哪种动画系统会比较好呢?我们来分析一下项目需求,群体战斗有大量的战斗单元在一个屏幕,这些战斗单元对单个的渲染质量要求不高,但是数量巨大导致非常消耗性能。渲染质量要求不高,这样我们的战斗单元的模型的面数可以尽量的低,这样顶点数不会特别多,同时动画也不会很复杂,帧数也可以比较少。总结一下需求:单个模型顶点面数少,动画简单不复杂,节点的数目很多。

  按照以上的需求,我们在群体战斗的游戏中选择《3D帧动画》模式来做大规模的角色动画。这个方式有几个好处,动画预先被计算生成到纹理里面,这样代替原来的动画采样计算,空间换时间节省了性能,同时面数少,动画简单,这样动画纹理的内存占用可以接受。针对《弹幕类游戏》有大量的同一种类型的物体,使用MeshRenderer+材质+动画纹理的模式,能最大限度地使用GPU Instancing合批,降低节约DrawCall。

基于帧动画的3D角色动画如何实现

  基于帧动画的3D角色动画如何实现呢?结合弹幕游戏的3D角色动画简单的特点,分成以下步骤来具体实现:

(1)基于URP来编写一个最简单的Diffuse Shader,渲染没有光照计算,阴影计算,直接将模型纹理贴到模型表面;

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

// Includes
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl"

struct appdata
{
  float2 uv : TEXCOORD0;
  float4 pos : POSITION;
  UNITY_VERTEX_INPUT_INSTANCE_ID
};

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


CBUFFER_START(UnityPerMaterial)
  sampler2D _MainTex;
  float4 _MainTex_ST;

  // float _AnimLen;
  // sampler2D _AnimMap;
  // float4 _AnimMap_TexelSize;//x == 1/width
CBUFFER_END

float4 ObjectToClipPos(float3 pos)
{
  return mul(UNITY_MATRIX_VP, mul(UNITY_MATRIX_M, float4 (pos,1)));
}

v2f vert(appdata v, uint vid : SV_VertexID)
{
  UNITY_SETUP_INSTANCE_ID(v);


  v2f o;
  o.uv = TRANSFORM_TEX(v.uv, _MainTex);
  o.vertex = ObjectToClipPos(v.pos);
  return o;
}

float4 frag(v2f i) : SV_Target
{
  float4 col = tex2D(_MainTex, i.uv);
  return col;
}
ENDHLSL

(2)扩展Diffuse Shader,添加anim功能,增加uniform 来接受动画纹理,包括动画纹理对象(sampler2D),动画的时间长度(float);

v2f vert(appdata v, uint vid : SV_VertexID)
{
  UNITY_SETUP_INSTANCE_ID(v);

  float f = _Time.y / _AnimLen;

  fmod(f, 1.0);

  float animMap_x = (vid + 0.5) * _AnimMap_TexelSize.x;
  float animMap_y = f;

  float4 pos = tex2Dlod(_AnimMap, float4(animMap_x, animMap_y, 0, 0));

  v2f o;
  o.uv = TRANSFORM_TEX(v.uv, _MainTex);
  o.vertex = ObjectToClipPos(pos);
  return o;
}

(3)基于简单的Diffuse Shader+传统的Animation组件做好普通的预制体节点给AnimBaker工具来使用;

(4)编写一个工具,将普通3D角色动画的预制体节点中的动画进行采样,把数据保存到动画纹理,并创建角色Mesh+材质(Difuse Anim Shader + Anim纹理+Anim时间)的预制体;

(5)将生成好的预制体放到DOTS中的subscene来进行Bake,Bake成Entity,这样角色就可以带动画了。


  今天的分享就到这里,需要本篇文章完整的项目工具与源码的同学可以 + 企.鹅.裙 428 540 563


  下面是DOTS的VIP课程前18节视频,免费观看

Unity DOTS进阶与项目实战(B站18集)

第001课DOTS的环境安装与准备事项

第002课 DOTS的核心机制与概述

第003课DOTS的SubScene

第004课Component的概述与普通组件的Baker

第005课System与SystemGroup概述

第006课DOTS中的ECS核心概念总结

第007课Baking系列之Baking与Baker详解

第008课Baking系列之BakingSystem与BakingWorld详解

第009课FilterBakingOutput与PrefabsInBaking

第010课BlobAsset核心机制分析

第011课Aspect核心机制分析

第012课 StructChange核心机制详解

第013课Managed与Unmanaged Component详解与性能分析

第014课ShareComponent核心机制与性能分析

第015课CleanupComponent核心分析

第016课 Dynamic Buffer Component详解与分析

第017课Tag与Chunk Component详解与分析

第018课Enableable与Singleton组件详解与分析

上一篇:基于ES-EKF的LiDAR/GNSS/IMU传感器融合轨迹估计(附项目源码)


下一篇:3D可视化技术亮相高铁站,引领智慧出行新潮流