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带有的蒙皮信息,就可以算出顶点在某一帧动画里面的位置了。

3、运行动画

  当生成完了需要的文件之后,就可以进行播放了
  先建一个空的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一样完整的解决方案,阿赵我还是很难写出来的,因为要配套写很多相关的编辑器工具,对于我来说,工具量还是非常大的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/131528.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

虹科方案 | HK-NEOs系列带来先进的磁带自动化解决方案

一、HK-NEOs 系列自动磁带库 通常只有在昂贵的企业解决方案中才能找到的高级功能&#xff0c;我们的入门级磁带自动化产品就能够具备。使用 HK-NEOs 系列自动化磁带库&#xff0c;可以获得远程管理、可拆卸盒式磁带卷、可升级磁带驱动器、条形码阅读器等更多功能。 但这还不是…

JavaScript逻辑题:输出1000之内的所有完数。所谓完数指的是:如果一个数恰好等于它的所有因子之和,这个数就称为完数。

// 定义函数function judgeNum(){// 定义数组存储完数let arr []// for循环1000以内的所有数for(let i 1;i<1000;i){// 定义sum存储一个数的因子之和let sum 0;// 内层循环一个数的因子for(let j 1;j<i;j){if(i % j 0){sum j;}}// 如果一个数和它的因子之和相等&am…

FPGA-结合协议时序实现UART收发器(六):仿真模块SIM_uart_drive_TB

FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模块SIM_uart_drive_TB 仿真模块SIM_uart_drive_TB&#xff0c;仿真实现。 vivado联合modelsim进行仿真。 文章目录 FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模…

JavaScript逻辑题:牙膏2元 牙刷5元 牙膏盒15元 请问正好花完100元 有多少情况?

// 定义牙膏 牙刷 牙膏盒分别的价格 let toothpaste 0;let toothbrush 0;let toothpastebox 0;// 定义sum用来存储几种情况let sum 0;//第一层循环 循环牙膏买多少for (let i 0; i < 20; i){toothpaste 5 * i;// 二层循环 循环牙刷的数量for (let j 0; j < 50; j…

合伙企业是什么?

合伙企业可能大家听说也较多&#xff0c;但是到底什么是合伙企业&#xff0c;可能就没那么清楚了。看完今日的内容&#xff0c;你就会知道原来这就是合伙企业啊。 一、什么是合伙企业&#xff1f; 根据《中华人民共和国合伙企业法》&#xff0c;合伙企业是由两个或两个以上的自…

黑马JVM总结(六)

&#xff08;1&#xff09;常量池 方法区的组成中都由一个叫做运行时常量池的部分&#xff0c;内部包含一个叫做StringTable的东西 反编译二进制字节码&#xff1a; 类的基本信息&#xff1a; 常量池&#xff1a; 方法定义&#xff1a; 构造方法 main方法 &#xff1a;方法中…

手动开发-实现SpringMVC底层机制--小试牛刀

文章目录 前端控制器Controller注解RequestMapping注解自定义容器LingWebApplicationContext设计handlerList完成分发请求Service注解和AutoWired注解RequestParam注解完整代码 在这里说的底层机制的实现主要是指&#xff1a;前端控制器、Controller、Service注入容器、对象自动…

go并发处理业务

引言 实际上&#xff0c;在服务端程序开发和爬虫程序开发时&#xff0c;我们的大多数业务都是IO密集型业务&#xff0c;什么是IO密集型业务&#xff0c;通俗地说就是CPU运行时间只占整个业务执行时间的一小部分&#xff0c;而剩余的大部分时间都在等待IO操作。 IO操作包括htt…

反编译小程序详细教程,处理各种异常报错

文章目录 一、准备工作 &#xff08;一&#xff09;安装Nodejs &#xff08;二&#xff09;解密和逆向工具 二、小程序缓存文件解密 &#xff08;一&#xff09;定位小程序缓存路径 &#xff08;二&#xff09;源码解密 &#xff08;三&#xff09;源码反编译 三、小结 四、异常…

Go 异常处理

代码在执行的过程中可能因为一些逻辑上的问题而出现错误 func test1(a, b int) int {result : a / breturn result } func main() {resut : test1(10, 0)fmt.Println(resut) }panic: runtime error: integer divide by zero goroutine 1 [running]: …

华为云arm架构的linux系统中通过docker部署python环境

背景 有时候需要在无互联网的环境安装部署python环境,虽然可以在linux系统中直接安装python环境,但是比较复杂乱,特别是环境多的时候,其实可以通过docker打包安装的方式来实现 1、租用华为云arm加载的服务器 https://www.huaweicloud.com/product/ecs.html 2、安装doc…

请明星出席品牌周年庆活动:巧妙策略与成功之道

品牌的周年庆典是一次展示实力、感谢客户和吸引更多关注的机会。在这个特殊时刻&#xff0c;让明星出席活动演出无疑是让庆典更加引人注目和难忘的方式。明星的存在不仅能增加活动的知名度&#xff0c;还能为品牌增色不少。然而&#xff0c;邀请明星出席活动是一项复杂的任务&a…

phpcmsV9.6.0sql注入漏洞分析

目录 前言 环境准备 漏洞点 看一看parse_str函数 看一看sys_auth函数 看一看get_one函数 全局搜索sys_auth($a_k, ENCODE) 查看哪里调用了 set_cookie 查看safe_replace函数 判断登录绕过 index的业务 加载modules/wap/index.php 加载modules/attachment/attachme…

免费版Photoshop2024智能人像磨皮插件

Portraiture是一款智能磨皮插件&#xff0c;为Photoshop和Lightroom添加一键磨皮美化功能&#xff0c;快速对照片中皮肤、头发、眉毛等部位进行美化&#xff0c;无需手动调整&#xff0c;大大提高P图效率。全新4版本&#xff0c;升级AI算法&#xff0c;并独家支持多人及全身模式…

vue手写提示组件弹窗

1、弹框展示 2、message组件 新建一个message.vue <template><div class"wrapper" v-if"isShow" :class"showContent ? fadein : fadeout">{{ text }}</div> </template> <script></script> <style s…

微信怎么定时发圈?

定时发圈的妙用 在合适的时间点发布新的产品、促销活动&#xff0c;不仅能够及时提醒用户品牌的存在&#xff0c;还可以引发用户的兴趣&#xff0c;增加品牌的曝光率。 选择最佳的发朋友圈时间段&#xff0c;以确保推广内容得到最大的曝光和关注&#xff0c;提高广告投放的效果…

python元组

元组tumple tumple_python1.元组的创建2.元组是不可变序列3.元组的遍历 tumple_python -元组(tuple)是是一个有序、不可变的序列。元组用小括号 () 表示&#xff0c;其中的元素可以是任意类型&#xff0c;并且可以通过索引访问。 不可变序列与可变序列 1>不可变序列&#x…

MySQL中分区与分表的区别

MySQL中分区与分表的区别 一、分区与分表的区别 分区和分表是在处理大规模数据时的两种技术手段&#xff0c;尽管它们的目标都是提升系统的性能和数据管理的效率&#xff0c;但它们的实现方式和应用场景略有不同。 1. 分区 分区是将一个大表分割为多个更小的子表&#xff0c…

【WEB3】区块链开发入门项目之「简易NFT交易市场」

参考文章 教程英文版&#xff1a;https://docs.alchemy.com/docs/how-to-build-an-nft-marketplace-from-scratch 教程中文版&#xff1a;https://zhuanlan.zhihu.com/p/557479922?utm_id0 以太坊测试币领取&#xff1a;https://goerlifaucet.com/ 入门项目地址&#xff1…

【C++杂货铺】优先级队列的使用指南与模拟实现

文章目录 一、priority_queue的介绍二、priority_queue的使用2.1 数组中的第k个最大元素 三、priority_queue模拟实现3.1 仿函数3.2 成员变量3.3 成员函数3.3.1 构造函数3.3.2 AdjustDown3.3.3 push3.3.4 AdjustUp3.3.5 pop3.3.6 empty3.3.7 size 四、结语 一、priority_queue的…