Unity的GPUSkinning进一步介绍
大家好,我是阿赵。
在几年前,我曾经写过一篇介绍GPUSkinning的文章,这么多年之后,还是看到不停有朋友在翻看这篇旧文章。今天上去GitHub看了一下,GPUSkinning这个开源的插件已经很久没有更新过了,还是停留在2017年的0.2.3版本。GPUSkinning的魅力在于可以在消耗比较低的情况下同屏显示很多个蒙皮动画的角色。
看了一下之前写的文章,当时的我,水平也比较有限,所以只是简单的介绍了一下这个插件的用法。这么多年过去了,我感觉可以更深入的讨论一下这个插件的用法,还有它的实现原理。
一、使用说明和原理介绍
1、下载和安装
由于最近上GitHub似乎有些困难,所以我这里再提供多一个能下载的地址:
https://kgithub.com/chengkehan/GPUSkinning/
把插件下载到本地后解压缩,里面是一个Unity项目,也可以把GPUSkinning文件夹复制到自己的项目里面
2、采样动画数据
接下来要把我们基于Animator的动画转换成GPUSkinning插件使用的动画格式。
1.添加GPUSkinningSampler脚本
把我们需要的带动画的预设文件拖到场景里面,然后添加一个GPUSkinningSampler脚本上去
这里要注意一点,这个Animator组件里面的AnimatorController不能是AnimatorOverrideController,一定要是非Override的AnimatorController。不然会有报错提示。
2.必须设置的选项
这里有几个东西需要设置,首先是这个动画角色的名称,然后是一个顶点受多少根骨骼影响,使用怎样的shader,设置根骨骼节点,选择是否生成新的Shader,最后指定动画片段。
需要注意的东西有:
(1)顶点受多少根骨骼影响这个是可以后改的,不管选择多少根骨骼,工具都会导出uv2和uv3数据,其中uv2的数据是该顶点对应第1、2根骨骼的蒙皮权重,uv3是顶点对应第3、4根骨骼的蒙皮权重。区别只是在于自动选择或者生成的shader不同而已,shader可以后面在材质球直接选择或者修改的。
(2)是否选择生成新shader,决定于你的模型是否有特殊的效果。如果有,比如原来的模型是使用了卡通渲染,或者pbr渲染之类的,在插件默认提供的shader里面找不到相同效果的shader可以选择时,就可以选择生成新shader,那么就会生成一个新的shader文件,里面的顶点程序是写好了蒙皮方法的,比如skin2或者skin3或者skin4之类,我们就可以修改这个shader,达到想要的效果
(3)先设置动画片段的数量,然后动画片段需要手动拖进去,后面有两个选项,RootMotion是根节点是否移动,Individual Difference是该动画如果同时出现在多个个体身上时,是否需要有差异。之前的文章里面我说过GPUSkinning的其中一个缺点是在场景里面同一个角色播放同一个动作时会完全一样,这里的Individual Difference是为了解决这个问题,如果某个动作选择勾上这个选项,那么如果这个动作循环播放时,将会随机取一个数字,来错开动画播放的帧。
3.LOD选项
在展开LOD选项后,可以配置LOD等级,这是一个可选项,如果本身模型有做多个不同等级的LOD模型,可以在这里设置
设置一个size,然后指定不同LOD等级使用的网格模型和距离。
4.预览和编辑
点击Preview/Edit按钮,可以打开预览和编辑的内容
这里可以选择动画,预览动画,并且增加动画事件
可以设置Bounds
最下面还有一个骨骼列表,前面可以勾选。这个勾选的作用,是可以保留某个骨骼节点。因为在通过GPUSkinning播放动画时,原有的骨骼和网格模型都不存在了,只有一个空节点。如果我们想做一些跟随功能,比如角色手上拿着的武器可以替换,那么我们就可以把手的骨骼勾上,这样播放的时候,会看到手的骨骼的节点,那么就可以把武器挂上去了。
5.采样动画
在LOD下面,有个Step1:Play Scene的按钮。这其实就是提示我们如果要开始采样动画,就先点这个按钮运行场景。
运行场景之后,这里会出现Step2,意思就是第二步,正式开始采样了。点击一下这个按钮
插件就开始采样动画,并生成需要的文件了。实际上的过程,是逐个动画播放一次,然后每一帧获取骨骼的bindposes矩阵,这个矩阵是每根骨骼的位移旋转缩放,然后记录下来。
最后生成了这些文件
接下来说一下这些文件的作用
(1)anim属性文件
这个文件是导出的动画文件的总文件,里面包含了这个动画角色的大部分信息,比如名字、骨骼列表、动画列表、Bounds范围、根骨骼index、保存每帧动画的texture的宽高、Lod等级设置等。
(2)材质球文件
每个角色,都会生成一个材质球,如果在生成时勾选了新Shader,这里还会生成一个对应的Shader
(3)网格模型文件
包含了本身的角色的网格模型,还有lod等级里面的网格模型
仔细的看看信息,它包含了uv2和uv3的信息,上面介绍过,就是这个模型的顶点蒙皮权重
(4)还有一个包含Texture的byte文件。
这个文件其实是一个贴图文件来的,不过是什么格式并不重要,因为里面的颜色值rgba,其实是每一帧每根骨骼的bindpose矩阵,3个顶点颜色代表了一个矩阵。
之前在采样的时候说过,实际上是每个动画播放一次,然后每帧记录bindposes,这里就是把这些数据记录在rgba颜色里面。在需要播放动画的时候,从这些颜色值里面还原骨骼矩阵,然后结合着模型的uv2和uv3带有的蒙皮信息,就可以算出顶点在某一帧动画里面的位置了。
6.运行动画
当生成完了需要的文件之后,就可以进行播放了
先建一个空的GameObject在场景里,然后添加一个GPUSkinningPlayerMono脚本到GameObject上。
把刚才生成的文件对应的拖入到GPUSkinningPlayerMono脚本里面
当文件都拖入之后,就能看到场景里面出现了会动的模型了。
这里可以选择需要播放的动画
然后在运行的时候,可以通过脚本播放指定的动画:
player = GetComponent<GPUSkinningPlayerMono>().Player;
player.Play("Idle");
观察一下GameObject节点,会发现刚才勾选了暴露的手部骨骼,在下面生成出来了。
现在可以创建很多个这样的角色了同时运行。
二、原理的归纳
刚才在介绍用法的时候,也介绍了一部分原理,这里归纳一下:
1、数据准备阶段
1.通过uv2和uv3记录网格模型的顶点蒙皮信息
2.采样的时候,每个动画播放一遍,然后每一帧记录骨骼的位移旋转缩放
3.把动画里面每根骨骼的位移旋转缩放矩阵,通过rgba颜色,一个颜色记录四个数据,然后一个4x4矩阵原则上是需要4个像素的颜色记录的,但由于最下面一行数据是固定的0,0,01,所以只记录前面3行就够了,所以这个工具实际上只使用了3个像素颜色来记录一个骨骼的矩阵。这些像素色,记录在一张Texture2D上,然后通过Texture2D.GetRawTextureData方法获得byte[]并保存。
2、播放阶段
1.通过一个控制器统一去加载和分配角色的动画资源,比如同一个角色,他使用的材质球其实是一样的,这样可以合并DrawCall
2.通过Texture2D.LoadRawTextureData方法还原Texture2D数据,并还原每一帧每根骨骼的矩阵
3.在需要播放动画的时候,通过uv2和uv3去得蒙皮信息,然后通过矩阵和权重计算出顶点的位置,达到了播放动画的目的。
4.通过Renderer.SetPropertyBlock给Render设置不同的参数,就能得到差异化的播放动画了。
三、扩展思考
类似GPUSkinning的功能,阿赵我其实自己也实现过,我实现过2次:
一次是在以前做页游的时代,使用AS3编写过在GPU进行蒙皮骨骼动画播放的功能,骨骼动画的原理在之前的文章里面也介绍过,有兴趣的朋友可以去翻一下旧文章。
另外一次是在Unity里面实现的,和GPUSkinning有点类似,也是通过贴图来记录动画关键帧信息,不过却有点不一样。
我自己的实现方式具体是这样的:
我在贴图里面记录的不是骨骼的信息,我也不记录顶点的蒙皮信息,我只关心顶点位置。
1、记录顶点的uv2
我把所有的顶点编了一个顺序,每个顶点的序号记录在uv2的u坐标里面,然后uv2的v坐标固定是0
Vector2[] uv2 = new Vector2[vertCount];
for(int i = 0;i<vertCount;i++)
{Vector2 tempUV = new Vector2((float)i /(float) (vertCount-1), 0);uv2[i] = tempUV;
}newMesh.uv2 = uv2;
这里的意思是,只要根据uv2采样贴图,就能得到顶点对应某一帧的像素颜色,然后移动v坐标,就可以实现不同帧数的采样。
2、记录关键帧的时候,同样把所有动画播放一次,然后每一帧记录。不过记录的不是骨骼的bineposes,而是每个顶点在当前帧的实际位置。
这里会存在2个问题
1.由于颜色的值只能是0-255,或者说是0-1范围,但顶点的坐标是没有范围的,而且可以是负数的。为了解决这个问题,所以需要先计算出所有顶点可能出现的最大和最小范围,记录两个Bounds,然后颜色值实际记录的,是当前顶点坐标在最大最小值之间的比例,所以肯定是0-1的。
2.如果模型顶点很多,图片的宽度会变得特别的大,比如一个一万顶点的模型,动画总共有100帧,按照上面说的生成方式,图片的大小将会是10000x100,这样明显是不合理的,所以一般来说会设置一个最大宽度,比如2048,那么上面这个模型,将会生成一张2048*500的贴图。
3、播放动画的时候,就非常简单了,只需要指定当前需要播放第几帧,然后根据uv2的v坐标来上下移动贴图的采样范围,就能得到某一帧的颜色点,然后计算出顶点的实际坐标了。
4、由于只需要移动uv2的v坐标,所以控制方式可以很多样,单纯在shader里面用_Time来自动播放也行,传入Index指定播放某一帧也可以。
对比一下GPUSkinning,我这种方法有优点,也有缺点
优点:
我这种计算方式,实际上是比GPUSkinning的计算量要小的,因为所有参数包括贴图都是直接放入材质球里面,基本上不需要C#的额外干涉就能播放,也不需要计算权重,直接设置顶点坐标就行。而且控制的手段也比较的灵活。
缺点:
我这种计算方式,缺点也很明显。由于记录关键帧动画是根据顶点来记录的,所以如果顶点很多的模型,比如上万个顶点的模型,也是有的,需要记录的数据就会非常多。而GPUSkinning记录的是骨骼,一个角色模型骨骼再多也就几十到上百根骨骼而已,记录的数据就明显少很多。
说了这么多,最后说句老实话,如果要我写一套像GPUSkinning一样完整的解决方案,阿赵我还是很难写出来的,因为要配套写很多相关的编辑器工具,对于我来说,工具量还是非常大的。
相关文章:

Unity的GPUSkinning进一步介绍
大家好,我是阿赵。 在几年前,我曾经写过一篇介绍GPUSkinning的文章,这么多年之后,还是看到不停有朋友在翻看这篇旧文章。今天上去GitHub看了一下,GPUSkinning这个开源的插件已经很久没有更新过了,还是停…...

Mysql redolog
一、redolog 是啥 数据库的ACID:A原子性,C一致性,I隔离性,D持久性; redolog:保证 持久性; redolog: 系统奔溃重启时需要按照上述内容所记录的步骤重新更新数据页,特点:…...

【设计模式】Head First 设计模式——桥模式 C++实现
设计模式最大的作用就是在变化和稳定中间寻找隔离点,然后分离它们,从而管理变化。将变化像小兔子一样关到笼子里,让它在笼子里随便跳,而不至于跳出来把你整个房间给污染掉。 设计思想 桥模式。将抽象部分(业务功能)与实现部分(平…...

CESM2代码下载
这半年忙着毕业写论文,好久好久好久不更新了∠( ω)/ ,今天准备开个新坑 ๑乛◡乛๑,学习一下CESM(Community Earth System Model),它是一个完全耦合的全球气候模型,可用于地球过去、…...

编写OpenCL程序的基本步骤
opencl pyopencl OpenCL-Headers OpenCL(全称为Open Computing Langugae,开放运算语言)是第一个面向异构系统(此系统中可由CPU,GPU或其它类型的处理器架构组成)的并行编程的开放式标准。 它是跨平台的。 OpenCL由两部分组成,一是用于编写…...

计算机网络之TCP/IP协议第一篇:网络基础知识
文章目录 写给自己的话 一:前言 1:手握金刚钻的TCP/IP 2:计算机中的协议 3:分组...

虚拟机扩容
系统环境centos8,分两步,第一步先在vmware扩容,第二部在虚拟机内部扩容 1.vmware分配磁盘空间 2.虚拟机内部扩容 查看当前磁盘信息,这个是扩容之前的,扩容完成才会显示新的 df -h查看系统分区信息 fdisk -l查看目录…...

Linux下的系统编程——进程间的通信(九)
一、进程间通信常用方式 IPC方式: Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核&am…...

Qt QtableWidget、QtableView表格删除选中行、删除单行、删除多行
文章目录 Qt QtableWidget表格删除选中行只能选择一行,点击按钮后,删除一行可以选择中多行,点击按钮后,删除多行选中某一列中的不同行,点击按钮后,删除多行 QTableWidgetSelectionRange介绍QTableWidget的选…...

【代码随想录day24】不同的二叉搜索树
题目 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n 3 输出:5示例 2: 输入:n 1 输出…...

数学建模--Subplot绘图的Python实现
目录 1.Subplot函数简介 2.Subplot绘图范例1:绘制规则子图 3.Subplot绘图范例2:绘制不规则子图 4.Subplot绘图范例3:gridspec辅助实战1 5.Subplot绘图范例4:gridspec辅助实战2 1.Subplot函数简介 """ 最近在数学建模种需要绘制多张子图,发现对于subplot函…...

JMeter(三十九):selenium怪异的UI自动化测试组合
文章目录 一、背景二、JMeter+selenium使用过程三、总结一、背景 题主多年前在某社区看到有人使用jmeter+selenium做UI自动化测试的时候,感觉很是诧异、怪异,为啥?众所周知在python/java+selenium+testng/pytest这样的组合框架下,为啥要选择jmeter这个东西[本身定位是接口测…...

c++ 移动构造方法为什么要加noexcept
背景: 最近看了候捷老师的c的教程, 他说移动构造方法要加noexcept, 在vector扩容的时候, 如果有移动构造方法没有加noexcept,是不会调用的. 个人感觉有些神奇, 这就去查下一探究竟. 过程: 测试代码如下: #include <iostream> #include <vector> struct A {A(){s…...

鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…...

手把手教你搭建园林园艺小程序商城
现如今,随着互联网的快速发展,小程序成为了企业和个人展示产品和服务的新方式。在园林园艺行业,构建一个园林园艺小程序能够更好地推广和销售自己的产品和服务。那么,如何构建一个园林园艺小程序呢?下面我们来详细介绍…...

Java Iterator(迭代器)
Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。 它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。 Iterator 是 …...

Logstash同步MySQL数据到ElasticSearch
当MySQL数据到一定的数量级,而且索引不能实现时,查询就会变得非常缓慢,所以使用ElasticSearch来查询数据。本篇博客介绍使用Logstash同步MySQL数据到ElasticSearch,再进行查询。 测试环境 Windows系统MySQL 5.7Logstash 7.0.1El…...

【C++】运算符重载的示例实现和应用
C运算符重载的格式: operator 运算符 比如要重载 ! 运算符 : operator ! 下面是一个例子: class DemoText{DemoText(string str, int num){m_text str; m_number num;}string m_text;int m_number; }这里来定义两个对象:…...

Kubernetes禁止调度
在Kubernetes中,您可以通过几种方式来禁止某个Pod调度到节点上。以下是一些方法: Node Selector:您可以使用Node Selector来限制Pod只能调度到带有特定标签的节点上。如果您希望完全禁止Pod调度到某些节点上,可以确保这些节点不拥…...

CocosCreator3.8研究笔记(七)CocosCreator 节点和组件的介绍
相信很多新手朋友,肯定会问,CocosCreator 中什么是节点?什么是组件? 一、什么是组件(Component)? Cocos Creator 3.8 的工作流程是以组件式开发为核心,即以组合而非继承的方式进行游…...

Ceph入门到精通-C++入门知识点
C中的双冒号(::)是作用域分解运算符(scope resolution operator)。 它主要有以下两种用法: 用于区分同名的不同成员,例如在不同类中声明了同名的成员函数或成员变量,可以使用A::B的方式来特指A类的B成员。当全局变量…...

Ansible之playbook详解和应用实例
目录 一、playbook简介 1.什么是playbook 2.playbook组成 二、应用实例 1.使用playbook安装启用httpd服务 2.使用playbook安装启用nginx服务 三、ansible-playbook其他用法 1.检查yaml文件的语法是否正确 2.检查tasks任务 3.检查指定的主机 4.指定从某个task开始运行…...

经验萃取方法
【经验萃取】 经验萃取不是简单的总结提炼归纳! 经验萃取需经过还原、复盘分析、萃取重构 一.经验萃取前三个准备 1.定主题: 萃取主题选择(阐述原因、确定级别、差距/问题是源头)->多维评分:普遍性、重要性、迫切…...

手写apply方法
<script>/** 手写apply方法 * */Function.prototype.myApply function (context, args) {console.log(this, sss)//fnconst key Symbol()context[key] thiscontext[key](...args)delete context[key]return context[key]}const obj {name: zs,age: 18}function fn …...

Jenkins实现基础CD操作
操作截图 在Jenkins里面设置通过标签进行构建 在Jenkins中进入项目,配置以下 将execute shell换到invoke top-level maven targets之前 在gitlab中配置标签 代码迭代新的版本 项目代码迭代 修改docker-compose.yml 提交新版本的代码 在Jenkins中追加新…...

开源软件合集(Docker)
Docker安装 1.安装命令:curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun2.启动:systemctl start docker3.停止:systemctl stop docker4.重启:systemctl restart docker5.开机启动:systemctl enab…...

Ceph入门到精通-生产日志级别设置
Ceph 子系统及其日志记录级别的信息。 了解 Ceph 子系统及其日志记录级别 Ceph 由多个子系统组成: 每个子系统都有其日志记录级别: 默认情况下存储在 /var/log/ceph/ 目录中的输出日志(日志级别)存储在内存缓存中的日志&#…...

16-MyCat
一 Mycat概述 1 什么是Mycat 什么是Mycat Mycat是数据库中间件,所谓数据库中间件是连接Java应用程序和数据库中间的软件。 为什么要用Mycat 遇到问题: Java与数据库的紧耦合高访问量高并发对数据库的压力读写请求数据不一致 2 Mycat与其他中间件区别 目…...

RKNPU2通用API和零拷贝API
RKNPU2通用API 通用API接口按照异构编程规范,需要将数据拷贝到NPU运行时的内存空间。 通用API部署流程 初始化上下文,需要先创建上下文对象和读取模型文件 rknn_context ctx; model load_model(model_path, &model_len); ret rknn_init(&ctx…...

LeetCode 1123. 最深叶节点的最近公共祖先:DFS
【LetMeFly】1123.最深叶节点的最近公共祖先 力扣题目链接:https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/ 给你一个有根节点 root 的二叉树,返回它 最深的叶节点的最近公共祖先 。 回想一下: 叶节点 是二叉树…...