《Unity3D高级编程之进阶主程》第五章,3D模型与动画(四) - 3D模型的变与换2

上一篇介绍了一些3D模型的渲染基础知识,并且将这些基础知识运用在了3D模型的切割、拉伸、扭曲当中。

技术原理是最重要的核心点,人只要理解了技术原理,就能将技术表象解释清楚,在实际项目中将这种技术运用自如。
实际中并没有这么容易,理解和运用自如之间,还是有一段很大的差距的,在实际项目过程中,理解只是理解,只是能解释这种表象是如何发生的,运用自如,则是熟知了原理的优缺点,不再对这种技术有任何的偏见,能做什么不能做什么,做什么有什么优势,有什么有什么劣势,有了更加深入的理解,当没有了这么多的偏见,运用起来自然得心应手。
理论上说,我们每个人把所有精力先放在理解原理上,然后再去做技术实现不就轻松了吗。理论上是这样的,但人类头脑的进化毕竟还没到那么高级的程度,还不能轻松的把抽象的事物短时间内在脑中有条不紊的整理清楚,这还没把人的情绪影响算进去,人性毕竟是弱点多于优点的,虽然表面上能处理一些看上去相对复杂的事物,但从学习知识的角度看,人类的大脑还是不够高级,说实话应该说是有点低级。
对于这么低级的大脑,我们只能通过反复的学习,然后实践,再学习,再实践,很多遍后才能对原理的理解推进那么一点点,我们也只能依靠长时间的、漫长时间的、很长很长时间的磨练,从有兴趣到放弃,再重新拿起到再放下,再拾取再举起再抛向天空,反反复复但始终不离弃,才能最终到达运用自如的境界。人之间没有聪明与不聪明之分,有的人善于思考,善于举一反三是因为前面很长一段时间他都一直在练习这种多思考和举一反三的方式,当我们看到他时他的思维方式已经成型,这些并不是先天就有,而是通过后天努力得来。天才这东西,不管它是否真的存在,我们都应该忽略它,转而更加专注于自己的努力上。

===

3.简化模型

经常在项目中看到有模型有几万个面,甚至几十万个面的情况,模型面数多的好处是能表现的更加精细,画面更细腻,但坏处却是加重了渲染压力,渲染的面数越多压力越重,帧率越低。所以一般都会对场景中的单个3D模型进行限制,或者对整体场景面数进行限制。

画面质量和性能需要权衡,通常都是要求模型降低面数而画面质量不变,LOD(Level Of Detail)在这里就可以发挥巨大的作用,随着镜头的靠近逐级更换更细腻的模型很好的解决这种需求。

简化模型在LOD中用到的最多,有手动用3D模型软件简化的,也有用程序工具式的简化模型,手动简化更加平滑但费时间,时间成本大,而用程序工具去简化则快速,但不平滑。我们可以根据项目的需求,规模,画质等要求来权衡是否使用程序工具去简化模型,或者更加灵活点,一部分不太需要细致的模型使用程序工具简化,而另一部分比较需要细节化的模型使用手动简化,这样即照顾到了画质,又照顾到了工期。

这里我们来具体讲述下简化模型的算法。虽然市面上简化模型的插件和工具很多,但如果我们对原理有了更深入了解,在实际的项目中运用这些工具会更加自如且得心应手。

3D模型是由点,线,面组成。面由点和线组成,减面相当于减点和线,单纯的减去点和线容易引起模型变化不受控制,收缩线上的两顶点成为一个顶点则更加靠谱些。[Garland et al. 1997]提出了一种基于二次项误差作为度量代价的边收缩算法,其计算速度快并且简化质量较高。

该方法是去选择一条合适的边进行收缩时,定义一个边的收缩都是有代价的,每个顶点也有自己的代价,对于网格中的每个顶点v,我们预先定义一个4×4的对称误差矩阵Q,那么顶点v=[x y z 1]的代价为其二次项形式Δ(v)=vQ。

同时也定义了边收缩的代价公式,假设对于一条收缩边(v1,v2),其收缩后v1,v2顶点收缩为v3,我们定义顶点v3的误差矩阵Q3为Q3=Q1 + Q2,也就说是v1,v2的这条边的收缩为v3后代价为Δ(v3) = v3(Q1 + Q2),以此类推每条边都有一个代价。

有了上面的代价公式,下面的网格简化算法就容易理解多了:

1,对所有的初始顶点都计算它们各自的Q矩阵.

2,选择所有有效的边(这里取的是两点有连线的边,也可以将两点有连线且距离小于某个阈值的边归为有效边)

3,对每一条有效边(v1,v2),计算最优收缩目标v3.误差(Q1+Q2)是收缩这条边的代价(cost)

4,将所有的边按照cost的权值都放在队列中从小到大进行排序。

5,每次移除队列顶部的代价(cost)最小的边,也就是收缩最小代价的边,删除v1,v2,并用v3替换。

6,重复1-5步骤,直到顶点数少于某个设定的值,或者所有cost代价大于某个值,则停止收缩算法。

整个算法并不复杂,关键这里有两个核心问题需要解决,一个是每个顶点的初始Q矩阵如何计算,另一个是v1,v2收缩为v3时的坐标位置该怎么计算。

在原始网格模型中,每个顶点可以认为是其周围三角片所在平面的交集,也就是这些平面的交点就是顶点位置,因此我们定义顶点的误差为顶点到这些平面的距离平方和。

由此定义我们可以计算出每个顶点的初始误差矩阵Q:Δ(v)为顶点误差值 = vQ = 0,因为初始顶点的误差值为0,因为是它与相交平面的距离平方和为0,那么初始顶点的误差矩阵Q也可以由此计算出来。

至于v1,v2收缩为v3时如何选择最优的坐标,简单的方法就是取v1,v2,和中点(v1+v2)/2的三个中收缩代价最小的一个为最优选择,另一种策略则是数值计算顶点v3位置使得Δ(v3)最小,由于Δ的表达式是一个二次项形式,因此令一阶导数为0。

按照这个算法步骤,不停的收缩最小代价的边,直到顶点数量小于某个值时停止,最终将得到一个简化的模型。

4.蒙皮骨骼动画

有了3D模型,又会有3D模型动画,那么3D模型和3D模型动画之间到底有什么区别,我们先做个了解。

为了能让读者们更直观的了解模型与模型动画的不同,我们从 Unity3D 的 MeshRenderer 和 SkinnedMeshRenderer 上作为切入点。

在Unity3D中,MeshRenderer 与 SkinnedMeshRenderer 分别用于渲染 3D模型 和 3D模型动画。

普通网格(MeshRenderer)渲染的是模型,由3D模型数据组成,蒙皮网格(SkinnedMeshRenderer)渲染的也是模型,也是由3D模型数据组成。

不过蒙皮网格被创造出来主要是为了动作动画服务的,所以蒙皮网格除了3D模型数据外还有骨骼数据以及顶点权重数据。

假设说蒙皮网格(SkinnedMeshRenderer)上没放置任何骨骼数据,那么它和普通网格的作用没有任何区别,都只能渲染静止不动的3D模型。

有很多人并没有理解骨骼动画的原理,所以在实际项目中对3D模型骨骼动画的运用有很多误区,这里我们有必要阐述一下骨骼动画的原理,以及在Unity3D的SkinnedMeshRenderer上骨骼动画是如何组装和组成的。通过对骨骼动画的原理解剖和对 SkinnedMeshRenderer 的解剖,我们能彻底的明白骨骼动画的计算和渲染其实并不复杂,揭开这层薄薄的面纱后是一片平坦的开阔地。

我们知道3D模型要做动作,首先是模型上的点,线,面要动起来,只有点,线,面动起来了,在每帧渲染的时候才能在每帧渲染时有不同的3D模型形状。

那么怎么让点,线,面动起来呢?有两种方法,一种是用一个算法来改变顶点位置,叫顶点动画,另一种是用骨骼机制去影响顶点,叫骨骼动画。

这两种都是通过每一帧改变模型上的各个顶点位置,从而让模型变形,进而形成动画的效果,每一帧模型的形状不一样,播放时形成动画,只是方法不同。

模型上成千上万的顶点,如果每个都要手动来调整,每一帧都要手动调整一次,这种量级的劳动力,人类无法承受,所以在最初,还没有骨骼动画这个概念的时候,只能用程序写一个顶点走向算法去改变每个顶点的位置,或者用着色器(Shader)在顶点函数中去改变顶点位置成为动画效果,这种动画效果现在很多游戏中都还在使用,这种方法已经成为节省骨骼动画开销的必要手段,比如草、树在风中的左右摆动,丝带或国旗在空中自然飘动等。至于如何在着色器(Shader)中编写顶点动画,我们会在OpenGL渲染章节中讲解。

骨骼动画的出现让3D模型的动画效果就变得越来越丰富多彩。为什么这么说?因为它是由简单的几个骨骼点组成,一般骨骼动画的骨骼数量都不会超过50个,是人类能够承受的操控范围之内。通过这一些简单的骨骼点操作,加上3DMAX,Maya这样的好用的动画工具,让人类能够创造出许许多多丰富多彩的动画效果。

首先,骨骼动画由骨骼点组成,每个骨骼动画可以由许多个骨骼,现代手机游戏中每个人物的骨骼动画的数量一般都会在30个左右,PC单机游戏中会更多点到达40-50个左右。骨骼数量越多,表现出来的动画越细腻越有质感,但同时也消耗掉更多的CPU计算。

其次,一个骨骼可以有很多个子骨骼,每个子骨骼都与父节点拥有相同的功能,只是当父节点移动、旋转、缩放时子节点也随着父节点的一起移动、旋转、缩放,他们的相对位置、相对角度、相对比例不变。这一点与Unity3D中的 GameObject 的节点很相似,父子节点有着相对位置的关系,所以骨骼点在Unity3D中的表现是由类似GameObject的形式存在的,我们可以直观的从带有骨骼的模型中看到骨骼点的挂载结构。

在 SkinnedMeshRenderer 中就有 bones 这个变量来记录所有骨骼点,骨骼点的表现在 SkinnedMeshRenderer 中就是 Transform 数组,因此 bones 这个变量就是 Transform[] 类型。

另外,一个骨骼点可以影响周围一定范围内的所有顶点,单一一个顶点,也可以受到多个骨骼的影响。在Unity3D中的 Quality setting 图形质量设置中,我们可以看到关于Blend Weights的设置,就是关于一个顶点能被多少骨骼影响的参数设置。选项中,有1 Bone,2 Bones,4 Bones,意思是一个顶点能被1个骨骼影响,或者被2个骨骼影响,或者被4个骨骼影响,能被影响的骨骼数越多,CPU消耗在骨骼计算蒙皮的时间越长,消耗量越大。

最后,模型中每个顶点都有对它顶点本身影响的最多4个骨骼的权重值,在Unity3D中这4个骨骼权重被记录在了 BoneWeight 这个类中,每个 Mesh类 都有一个 boneWeights 变量来记录所有顶点的骨骼权重值,而无骨骼动画的 Mesh 是不需要这些数据的。

每个顶点都有一个BoneWeight 类实例,BoneWeight 中boneIndex0,boneIndex1,boneIndex2,boneIndex3分别代表被影响的骨骼索引值,而weight0,weight1,weight2,weight3则是分别代表被影响的权重值,权重最大为1,最小为0,所有权重分量之和为1。

未完,篇幅有限,下篇继续

感谢您的耐心阅读

Thanks for your reading

  • 版权申明

    本文为博主原创文章,未经允许不得转载:

    《Unity3D高级编程之进阶主程》第五章,3D模型与动画(四) - 3D模型的变与换2

    Copyright attention

    Please don't reprint without authorize.

  • 微信公众号