Unity 使用 ECS With Burst 来再次加速 你的GPUSkinning
一般来说使用GpuSkinning 已经能得到很不错的性能了,那么能不能再快一点呢?答案当然是肯定的,这一次我们来使用ECS榨干CPU的部分先上性能对比图1万个蒙皮角色,每个角色472面,带有uv0,uv1测试设备硬件 win10, Intel i7-7700, GPU GTX-1060 6G可以看到Entity的帧数在 110帧以上, 而传统GPUSkinning 的帧数在 29帧这个Demo使
·
一般来说使用GpuSkinning 已经能得到很不错的性能了,那么能不能再快一点呢?
答案当然是肯定的,这一次我们来使用ECS榨干CPU的部分
先上性能对比图
1万个蒙皮角色,每个角色472面,带有uv0,uv1
测试设备硬件 win10, Intel i7-7700, GPU GTX-1060 6G


可以看到Entity的帧数在 110帧以上, 而传统GPUSkinning 的帧数在 29帧
这个Demo使用的GPU蒙皮方案为 将骨骼矩阵数据以双四元数的方式存储在纹理上,具体实现方法不是这个Demo的重点,大家也可以参考这篇文章
接下来一步一步开始分解这个Demo
首先实现Shader Include
Skinning.hlsl
#ifndef __AOI_GPUSKINNING
#
define
__AOI_GPUSKINNING
TEXTURE2D
(
_AnimTex
)
;
SAMPLER
(
sampler_AnimTex
)
;
inline
float2
BoneIndexToTexUV
(
float
index
,
float4
param
)
{
int
row
=
(
int
)
(
index
/
param
.
y
)
;
int
col
=
index
%
param
.
x
;
return
float2
(
col
*
param
.
w
,
row
*
param
.
w
)
;
}
inline
float3
QuatMulPos
(
float4
rotation
,
float3
rhs
)
{
float3
qVec
=
half3
(
rotation
.
xyz
)
;
float3
c1
=
cross
(
qVec
,
rhs
)
;
float3
c2
=
cross
(
qVec
,
c1
)
;
return
rhs
+
2
*
(
c1
*
rotation
.
w
+
c2
)
;
}
inline
float3
QuatMulPos
(
float4
real
,
float4
dual
,
float4
rhs
)
{
return
dual
.
xyz
*
rhs
.
w
+
QuatMulPos
(
real
,
rhs
.
xyz
)
;
}
inline
float4
DQTexSkinning
(
float4
vertex
,
float4
texcoord
,
float4
startData
,
Texture2D
<
float4
>
animTex
,
SamplerState
animTexSample
)
{
int
index1
=
startData
.
z
+
texcoord
.
x
;
float4
boneDataReal1
=
SAMPLE_TEXTURE2D_LOD
(
animTex
,
animTexSample
,
BoneIndexToTexUV
(
index1
,
startData
)
,
0
)
;
float4
boneDataDual1
=
SAMPLE_TEXTURE2D_LOD
(
animTex
,
animTexSample
,
BoneIndexToTexUV
(
index1
+
1
,
startData
)
,
0
)
;
float4
real1
=
boneDataReal1
.
rgba
;
float4
dual1
=
boneDataDual1
.
rgba
;
int
index2
=
startData
.
z
+
texcoord
.
z
;
float4
boneDataReal2
=
SAMPLE_TEXTURE2D_LOD
(
animTex
,
animTexSample
,
BoneIndexToTexUV
(
index2
,
startData
)
,
0
)
;
float4
boneDataDual2
=
SAMPLE_TEXTURE2D_LOD
(
animTex
,
animTexSample
,
BoneIndexToTexUV
(
index2
+
1
,
startData
)
,
0
)
;
float4
real2
=
boneDataReal2
.
rgba
;
float4
dual2
=
boneDataDual2
.
rgba
;
float3
position
=
(
dual1
.
xyz
*
vertex
.
w
)
+
QuatMulPos
(
real1
,
vertex
.
xyz
)
;
float4
t0
=
float4
(
position
,
vertex
.
w
)
;
position
=
(
dual2
.
xyz
*
vertex
.
w
)
+
QuatMulPos
(
real2
,
vertex
.
xyz
)
;
float4
t1
=
float4
(
position
,
vertex
.
w
)
;
return
t0
*
texcoord
.
y
+
t1
*
texcoord
.
w
;
}
inline
void
SkinningTex_float
(
float4
positionOS
,
float4
texcoord
,
float4
frameData
,
Texture2D
<
float4
>
animTex
,
SamplerState
animTexSample
,
out
float4
output
)
{
output
=
float4
(
DQTexSkinning
(
positionOS
,
texcoord
,
frameData
,
animTex
,
animTexSample
)
.
xyz
,
1
)
;
}
#
endif
这个Shader中,路口函数为SkinningTex_float 需要传入 模型空间原始顶点,uv1(存储的顶点对应的受影响的骨骼ID和对应权重), FrameData当前动画的帧数所在的动作纹理中的像素偏移值信息,animTex蒙皮动作数据纹理,采样器,output蒙皮后输出到模型空间顶点
Include 写完后,可以正式开始编写Shader了,使用的是 HDRP 的ShaderGraph -> LitMaster

左侧 Properties 准备参数
_BaseMap 为角色Diffuse 贴图
_AnimTex 为角色蒙皮动画数据贴图
_FrameData 为当前动画的帧数所在的动作纹理中的像素偏移值信息
_ECS_FrameData 为ECS模式下当前动画的帧数所在的动作纹理中的像素偏移值信息,区别在于勾选了 Hybird Instanced
_ECS_ON 在ECS模式下激活的宏 展开的结构如图

该节点就是讲之前编写的 Skinning.hlsl 以节点的方式添加到ShaderGraph 中使用
分别是之前介绍的 4个 输入,和一个输出
SkinningTex_float(float4 positionOS, float4 texcoord, float4 frameData, Texture2D<float4> animTex, SamplerState animTexSample, out float4 output)

使用 KeyWordNode 来switch ECS 和 非ECS模式下的_FrameData 数据输入来源
ShaderGraph的流程就只有这些节点,较为简单。
ECS实现
正常来说Unity会自动转换 MeshRenderer , SkinnedMeshRenderer 为Entity可用的格式
但是这个Demo中使用了自定义的GPUSkinning 方式,因此需要自己编写一套转换工具 编写 ECS_AnimatorConvertToEntity

Entity的结构如图,本Demo中的角色为拆分为马匹和士兵的2个模型,因此有2个部分,而每个部分各有一个低模,共4个部分,
最后加上一个4个部分共用的父级挂载Animator,MeshLODGroupComponent和动画信息,因此共有5个Entity来表达一个模型逻辑
/// <summary>
/// 生成结构为
/// Primary Entity (为ECS_SkinnedMatrixAnimator,MeshLODGroupComponent组件所在)
/// |-Attach0
/// | |-LOD0 (RenderMesh, RenderBound, MeshLODComponent)
/// | |-LOD1
/// |
/// |-Attach1
/// | |-LOD0
/// | |-LOD1
/// </summary>
编写 ECS_AnimatorEntitySpawnerSystem

这部分是将各个部件组合,同时记得添加 ECS_FrameDataMaterialPropertyComponent 组件,这是ECS中使用MaterialPropertyBlock的方式
ECS_FrameDataMaterialPropertyComponent.cs 内容
using
Unity
.
Rendering
;
using
Unity
.
Entities
;
using
Unity
.
Mathematics
;
[
MaterialProperty
(
"_ECS_FrameData"
,
MaterialPropertyFormat
.
Float4
)
]
public
struct
ECS_FrameDataMaterialPropertyComponent
:
IComponentData
{
public
float4
Value
;
}
其中 MaterialProperty("_ECS_FrameData") 名字要对应上ShaderGraph 中属性定义Reference

最后是ECS GpuSkinning的渲染部分

将计算好的帧数对应的纹理偏移值传给 ECS_FrameDataMaterialPropertyComponent 组件即可
最后运行的FrameDebuger 比较 一万个模型,为5万个Entity

一万个模型下,为6个Srp Batch, 这里要注意 Srp Batch != Instancing Batch Count


一万个模型下, 常规的GPUSkinning 有48个批次
最后是整个项目的源码 github https://github.com/dreamfairy/Unity_ECS_GPUSkinning
最后欢迎关注我的博客 http://www.dreamfairy.cn
更多推荐
所有评论(0)