The humanoid animation option in Unity 4 makes it possible to retarget the same animations to different characters. The characters can have different proportions and skeleton rigs where the bones have different names etc. But before you can take advantage of that, the bones in your character have to be matched up with the authoritative “human bones” that Mecanim uses. Fortunately, for the majority of models this otherwise lengthy setup process is completely automatic. Let’s have a look at how that works…
Unity 4 中的人形动画选项使得将相同的动画重新定位到不同的角色成为可能。角色可以有不同的比例和骨骼,骨骼有不同的名称等。但在你利用这一点之前,你角色的骨骼必须与 Mecanim 使用的权威“人类骨骼”相匹配。幸运的是,对于大多数模型来说,这个漫长的设置过程是完全自动的。让我们来看看它是如何工作的……
What Avatar Mapping Involves
Setting up a humanoid Avatar in Unity involves matching every “human bone” to one of the transforms in the model. It’s possible to do this manually in Unity by clicking Configure… under the Rig tab of the Model Importer. Without fingers there’s up to 24 bones to map – and 30 more if you have finger bones too. For every one of those you have to drag the transform into the corresponding slot in the GUI when you use the manual method.
在 Unity 中设置一个人形化身需要将每个“人骨”匹配到模型中的一个转换。可以在 Unity 中通过点击模型导入器的 Rig 选项卡下的 Configure… 手动完成。没有手指的情况下,需要绘制多达24块骨头,如果你有手指骨的话,还需要多绘制30块。在使用手动方法时,您必须将每个转换拖放到 GUI 中相应的槽中。
When I first joined the effort to improve the interface for Mecanim, that was the only way to setup characters. For anyone who wanted to experience the Mecanim awesomeness with their own characters, there was first these 24-54 bones to drag in with the mouse, one by one. Things like that don’t leave an exciting first impression. So I began looking into how to do that automatically.
当我第一次加入到为 Mecanim 改进界面的工作中时,这是设置字符的唯一方法。对于任何想要体验自己角色的 Mecanim 恐怖的人来说,首先要用鼠标把这24-54块骨头一块一块地拖进去。这样的事情不会给人留下令人兴奋的第一印象。所以我开始研究如何自动做到这一点。
Hints to Go By
How do you make a computer figure out what transforms correspond to which human bones? There is more than one way to go about it…
你如何让计算机计算出哪些变化对应于哪些人类骨骼?做这件事的方法不止一种……
Names of Bones
One seemingly straightforward option is to use the names of the transforms. But unfortunately, some of the most commonly used terms are actually used to describe different bones in different models.
一个看似简单的选项是使用转换的名称。但不幸的是,一些最常用的术语实际上是用来描述不同模型中的不同骨骼的。
- Hip can mean the center bone that’s parent to both legs, or it can mean the left or right hip.
髋关节指的是双腿的中心骨,也可以是左髋关节或右髋关节。 - Leg can mean upper or lower leg.
腿可以是上腿或下腿。 - Shoulder can mean either the bone for the upper arm or, if present, the collar bone that’s the parent to the upper arm bone.
肩膀可以是上臂的骨头,也可以是上臂的锁骨。 - Arm can mean upper or lower arm.
手臂可以指上臂或下臂。
Naming for bones in the spine is often a total anarchy since the number of bones is completely variable, and sometimes a bone called “neck” even has the left and right shoulder as children (due to a flaw in a popular software used for rigging), meaning the mapping have to assign it as the chest bone despite its name. Furthermore, finger bones are often simply named “finger_0” to “finger_14” or similar, with no convention of which numbers mean what. And bones for eyeballs and jaw have a ridiculous amount of variation for what they’re called, including helpful things like “nurbsToPoly2”. So while names of bones often give us certain useful hints, they don’t contain sufficient information on their own to cover a wide variety of models.
命名为脊柱骨骼通常是一个总无政府状态由于骨骼的数量完全是变量,有时是一种骨,称为“脖子”甚至有左和右肩的子物体(由于一个缺陷在一个流行的软件用于操纵),意义的映射必须指定它的胸部骨骼,尽管它的名字。此外,手指骨通常被简单地命名为“ finger_0 ”到“ finger_14 ”或类似的数字,没有哪个数字代表什么。眼球和下颌的骨骼有非常多的变异,包括像“ nurbsToPoly2 ”这样有用的东西。因此,虽然骨骼的名字经常给我们一些有用的提示,但它们本身并没有包含足够的信息来涵盖各种各样的模型。
Side Keywords
Just determining if a bone is for the left or right side is a mini challenge by itself. Obviously if the word “left” or “right” is included in the name it’s a given, but a lot of the time only the letter “l” or “r” is used. However, those letters can also be part of a word, so I only regard them as side keywords if they stand by themselves, separated from the rest of the string by spaces, punctuation, or similar.
仅仅是判断骨头是左边的还是右边的本身就是一个小小的挑战。显然,如果名称中包含“左”或“右”这两个词,那么名称就是给定的,但很多时候只使用字母“ l ”或“ r ”。但是,这些字母也可以是单词的一部分,所以我只在它们单独出现时才将它们视为侧关键字,并通过空格、标点符号或类似的方式将它们与字符串的其余部分分隔开。
Bone Directions
Some information can be inferred by looking at the direction of a bone. While there’s no guarantee about the pose of the character at the time when the auto-mapping happens, it’s generally more likely that bones in the right side point towards +x and that bones in the spine point towards +y while bones in the legs point towards -y etc. For each human bone we have specified a “correct” direction. Direction matches of different child transforms are computed using dot products of the normalized transform direction compared to a target human bone direction, and a score is awarded accordingly. Where the bone direction information really shines is in figuring out the mapping for the fingers, where there is often nothing else to go by.
通过观察骨头的方向可以推断出一些信息。虽然不能保证在自动映射发生时角色的姿势,但通常情况下,右侧的骨骼指向+x,脊柱的骨骼指向+y,而腿部的骨骼指向-y,等等。对于每一块人类骨骼,我们都指定了一个“正确”的方向。利用归一化变换方向与目标人体骨骼方向的点积计算不同子变换方向的方向匹配,得到相应的分数。骨骼方向信息真正的亮点在于找出手指的映射,而这通常是没有其他依据的。
Bone Length Ratios
While proportions of different models differ, there are usually some aspects of the proportions that are consistent for most models. For example, the upper leg and lower leg usually have roughly the same length, while the length of the foot is typically much shorter. The length of the upper arm bone is usually about twice as long as the length of the collar bone, when present. The length ratios contribute a lot to ensuring sensible mappings. Imagine for example a model that has no collar bone, and the upper arm bone is called “shoulder”. The upper arm also has a bone in between the shoulder joint and elbow joint used for twisting of the upper arm (but nothing in the name necessarily indicates this). Looking at the names of the bones alone, the computer would be inclined to map the bone called “shoulder” to the human collar bone and map the upper arm twist bone to the upper arm human bone. This would be completely wrong. But thanks to the length ratios, this scenario is almost always avoided and the bones are mapped correctly. Another place where the length ratios shine is for the bones in the spine. The spine in a model can consist of any number of bones, but they have to be mapped in a sensible way to just the following human bones: Hips, spine, chest (optional), neck (optional), and head. If you have, say, 8 transforms in the spine, how should they be mapped? The names are no use, and the bone directions are all the same. Just attempting an even distribution gives terrible results. Instead we strive towards these length ratios: spine-chest should be 1.4 times as long as hip-spine. Chest-neck should be 1.8 times as long as spine-chest. Neck-head should be 0.3 times as long as chest-neck. (Humanoids generally bend mostly in the area below the chest since the chest itself is more rigid.) The result is a mapping that produces bending in the spine that looks natural within the confines of the bones in the model. The algorithm chooses bone assignments that matches the desired length ratio as closely as possible. Calculating “closeness” in this case requires converting both the actual ratio and the desired ratio to a logarithmic scale and measuring the difference of those. The logarithmic scale ensures than a length that’s twice as longs as it’s supposed to be is penalized by the same amount as a length that’s half as long as it’s supposed to.
虽然不同模型的比例是不同的,但是对于大多数模型来说,比例的某些方面通常是一致的。例如,上肢和小腿的长度通常大致相同,而脚的长度通常要短得多。上臂骨的长度通常是锁骨长度的两倍。长度比有助于确保合理的映射。想象一个没有锁骨的模型,上臂的骨头被称为“肩膀”。上臂在肩关节和肘关节之间也有一根骨头,用来扭转上臂(但名字里没有必要说明这一点)。只看骨头的名字,计算机就会倾向于把被称为“肩膀”的骨头映射到人类的锁骨,把上臂扭转的骨头映射到上臂人类的骨头。这是完全错误的。但是由于长度比的关系,这种情况几乎总是避免的,而且骨骼的映射是正确的。另一个长度比突出的地方是脊椎的骨头。模型中的脊柱可以由任意数量的骨骼组成,但是它们必须以一种合理的方式映射到以下人类骨骼:臀部、脊柱、胸部(可选)、颈部(可选)和头部。如果在脊柱中有8个变形,应该如何映射它们?名字没有用,骨头的方向都是一样的。仅仅尝试均匀分布就会产生可怕的结果。相反,我们努力达到这些长度比例:脊柱-胸部应该是臀部-脊柱长度的1.4倍。胸-颈应该是脊柱-胸部的1.8倍长。颈部是胸颈的0.3倍长。(类人机器人通常在胸部下方弯曲,因为胸部本身更坚硬。)其结果是在模型的骨骼范围内产生脊椎弯曲,看起来很自然。该算法选择尽可能匹配所需长度比的骨分配。在这种情况下,计算“亲密度”需要将实际的比率和期望的比率转换成对数尺度,并测量它们之间的差异。对数比例尺确保,长度是预期长度的两倍的,与预期长度的一半,受到相同的惩罚。
Topology
Last but not least, the “topology” of the bones play a major role. For example, the neck and the left and right arm must all be children of the chest, so a mapping where one bone is mapped to the neck, but children of that bone are mapped to the left and right shoulder is not an option. Contrary to the other factors described above, the topology requirements work as a hard constraint. The requirements are specified in the form like this
最后但并非最不重要的是,骨骼的“拓扑结构”起着重要作用。例如,颈部以及左臂和右臂都必须是胸部的子节点,因此映射到颈部的一根骨头,但是映射到左侧和右侧肩膀的一根骨头的子节点不是一个选项。与上面描述的其他因素相反,拓扑需求是一个硬约束。需求是这样指定的
- The RightShoulder human bone must be a child to the Chest human bone, placed 1-3 levels further down the hierarchy. (1 level would be a child; 2 levels would be a child of a child etc.)
右肩人骨必须是胸部人骨的子骨,位于更低层次的1-3级。(1级为儿童;2级将是一个孩子的孩子等等) - The RightUpperArm human bone must be a child to the RightShoulder human bone, placed 0-2 levels further down. (A level of 0 means they are the same bone. This is permitted when one of the bones are optional, like the Shoulder ones are.)
右臂人骨必须是右肩人骨的孩子,再往下0-2级。(0表示它们是同一块骨头。当其中一块骨头是可选的,就像肩部的骨头一样,这是允许的。 - The RightLowerArm human bone must be a child to the RightUpperArm human bone, placed 1-2 levels further down.
右下臂人骨必须是右下臂人骨的子骨,再往下放置1-2层。
Most bones allows for a range of levels in between the mapping of itself and its parent. This is because different models have different amounts of transforms in between the principal human bones. For example there might or might not be a twist bone in between an upper arm bone and an elbow bone. The range also function as an optimization, since transforms that are further down the bone hierarchy than the number of levels allowed do not need to be considered as potential matches.
大多数骨骼都允许在自身和父类的映射之间有一定的层次。这是因为不同的模型在人类主要骨骼之间有不同数量的转换。例如,在上臂骨和肘骨之间可能有也可能没有扭转骨。范围也可以作为一种优化,因为在骨骼层次结构中比允许的级别数更低的转换不需要考虑为潜在匹配。
Optimal Mapping as a Search Problem
We’ve looked at various hints that can be used as part of the mapping algorithm, but how is it all combined? The solution I arrived at is to treat the mapping as a search problem. Consider the function EvaluateBoneMatch which evaluates a match between a human bone (for example Chest) and a transform (for example “MyModel_Chest”). This function evaluates the match itself according to the keyword, direction, and length ratio hints described above. The result is a score that indicates how good this match is. But the EvaluateBoneMatch function goes further than that. It iterates through all the child human bones. (For the Chest human bone, the children would be LeftShoulder, RightShoulder, and Neck.) For each of those it gathers all the transforms that are potential matches according to the topology requirements, i.e. all transforms n levels down the hierarchy, where n depends on which human bone is being mapped. And for each of those pairs of a child human bone and a transform it calls EvaluateBoneMatch. Since the function calls itself, this is called a recursive function. Each of those calls return a score value that determines how good the match is. Now the function determines the best match for each of the child human bones (LeftShoulder, RightShoulder, and Neck). This is based mostly on the scores, but it can’t simply choose the top ranking choice for each of them, since multiple child bones sometimes pick the same transform as their top choice! More on that later. Anyway, the score for each of the picked child human bone matches is added to the score of the current bone match itself, and the result is what the EvaluateBoneMatch function returns. Confused? It is common for it to take a little while and some effort to wrap one’s head around a recursive function and it sure did for me as I implemented the algorithm. Having many different inter-related hierarchical structures in parallel didn’t make it easier (the human bone hierarchy, the transform hierarchy, and the search graph hierarchy). But the gist of it is that the EvaluateBoneMatch function returns a score that represents not just how good that specific match is in itself, but including the entire child hierarchy of best matches. This in effect means that every bone mapping gets chosen not just based on how good that match is by itself, seen in isolation, but also based on how well it fits as a piece in the entire mapping. Remember how I mentioned that a lot of models incorrectly have the arms as children of a bone called “neck”? Imagine this conversation:
我们已经研究了可以作为映射算法的一部分使用的各种提示,但是它们是如何组合在一起的呢?我得到的解决方案是将映射视为一个搜索问题。请考虑EvaluateBoneMatch函数,该函数评估人类骨骼(例如胸部)和转换(例如“MyModel_Chest”)之间的匹配。这个函数根据上面描述的关键字、方向和长度比提示计算匹配本身。结果是一个分数,表明这个匹配有多好。但是EvaluateBoneMatch函数的作用不止于此。它遍历所有的子人类骨骼。(对于人的胸骨,孩子们会选择左肩、右肩和颈部。)对于其中的每一个,它都根据拓扑需求收集所有可能匹配的转换,即在层次结构的n个级别上的所有转换,其中n取决于映射的是哪块人类骨骼。对于每一对人类儿童骨骼和它称为EvaluateBoneMatch的变换。由于函数调用自己,所以这称为递归函数。每个调用都返回一个分数值,该值决定匹配的好坏。现在,该功能决定了每个子人类骨骼(左肩、右肩和颈部)的最佳匹配。这主要是基于分数,但它不能简单地为每个选项选择排名最高的选项,因为多个child bones有时会选择与它们的首选转换相同的转换!稍后将对此进行详细介绍。无论如何,所选择的每个子人类骨骼匹配的分数都被添加到当前骨骼匹配本身的分数中,结果就是EvaluateBoneMatch函数返回的结果。困惑吗?这是很常见的,它需要一些时间和一些努力,围绕着一个递归函数,这对我来说是肯定的,因为我实现了算法。并行拥有许多不同的相互关联的层次结构 大专栏 Automatic Setup of a Humanoid并没有使其变得更容易(人类骨骼层次结构、转换层次结构和搜索图层次结构)。但它的要点是EvaluateBoneMatch函数返回的分数不仅表示特定匹配本身有多好,还包括最佳匹配的整个子层次结构。但它的要点是EvaluateBoneMatch函数返回的分数不仅表示特定匹配本身有多好,还包括最佳匹配的整个子层次结构。这实际上意味着,每一个骨骼映射的选择不仅仅是基于其自身的匹配程度(单独来看),还取决于它作为整个映射中的一个部分的匹配程度。还记得我说过的吗,很多模特的胳膊都是骨头的一部分,叫做脖子?想象这样一个对话:
Ah, so this is the Neck bone! It’s called “neck” and points upwards and has a good length ratio, so it seems to match. The neck should only have the Head as a child, but there’s some other transform children as well that don’t seem to match any bones in the head. Oh well. … later … Hmm, alternatively we can try to map the bone called “neck” as the Chest bone. This doesn’t score well in terms of keyword matches but let’s try it anyway. This Chest has child transforms that seem to match the LeftShoulder and RightShoulder as well as the Neck. And those left and right shoulders have children that matches the various bones in the arms, and so on. All those matches are worth a lot of points so it ends up being better to match the transform “neck” to the Chest than to the Neck, based on the points obtained from the hierarchy further down.
啊,原来这是颈骨!它被称为“颈”,指向上方,有一个很好的长度比,所以它似乎匹配。颈部应该只有头部,但也有一些变形的儿童似乎与头部的骨头不匹配。哦。嗯,或者我们可以试着把这块叫做“脖子”的骨头映射成胸骨。这在关键字匹配方面得分不高,但让我们尝试一下。这个胸部的子变形似乎与左肩、右肩以及颈部相匹配。左肩和右肩的孩子与手臂上的骨头相匹配,等等。所有这些匹配值很多点,因此根据从更低层次获得的点,将转换“颈部”匹配到胸部要比匹配到颈部更好。
With a search based algorithm you get seemingly “smart” considerations like this for free all over the place. It’s very similar to the problem of trying to solve a maze: In order to get to the exit that you know is to the North, you may have to go in the wrong direction some of the way first. Knowing the right direction for even the first step requires knowing the entire solution.
使用基于搜索的算法,您可以免费获得类似这样的“智能”考虑。这与试图解决迷宫的问题非常相似:为了到达你知道的向北的出口,你可能必须先走错方向。知道正确的方向,即使是第一步,也需要知道整个解决方案。
Child Conflict Resolution
Multiple child human bones sometimes pick the same transform as their best matching choice choice. For example the human bone LeftHand have the child human bones ThumbProximal, IndexProximal, MiddleProximal, RingProximal, and LittleProximal. And maybe both RingProximal and LittleProximal picked the transform called “Finger_2” as the first choice. Now what to do? The same finger can’t be both the ring finger and the little finger! For each of the human bones we keep not just the first choice, but a ranked list of choices. To make sure that each transform is only mapped to one human bone, and that the transforms are assigned to the human bones in the best possible way, a function called GetBestChildMatchChoices is called. First we make a list that contains the current choice for each human bone. Initially they’re all set to 0 (the first choice) even if that means there are potential conflicts. We pass that list of current choices to the function, and the function then goes through these steps:
多个子人类骨骼有时选择相同的转换作为他们最佳匹配的选择。例如人的骨头左边,有子人骨拇指近端,指端近端,中近端,环近端,和小近端。或许近端和近端都选择了被称为“Finger_2”的变换作为第一选择。现在该怎么办?同一个手指不可能同时是无名指和小指!对于人类的每一根骨头,我们所保留的不仅仅是第一选择,而是一份经过排序的选择清单。为了确保每个转换只映射到一个人的骨骼,并且以最好的方式将转换分配给人的骨骼,将调用一个名为GetBestChildMatchChoices的函数。首先,我们制作一个列表,其中包含当前每个人骨头的选择。最初它们都被设置为0(第一选项),即使这意味着存在潜在的冲突。我们将当前选择的列表传递给函数,函数会执行以下步骤:
- Check if any of the current choices have conflicting transform. If not, we are done and can return the current choices!
检查当前的任何选项是否具有冲突的转换。如果不是,我们就完成了,可以返回当前的选择! - Make a list of all the human bones that are part of the conflict.
把冲突中所有的人的骨头列个表。 - For each of those human bones, try out an alternative list of current choices where this human bone retains its current choice and all the other human bones in the conflict have to use their next choice on their priority lists. For each of those alternative lists of current choices, call the GetBestChildMatchChoices function. (Yes it’s a recursive function again.)
对于这些人骨中的每一根,尝试一个当前选择的替代列表,其中这个人骨保留当前选择,而冲突中的所有其他人骨必须在其优先列表中使用它们的下一个选择。对于当前选择的每个备选列表,调用GetBestChildMatchChoices函数。(是的,这又是一个递归函数。) - Each call of GetBestChildMatchChoices returns a new current choices list and a score value that is simply the summed scores of all the matches corresponding to the current choices.
GetBestChildMatchChoices的每次调用都返回一个新的当前选项列表和一个分数值,这个分数值仅仅是当前选项对应的所有匹配的分数之和。 - Choose the current choices list with the best score and return that list along with its score.
选择当前得分最高的选项列表,并返回该列表及其得分。
This procedure basically tests all permutations and chooses the best scoring one. Before I came up with this approach I had initially just implemented an approach where the human bone with the best scoring match got its first choice, the human bone with the next best scoring match would get its first choice excluding the already picked, and so on. This was faster computationally and easier to understand, but unfortunately it often resulted in incorrect results. The illustration below demonstrates the difference between picking the best individual match versus the best overall match for all siblings.
这个过程基本上测试所有的排列并选择最好的评分。在我提出这个方法之前,我最初只是实现了一种方法,在这种方法中,得分最高的人的骨头得到它的第一选择,得分次之的人的骨头得到它的第一选择,不包括已经被选中的,等等。这在计算上更快,也更容易理解,但不幸的是,它经常导致错误的结果。下图展示了为所有兄弟姐妹选择最佳个人匹配与选择最佳总体匹配之间的差异。
Optimizations
Since the search evaluates many hundreds of possible mappings for a typical avatar, the auto-mapping started to get a bit slow at some point. A lot of the time was spent with string handling related to trying to match keywords, but other parts of the evaluations also took up significant time. I realized that the same pair of human bone and transform was being evaluated many times as part of different possible mappings. The biggest optimization was achieved by caching the results of such an evaluation and simply use the cached result the next time the same pair needed to be evaluated. Different parts of the cached data needed to be cached differently because they had different amounts of needed context. A keyword evaluation only needs the human bone and transform themselves, but the evaluation of bone length ratios need the parent match and grandparent match as well, since they are used for calculating the lengths. In the end I used a design where I cache the result of an entire EvaluateBoneMatch call based on a pair and the parent and grandparent pairs. If a potential pair needs to be evaluated and the cache already has an existing evaluation for the same pair with the same parent and grandparent, that result is used and the call to EvaluateBoneMatch is skipped altogether for that pair. If it doesn’t exist, EvaluateBoneMatch is called to do the evaluation, but some of the sub-routines that evaluate keywords etc. use their own caching that requires less context and hence is more likely to have already been evaluated before. Using these cached result sped up the evaluation times by a factor of 8. After that, it was fast enough that the entire Auto-Setup process was no longer a bottleneck. (It usually takes less than a second which is a fraction of what the model import process takes.) Doing high-level optimization like this is often very satisfying since it can change the fundamental time complexity of an algorithm which can often result in very big improvements.
由于搜索会评估一个典型化身的数百个可能的映射,所以自动映射在某些时候会变得有点慢。很多时间花在与匹配关键字相关的字符串处理上,但是计算的其他部分也占用了大量时间。我意识到,作为不同可能映射的一部分,对相同的人类骨骼和转换进行了多次评估。最大的优化是通过缓存此类求值的结果来实现的,并在下一次需要计算相同的对时简单地使用缓存的结果。缓存数据的不同部分需要以不同的方式进行缓存,因为它们具有不同数量的所需上下文。关键字的求值只需要人的骨骼和自身的变换,但求骨长比还需要父匹配和祖父匹配,因为它们是用来计算长度的。最后,我使用了一种设计,在这种设计中,我缓存整个EvaluateBoneMatch调用的结果,该调用基于一对以及父和祖父对。如果需要计算一个潜在的对,并且缓存已经对具有相同父和祖父的相同对有一个现有的求值,那么将使用该结果,并对该对完全跳过求值bonematch的调用。如果它不存在,则调用EvaluateBoneMatch来执行评估,但是会调用一些子例程来评估关键字等等。使用他们自己的缓存,这需要更少的上下文,因此更有可能已经被评估过。使用这些缓存的结果可以将计算时间提高8倍。在那之后,它的速度很快,整个自动设置过程不再是瓶颈。(通常只需要不到一秒的时间,这只是模型导入过程所需时间的一小部分。)这样做高级的优化通常是非常令人满意的,因为它可以改变算法的基本时间复杂度,这通常会带来非常大的改进。
Special Handling
The Auto-Mapping algorithm was designed for to be primarily data-driven. I wanted to avoid a solution that had all kinds of hard-coded rules for different parts of the body, since code like that is often brittle and error-prone, and doesn’t lend itself to the kind of search-based solution I could see would be necessary for good results. Nevertheless, some compromises ended up being necessary. One compromise is that the search for the body mapping stops at the hands rather than including all the fingers. The fingers include more bones than the entire rest of the body, and excluding all that from the body mapping search reduced the search time significantly. Instead, the fingers are mapped by themselves starting from the hand bones found in the body mapping search. Another thing that’s handled in a special way is the assumption about body orientation. As mentioned earlier, the search algorithm takes hints from the directions of the various bones. However, sometimes a model is imported in Unity with a completely arbitrary rotation and all the assumptions about bone orientations are wrong. The remaining hints are usually enough to map some parts of the body well enough, but the fingers, if present, are often mapped completely wrong in those cases. To counter this we check the positions of left and right hip and shoulder after a mapping is completed, and derive the body orientation from those positions. If the body orientation is significantly different than the assumed one where +z is forward and +y is up, then we redo the entire mapping based on the now known actual orientation. In this second pass the bones are usually mapped with much higher rate of correctness.
自动映射算法主要是为数据驱动而设计的。我想要避免一种解决方案,这种解决方案对身体的不同部分都有各种硬编码的规则,因为这样的代码通常很脆弱,容易出错,而且不适合我所看到的那种基于搜索的解决方案,这对于获得良好的结果是必要的。然而,一些妥协最终是必要的。一种折衷方案是,对身体映射的研究仅限于手部,而不是包括所有的手指。手指包含的骨头比身体的其他部分都多,排除所有这些,从身体映射搜索中大大减少了搜索时间。相反,手指是由它们自己绘制的,从在人体测绘搜索中发现的手部骨骼开始。另一种特殊的处理方法是关于身体方向的假设。如前所述,搜索算法从不同骨骼的方向获取提示。然而,有时模型是在完全任意旋转的情况下导入的,所有关于骨骼方向的假设都是错误的。剩下的提示通常足以很好地映射身体的某些部分,但是如果手指存在,那么在这些情况下通常是完全错误的。为了解决这个问题,我们在完成映射后检查左右髋部和肩部的位置,并从这些位置推导出身体的方向。如果主体的方向与假设的+z向前+y向上的方向明显不同,那么我们将根据现在已知的实际方向重新进行整个映射。在这第二次扫描中,骨骼的正确率通常要高得多。
Testing Framework
While implementing the automatic setup I also created a function for automatically testing and validating the automatic setup for a model. Every time we have come across a model where the auto-setup fails, we have added it to the testing framework, provided a sensible setup is possible to do manually in the first place. (Some models don’t have a proper skeletal hierarchy at all. Those are not compatible with Mecanim humanoid animation, so obviously auto-mapping won’t work for those.) At this time we have around 30 models in the framework, which include a lots of humans with different proportions as well as some fantasy and toon characters with completely different proportions and a few robots. Among them the models have wildly varying rigs. While the auto-mapping is based on a solid algorithm, there is a lot of tweaking involved in determining which hints should contribute how much in the scoring. Having the testing framework has been essential to being able to tweak those parameters and be able to immediately test that it fixes the intended edge cases without causing regressions for other characters. If you have any characters that you CAN do a manual setup for, but which the automatic setup did not handle satisfactory, you are welcome to send the models in question in a bug report. Include the words “avatar auto-mapping” in the first line of the description. We can’t guarantee that we’ll be able to make the auto-setup handle every single model correctly, but being aware of problematic cases provides us with a better basis for attempting it.
在实现自动设置时,我还创建了一个函数来自动测试和验证模型的自动设置。每当遇到自动设置失败的模型时,我们都会将其添加到测试框架中,前提是首先可以手动进行合理的设置。(有些模型根本没有合适的骨架层次结构。它们与Mecanim类人动画不兼容,所以显然自动映射对它们不起作用。目前我们的框架中大概有30个模型,其中包括很多比例不同的人类,以及一些比例完全不同的奇幻卡通人物和一些机器人。其中的模型有各种各样的钻机。虽然自动映射是基于一个可靠的算法,但在确定哪些提示应该在评分中占多大比例时,需要进行大量调整。其中的模型有各种各样的钻机。虽然自动映射是基于一个可靠的算法,但在确定哪些提示应该在评分中占多大比例时,需要进行大量调整。拥有测试框架对于能够调整这些参数以及能够立即测试它是否修复了预期的边界情况而不会导致其他字符的回归至关重要。如果您有任何可以手动设置的字符,但是自动设置不能处理满意的字符,那么欢迎您在错误报告中发送有问题的模型。在描述的第一行写上“avatar auto-mapping”。我们不能保证自动设置能够正确地处理每个模型,但是了解有问题的情况可以为尝试提供更好的基础。
Conclusion
I’ve covered the primary functionality of the Auto-Mapping for humanoid characters in Unity. The Auto-Setup includes other functions as well, such as getting the character into T-pose, but those are outside of the scope for this post, and not quite as interesting and challenging anyway. The Auto-Mapping is an interesting feature in that it contains quite advanced functionality yet is practically invisible to the user. It’s very purpose is to be something you don’t have to think about at all. Of course it doesn’t always work out that way in all cases – for some models here and there the automatic mapping fails and then you’re suddenly painfully aware of it. But for the majority of cases, the Humanoid Animation Type is just a setting you can enable, and then not have to think about. Instead you can get on with the real fun of animating your characters.
我已经介绍了Unity中类人角色自动映射的主要功能。自动设置还包括其他功能,比如让角色进入t型位,但这些都不在本文的讨论范围之内,而且也没有那么有趣和具有挑战性。自动映射是一个有趣的特性,因为它包含相当高级的功能,但实际上对用户是不可见的。它的目的就是成为你根本不用去想的东西。当然,并不是所有情况下都是这样——对于一些模型,自动映射会失败,然后你会突然痛苦地意识到这一点。但在大多数情况下,类人动画类型只是一个您可以启用的设置,然后不必考虑。相反,你可以继续你的角色动画的真正乐趣。