一般来说使用GpuSkinning 已经能得到很不错的性能了,那么能不能再快一点呢?
答案当然是肯定的,这一次我们来使用ECS榨干CPU的部分

先上性能对比图
1万个蒙皮角色,每个角色472面,带有uv0,uv1
测试设备硬件 win10, Intel i7-7700, GPU GTX-1060 6G

可以看到Entity的帧数在 110帧以上, 而传统GPUSkinning 的帧数在 29帧

这个Demo使用的GPU蒙皮方案为 将骨骼矩阵数据以双四元数的方式存储在纹理上,具体实现方法不是这个Demo的重点,大家也可以参考这篇文章

GPU Skinning 加速骨骼动画
https://github.com/chengkehan/GPUSkinning

接下来一步一步开始分解这个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 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 animTex, SamplerState animTexSample, out float4 output) {
output = float4(DQTexSkinning(positionOS, texcoord, frameData, animTex, animTexSample).xyz,1);
}

#endif

Continue reading

一个使用ECS框架,制定Agent移动, 最后使用倒播功能还原路径的功能演示。

20180502 Unity 2018.1 正式版出来了, 带来了内置的ECS和Job System。 在UnityECS中, Entity对应的是GameObject, Component对应的是MonoBehavior, 而System对应的是ComponentSystem。
不过在写这篇文章的测试demo时使用的还是GitHub上的版本, 两者之间仅有API的名称上的区别。
GitHub地址:https://github.com/sschmid/Entitas-CSharp

安装:
版本 1.52
https://github.com/sschmid/Entitas-CSharp/releases

1.使用已编译的版本 https://github.com/sschmid/Entitas-CSharp/releases/download/1.5.2/Entitas.zip
直接将文件解压到Assets目录下即可

2.使用源码安装 git clone https://github.com/sschmid/Entitas-CSharp.git
1.将Libraries/Dependencies 里的文件全部拷贝到 Plugins 目录下
2.将Entitas 和 Addones 目录拷贝到Assets 目录下
3.随便打开一个C#文件,让Unity生成项目文件后,就可以在工具栏启动 Tools/Entitas/Perfererces
4.如果在第三步出错了,那是因为源码文件中 EntitasResources.cs 取资源目录里的文件Version.txt 失败了, 这是由于Unity生成的dll文件不会包含资源,因此要么修改这个函数直接返回版本号(不取Version),要么直接使用官方已经编译好的Entitas.dll

ECS架构概述

ECS架构看起来就是这样子的。先有个World,它是系统(译注,这里的系统指的是ECS中的S,不是一般意义上的系统,为了方便阅读,下文统称System)和实体(Entity)的集合。而实体就是一个ID,这个ID对应了组件(Component)的集合。组件用来存储游戏状态并且没有任何的行为(Behavior)。System有行为但是没有状态。

这听起来可能挺让人惊讶的,因为组件没有函数而System没有任何字段。

from:http://gad.qq.com/article/detail/28682

ECS框架其实在许多年前就已经诞生了,这几年名声大噪源于GDC2017守望先锋的一次技术分享,ECS的理念是组合模式优于面向对象模式,ECS解决的问题是之前OOP框架开发时状态和行为混合,在对象功能非常庞大时维护的成本很高。
ECS的全称是 Entity , Component, System. 其中Entity是对象实体, Component维护了对象的所有状态, 而System则是利用对象身上的Compoent实现各种行为, 在实际编程发现这种编程方式非常类似行为树的构架,开发过行为树逻辑的同学非常熟悉开发模式就是讲各种逻辑进行拆分,分散一个个处理细节的函数,使这些函数可以被行为树任意组合复用。

C#版的ECS利用了许多C#的特性,比如分散类 partical. ECS中Component 实际上是把OOP中的成员属性变成一个个单独的partical class进行拆分,但是本质上组合后还是属于他的成员。
举个例子, 在OOP中一个玩家包含了生命和移动速度的属性 Like this
[cc]
public class Player {
public uint HP;
public uint MoveSpeed;
}
[/cc]

而在ECS构架中这个类会变成这样
[cc lang=”C#”]
HPComponent.cs

public partial class Player{
public HPComponent hpComponent;
}

MoveSpeedComponent.cs
public partial class Player{
public MoveSpeedComponent moveSpeedComponent
}
[/cc]
这里我为什么要把类名标识出来, 这是因为对于程序员开发来说,当你要修改Player的某个属性时,只要关注属性类即可, 不需要去Player.cs中大海捞针找属性在哪,这是一种开发上的快捷。 而对于编辑器来说,这些类都都是partial 聚合类, 本质上都是Player.cs 的一部分与OOP无差。

Continue reading