了解一下COLLADA

COLLADA, Skeletal Animation, C++, OpenGL


简化翻译一下这篇介绍COLLADA的教程,作者废话很多。。。。删减一些,尽量保留-。-

适合阅读这个教程的人:程序员(不是美术同学),熟悉COLLADA结构以将COLLADA模型加载进自己的引擎里使用。语言是C++,图形库是OpenGL。

此教程涉及的内容:
一步步的学习如何从COLLADA文件里读取几何形状,骨骼和动画数据。怎样用骨骼动画让你的角色动起来(C++,OpenGL)。这篇文章并没有囊括所有的COLLADA知识点。为了对COLLADA文件格式更容易理解,将会从一些假设开始。也不会涉及到shader和FX的东西。

需要的一些前置知识:读懂这篇文章需要一些编程功底,会C++。熟悉OpenGL更好但不是必须项。但是需要知道向量是什么,知道纹理坐标,知道几何形状和动画,什么是XML格式等图形学的一些知识。

需要做哪些准备来学习:

C++编译器和OpenGL环境。
可以从www.COLLADA.org找到一些COLLADA的例子。现在这个网站好像不用了,会跳转到https://www.khronos.org/collada/

作者介绍了他之前想要解析3D模型的坎坷经历(略!)没有太多资料解析MAX和MAYA文件,很难将MAX和MAYA文件导出到其他的应用去使用。

解决方案就是COLADA。跨平台,用XML以UTF-8格式保存。想关注和详细了解就去官网(上面有)

作者发现官网上有可用的COLLADA DOM。可以直接用自己的代码从DOM格式提取所有的COLLADA文件信息然后导出成自己想要的格式。但是也需要理解COLLADA的格式,而且DOM也有一些问题。比如纹理坐标的读取(s,t),只有两个元素,但是MAX默认导出的COLLADA文件格式却是(s,t,p)。所有最后作者决定自己写一个解析COLLADA数据的解析工具,而且没有看起来那么难。

作者写的时候发现找不着一个入门的教程(直接去啃文档太心酸的感觉),所以写了这个教程,心路历程到此

第1部分:

阅读并理解COLLADA文档

前面引言的部分已经讨论过了,这篇教程分为两个部分,第一部分简单介绍一下COLLADA文档并且不涉及任何的编程语言。如果你想要跳过这部分直接到实现的部分,非常可能会有很多难理解的东西进而无法顺利进行。所以,对于不熟悉COLLADA文档的人,强烈推荐先理解第一部分,然后进行第二部分。

COLLADA文档
在我们深挖COLLADA文档之前,建议你先下载我们教程始终要用到的例子。在COLLADA模型资源库里也以COLLADA文档的形式存在。叫做“astroBoy_walk.dae”如果你哪里也找不到。这篇教程网站的下载区里也有。

就像我们前边提到的(如果你没有读前边的介绍部分)COLLADA文档以XML的格式出现。如果你打开astroBoy_walk.dae,从现在开始我们称它为示例文档,在你最喜欢的文本工具里,你会看到根节点为“COLLADA”,如果你的文本编辑器支持扩展和编辑XML节点(如果是Notpad++可以设置语言类型为XML来打开XML的编辑功能)操作一下就能发现COLLADA节点为根节点。。。。

图表1:COLLADA文档的简短浏览

这个.dae或者.xml文件的节点下,你能发现很多其他的库用来保存不同类型的信息。<library_geometries>标签用来保存几何形状数据,<library_lights>用来保存场景的光照信息。如表1,它并不是火箭工程。在那些库里你可以发现实际的数据。例如一个几何体库包含的节点light的库里包含light的节点,可能有多个节点来保存这些结合体和光照信息。接下来我们一个一个的来分析这些库。通过将这些数据分析来解析COLLADA数据。

我们先来让整个过程变得简单一点。前边已经说过,这里不会全面具体的讨论COLLADA文档的内容;我们试着以做一些猜想的方式来将其复杂性变得简单一点。

列出猜想:

  1. 虽然Max和Maya的COLLADA文档应该是相同的格式和内容,但是会有一些原因导致他们有点不一样。我们将只讨论Max导出的COLLADA数据,相信这些分析对于从Maya导出数据也会有帮助的。我始终觉得Maya导出的COLLADA文档应该是相同的,如果导出的时候在COLLADA导出设置面板设置“backed matrices”和“triangulate options”为"checked"。但是我没有用过Maya所以不知道导出工具在哪里可能出错。
  2. The COLLADA document must have at least and at most a mesh, which means, anything in the asset's Max file, should be attached. So we must not have more then one <mesh> in the <library_geometries> in the COLLADA document. If we are able to read one <mesh> then we can read a 10000 too.
    
  3. COLLADA文档肯定最少并且最多有一个网格,就是说Max文件里的所有资源都应该是有attached的。所以COLLADA文档<library_geometries>的<mesh>里不能超过一个。如果能读一个,就能读10000个。
    
  4. Geometry in COLLADA should be triangulated, since that’s the better (If not best) option, we can provide OpenGL, so we let the triangulation work done by Max.
    
  5. 几何形状应该是三角形
    
  6. Later in the implementations part we will assume our Model which was exported to COLLADA document has only One Texture file.
    
  7. 后面的实现部分我们假设导出为COLLADA文档的模型只用一个纹理文件。
    
  8. Animations in COLLADA must have at least or at most one Skeleton, with only one Root Bone (Typical). And I think that’s why we are here, to implement skeletal animation.
    
  9. 动画必须最少或者最多有一个骨骼,只有一个根骨骼(主要的)。实现骨骼动画,就是我们这篇教程的主要目的。
    
  10. Animation exported to COLLADA must be baked in matrices, which essentially in some cases makes 1 channel of animation and in others 16 channels of animation (Now what is channel? It should be explained later).
    
  11. 导出为COLLADA的动画必须以矩阵的形式烘焙,本质上是1通道的动画,而其他的动画为16通道的动画(什么是通道在后面会解释)
    
  12. Animations can only be valid if the channel targets the "Transform" of the targeted entity, just to keep things clear and easy. When you will bake matrices, then you will have this automatically, so don't need to worry about that.
    
  13. 为了确保动画更简单和清晰的表示出来,动画必须以一种“通道对象”被实施“变换”的模式呈现,(如果理解不了,这些是自动发生的)当你烘焙变换矩阵的时候,这些就会自动的发生了,所以不用太担心。
    
  14. Animations can't have nested animations.
    
  15. 动画里不能再嵌套动画。
    
  16. Only Skeletal Animation is supported (No baked animation yet).
    
  17. 只支持骨骼动画(不支持帧动画)
    
  18. Every bone in the hierarchy must be attached as effecter on the skin. In other words, it must be added to the skin.
  19. 层级中的每个骨头都必须作为“影响者”关联上皮肤。也就是说,每个骨头都必须被添加到皮肤上。

Keeping those assumptions in mind all the times, let’s start explaining different libraries in their related sections. You might also find it easy and handy to switch to Implementation part for each section immediately, when you read that section, before you finish reading the first part completely. The link to the implementation of each section is given at the end of that section.
将这些猜想记住,让我们开始解释不同库对应的猜想。当你看实现部分的时候,或许每个部分对应的猜想项很容易被发现。实现部分和对应的每个猜想会在该实现部分的末尾标出。

Reading Geometry Data from COLLADA document
<library_geometries>
This is by far the most important library of all of the COLLADA libraries; if you have a character to animate we need its geometric data, which is provided in <library_geometries>.
This library contains type nodes which contain separate geometries in the scene, keep in mind COLLADA is not just an asset file format rather you can put a complete scene and many scenes in them. Like we assumed we are only concerned with one node which will have one node. So if so far you have found that node lets analyze it.

从COLLADA文件读取几何数据
<library_geometries>
这是目前来说COLLADA库中最重要的库;如果要将一个角色做成动画,就需要他的几何信息的数据,就会在这个库里提供。
这个库包含类型的节点,包含场景中分离的几何体,要知道COLLADA不只是一个资源的文件格式,你甚至可以将一个完整的场景和许多场景放进去。就像我们猜想的我们只关心一个节点,就是包含一个节点的节点。所以如果你发现了它,让我们开始分析。

"
Mesh is the node where we will find our geometry data. If you try to analyze the node you will see further at least 1 or 2 nodes, which, depending on its type, gives information about Vertices, Normals and Texture Coordinates etc. In the example file (if you have downloaded from COLLADA.org, it will not have backed animation in it, so you have to import it in Max and then export it back with Backed Matrices and “triangulate” checkbox checked as shown in figure 2), you will find 3 nodes and we are lucky that each source node is defined in the same way in COLLADA.

"
Mesh是我们可以找到几何数据的节点。如果要分析mesh,将至少查看1到2个“source”节点,取决于mesh类型,包含顶点,法线,纹理贴图等信息。在示例文件里(如果你是从COLLADA.org下载的,文件不包含反向动画,所以你需要将它导入Max然后以“反向矩阵”,并且勾上对话框的“triangulate”选项,图表2,导出)
,里面有3个“source”节点,并且很幸运,每个source节点都以同样的格式定义。

Figure 2: Export Options from COLLADAMax
图表2:从Max导出COLLADA的设置

"
Remember all these XML nodes discussed so far have an ID associated with them which is used for locating the Node in COLLADA document when referred from any place. And Source Nodes are not any exception to that. Now can have many children nodes but the most important ones are <float_array> or <NAME_array> and <technique_common>.

source
需要知道的是,目前为止提到的所有的xml节点都有一个对应的ID,用来定义其在COLLADA文件中的位置,其他地方可以用这个ID来引用节点。source节点也不例外。source可以包含很多子节点,但是最重要的是“float_array”或者“NAME_array”和“technique_common”。

As the names suggests <float_array> contains floats which can be used for different purposes which are described by <technique_common> of that source and the difference between <float_array> and <NAME_array> is that, the former contains floats and the later contains strings.
同名字所描述的,<floadt_array>包含浮点数。source的<technique_common>节点用其数据来描述不同的东西。<float_array>和<NAME_array>的不同在于,前者包含浮点数,后者包含字符串。

Now we use the <technique_common>'s child node to specify what kind of data is in those arrays <float_array>, <NAME_array> or any other type of many types of arrays. node has a “source” attribute which says “What kind of “array” are we talking about?”. And a “count” attribute says how many elements we have in the “array”. The “stride” attribute says after how many values the next element starts.
I hope I am not talking Chinese but let’s explain it with a figure and example COLLADA source.

现在我们用<technique_common>的子节点来指定其中的数据结构。节点包含"source"属性来指明“将要包括一些什么样的数组”。“count”属性指明“数组里有多少个元素”。“stride”属性指明下一个元素从什么位置开始。我希望我不是在讲汉语(为什么不讲汉语。。)让我们用一个图表和COLLADA source的例子来说明一下.

"
"<float_array id=“values” count=“6”> 0.3 0.5 0.7 0.2 0.4 0.6 </float_array>
"<technique_common>
"
"
"
"
"
"</technique_common>
"

Figure 3: Structure of a source
表3:一个source的结构

As you can see in figure 3, there are float array’s (count=“6”) values which are 3 (x, y, z) float components of 's (count=“2”) number of vertices. And when we have 3 child nodes in then we have 3 (x, y, z) components in each Vertex (3D vertex) (it can also be 3 Components of Normal, or 3 Components of Texture Coordinates). This information is very important since I can’t find it in COLLADA Specifications and it took me a lot of time to understand this concept (may be I am dumb) ? So if you don’t understand, please read again.

在表3里,有含6个浮点数的数组(count=“6”),节点定义了两个(count=“2”)顶点数据(每个数据均为xyz,3个浮点数)。节点有3个数据,3d顶点的(x,y,z)部分(也可以理解为法线,或者纹理的采样坐标)我在COLLADA说明里无法找到这种数据的说明,花了很多时间去搞清楚这个概念(也许是我太愚笨了–其实这是图形学渲染的概念,了解渲染过程的应该很容易理解,也不是什么难理解的东西,知道就哦了)

In short, this source says, “I have 2 vertices with 3 components each, which are saved in <floats_array> as 6 float values”. Components are called “X”, “Y” and “Z”. And they are float type values. If we had a which saves texture coordinates, then those components would be “S”, “T” and “P”.

简单的来说,这个source的意义为,“包含两个成员分别为3个数据的顶点数据,用<floats_array>保存的6个浮点值”。数据分量为“X”,“Y”,“Z”。如果保存纹理坐标的话,分量的名字应该是“S”,“T”和“P”

So that’s all a source is about. Now as we discussed before there are 3 nodes in the example COLLADA document. And as you might have guessed already, the other two sources are for Normals and Texture Coordinates, if you have exported your Model with other attributes then you will have more nodes, like bitangents and tangets etc.

以上就是所有的source相关的东西。同上面我们谈到的一样的结构,示例COLLADA文档里有3个节点。也许你已经能够猜到,另外两个source分别是发现和纹理坐标,如果导出的模型属性还包含其他的数据,就会有更多的节点,例如双切线和切线等等。

Now when we are able to decode s, we still can’t just decide on the order of those sources which one is Vertices and which one is Normals etc. we have to read one other child of which is called to find the vertices source, although I really don’t understand the reason why they do this in COLLADA but for the sake of completeness you have to read this Node it has at least one Child node called with a semantic attribute of “POSITIONS” value, which references the vertices with another source name/id. And then you refer to this ID when ever you need the vertices source. If you don’t understand these sections then skip to the next section and you hopefully will understand.

现在我们准备好了去解析各个了,但是我们仍然无法决定哪些source是顶点数据,哪些是法线等其他数据。可以从的子节点来找到顶点source,虽然我实在不理解COLLADA为什么这么设计(作者的感慨),但是为了获得完整的信息你必须查看这个节点,他最少包含一个子节点和其属性“POSITIONS”值,引用了顶点的名称/id。根据该id确定了POSITION的值。如果你不理解此小节的内容可以跳到下一章节,比这一节好理解的多。

Now as we have assumed we are only considering COLLADA documents triangulated so you will only see types nodes as children of otherwise you might see etc nodes, which we are least concerned with.

"
就像我们假设过的,只考虑COLLADA文档里存在三角形模式的节点,作为的子节点出现。否则你将会看到什么似的复合形状的节点,现在我们不需要担心了(全是三角形)

This node tells all the information we need to make a triangle out of those 3 sources (in this case) which we read earlier. A node says how many triangles we have in this node with a “count” attribute and also lists “material” attribute which is used to find the material from the <library_material> and that material is used to render the list of triangles from this node. So you might see many triangle groups in one Mesh, which are separated by Materials. So we have to read all the nodes.

·节点描述了通过前边读到的3个sources(这个例子来说)的信息来生成一个三角形。一个节点通过“count”属性来描述三角形个数,并且列出“material”属性用来从<library_material>寻找material去作用于节点里三角形的渲染。所以一个Mesh里可能会有多个三角形的组合,这些三角形通过Materials来区分。所以必须了解所有的节点。

To decode nodes we have to read its children in which and

are the most important. The number of nodes says the number of attributes per vertex. And

has the indices of those attributes in their corresponding nodes. Let’s see an example

解析节点的时候,最重要的两个节点可以说是和

。的数量描述了每个顶点数据属性的数量。

指明了那些属性在对应的序数。看一个例子

·
·
·
·

·






·<·p>
0 0 1 3 2 1
0 0 2 1 3 2
·<·/p>

http://www.wazim.com/Images/Triangles Node.jpg

Figure 4: Structure of a Triangle
表4:三角形的结构

As you can see from the above example node is renaming the “position” source with “verts” and then defining the triangles vertices source with “verts” name. So this is why we need to read node to find the real position from the s.

从上面的例子可以看到节点将“position”重命名为“verts”,然后用“verts”这个名字定义三角形的顶点source。所以这就是我们为什么要了解节点来找到里真正的position信息。

If you read the Children of node you can see that it has 3 nodes with values of “VERTEX” “NORMAL” and “TEXCOORD” for its semantic attribute. This essentially means that our triangles have 3 attributes per vertex, first Vertex Position, second Vertex Normal and third Vertex Texture Coordinate.
And how we know which one is first in the list of indices in

? We can see that

如果你看到了的子节点们,会发现3个节点,值分别为“VERTEX”、“NORMAL”和“TEXCOORD”,是他们的属性名称。即是说三角形每个顶点拥有三个属性,第一个为顶点位置,第二个为顶点法线,第三个是顶点纹理坐标。
那么我们怎么知道在

节点的序列表里哪个是第一个呢?来看一下:

node with semantic = “VERTEX” has offset = “0”,
node with semantic = “NORMAL” has offset = “1” and
node with semantic = “TEXCOORD” has offset = “2”.

So when ever we will read values from

for each triangle’s each vertex,
The first one will be the Index of “VERTEX” Position from the “positions” ,
The second one will be the index of the “NORMAL” from the “normal” and
The third one is the index of “TEXCOORD” from the “textureCoords” .

所以根据这些内容,无论什么时候,当我们从

节点查看每个三角形的顶点信息时,第一个是“VERTEX”,引用自的“positions”,第二个是“normal”,引用自的“normal”,第三个是“TEXCOORD”,引自的“textureCoords”。

Now one thing I would like to make clear here is, that all these values we read from

are “indices” not actual values. And all the data for all the triangles are saved as indexed data to save space in case of repetitions of attributes data. To find the real data we have to refer to the corresponding s and pick the corresponding data at that particular index.

现在要搞清楚的一件事情是,所有我们从

节点里读到的这些“indices”数据不是真实的值。所有这些三角形的数据是为了节省空间而保存为属性数据的序数的形式。真实的数据我需要根据这些序数从对应的里找到对应的具体的值。

Making Triangles is very easy now. All you have to do is keep on reading 3 * (Number of Input Nodes in ) values from

and read the corresponding attribute values from the corresponding s. If we have only one attribute per vertex for any number of triangles then we will have the following Node, with only one child. In this case all you have to do to read the triangle is to keep on reading 3 values from

and read the corresponding vertex values from the corresponding “verts” source

到这里生成三角形已经近在咫尺了。剩下的就是继续从

标签读取3个(节点里指定的数量)值并且从里找到对应的属性值。如果三角形的每个顶点只有一个属性,那么将会只有一个子节点。这种情况解析三角形需要做的就是继续从

节点读取3个值然后从对应的“verts” source读取对应的顶点值。

·

<·p>
0 3 2
0 2 1
<·/p>

One thing we still need to know is the “material” attribute of a node. This attribute references the material used from the "library_materials> which will we discuss later in the tutorial.

剩下的需要知道的是一个节点里的“material”属性。这个属性引用了<library_materials>里的material,将在后边的教程里讨论。

That’s all for the geometry data. If you understand this part correctly I hope you will have no problems going on from here onwards. Other wise go back and read again until you understand completely. Now if you directly want to jump to the implementation part (part 2) of the tutorial, you should be able to read and display static 3D objects in your engine. And if you want to render them with Materials and texture maps as well as animate them, you will have to keep on reading this part of the tutorial completely.

这就是所有的几何体数据了。如果正确的理解了这部分,希望接下来的也没有问题。否则回去再看两遍直到完全理解了上边的内容。现在如果你直接跳到教程的实现部分(part 2),你可以在引擎里读取和显示静态的3D物体。但是如果想要结合相应的材料和纹理贴图渲染并且让他们动起来的话,需要继续完成全部的教程。

Click here to go to: Implementation section in part 2, for this section of part 1

Reading Texture filename from COLLADA document
As you know we took some assumptions in the beginning, one of which was only one texture file can be used in the COLLADA document. This will make finding the file name very easy.

从COLLADA文档读取纹理文件名称
在开始部分的设想里,有一个是在COLLADA文档里只能使用一个纹理文件。这样能够很容易的找到该文件。

All we have to do is to read the <library_images> and read the “id” attribute of the “Only” node in it. Usually that will be the file name of the texture file used in COLLADA. But it might not be correct file name, and COLLADA might create that ID different then the file name. So, to correctly read the file name we must read the <init_from> child node of node, which gives the whole path, with file name. For our export purposes I am only interested in the file name, not the whole path, so I will tokenize the full path and save the file name only.

只需要读取<library_images>并且读取“唯一的”标签的“id”属性。一般将会是COLLADA的纹理文件名称。但是有可能不是正确的文件名称,COLLADA可能创建id的时候改变了文件名称。所以,为了正确的读取文件名称,得读取<init_from>子节点的节点,该节点给出了文件名称的完整路径。为了输出的话,只需要文件名称就行了,不需要具体的路径,所以需要截取完整路径,只保留文件名称。

Click here to go to: Implementation section in part 2, for this section of par 1

Reading Materials from COLLADA document
从COLLADA文档读取材料
We discussed in the section “Reading Geometry Data from COLLADA document” that each triangle group is separated by a “material” and the Material ID is the value of the “material” attribute in node. Now to find those materials with those IDs we have to read <library_materials>. In <library_materials> you will find nodes with those IDs which were referenced from nodes. But unfortunately those nodes have only one type of child named <instance_effect> which has one attributes called “url”. All what its saying is that this specific node is referring to an effect from the <library_effects>, which in turn defines the material completely.

我们在“从COLLADA文档读取几何数据”章节讨论了每个三角形数据组被一个“material”隔开,Material的ID为节点“material”属性的值。现在从<library_materials>里通过这些ID来寻找materials。在<library_materials>里能找到里通过ID引用的。但很不幸,这些节点只有一种类型的子节点叫做<instance_effect>,其包含一个“url”的属性。这就代表涉及到<library_effects>里的一个effect,该effect相应地完整定义了对应的material。

So we take the value of this “url” attribute for this specific and find it in <library_effects>, now unfortunately this library <library_effects> is the most complicated library of COLLADA as much as I know. It can get very complicated when shaders and what not is included in the COLLADA document. But since I promised to keep things clear and easy, we will only read the data that we desperately need for defining a material.

所以指定的要根据“url”属性的值从<library_effects>里寻找,不幸的是这个<library_effects>库是我知道的COLLADA里最复杂的库。它可能会包含很多shaders啊等不属于COLLADA文档定义的复杂的内容。但我承诺过要把事情解释的简单和清楚,所以我们只分析我们用来定义material需要用到的数据。

Once we have found the node with id of the value of the “url” attribute for any material, we have to find either or node in <profile_COMMON> child node of the node. or are usually inside child node of <profile_COMMON>. Once we have found either or keep looking for all the parameters of the Material we are looking for, like “ambient” “diffuse” “specular” “emission” “shininess” and “transparency” etc. what ever you need for the material to look good. Usually “diffuse” “shininess” and “transparency” is enough to create a good looking material.

当使用id值的“rul”属性在里找material的时候,必须先找到节点的子节点<profile_COMMON>里的或者。一般在<profile_COMMON>的里。当找到无论哪个以后,接着去找我们要找的所有的材料的属性,像“ambient(环境)”、“diffuse(漫反射)”、“specular(镜面反射)”、“emission(发散)”、“shininess(发光度)”、“transparency(透明度)”等等。通常“diffuse”、“shininess”和“transparency”就足够创建一个看起来很不错的材料了。

How can we read the data from those nodes is very easy? Usually ambient, emission, diffuse and specular nodes has 4 float values, inside a child node, which corresponds to “RGBA” components of that particular material’s property, while reflectivity and transparency etc have 1 float value.

怎样从那些节点读取数据会非常简单呢?通常来说,ambient、emission、diffuse和specular节点都有4个浮点数值,在一个的子节点里,对应该材料的“RGBA”属性。reflectivity和transparency等一些属性为1个浮点值。

<*ambient>
1.0 1.0 1.0 1.0

<*transparency>
0.5

If we have placed a texture map on the diffuse color of the material then will not have a child, rather that texture image. But for the sake of ease we will not worry about this and assume the texture map is always applied on the component of the material, which means we will not be reading values from COLLADA. But we will be using a default value for diffuse inside our OpenGL implementation.

如果已经用一张纹理贴图放置在材料的漫射颜色项,那就没有子节点了,而是那个纹理图像。为了简单些我们假设材料的部分始终为一个纹理贴图,代表我们不需要从COLLADA读取数据。在OpenGL的实现里diffuse将使用一个默认的值。

That’s all what we need for any static geometry. So if you are only interested in reading Static geometry from COLLADA you can stop reading this part and jump to the implementation. Other wise keep on reading for extracting animation data from COLLADA documents.

这些就是一个静态的几何体需要的所有数据了。如果只是从COLLADA文档读取静态的几何数据,到这里就可以停了,可以跳过这部分去继续实现部分。继续下面的内容,就是提取COLLADA文档的动画数据。

Click here to go to: Implementation section in part 2, for this section of part 1

Reading Skeleton from COLLADA document

从COLLADA文档读取骨骼动画

We assumed that we will only read COLLADA documents with skeletal animations, and not those with backed animations, so we have to read the skeleton from the COLLADA document. By skeleton, I mean reading the Joints (Bones) information. We also have to read the hierarchy of the Joints. This will tell us, who are the child of whom! And who is the parent of a joint etc. In the following figure all these terminologies are explained. Remember that Bones and Joints are One and the same thing, they are just convenience names, and the data that we read from COLLADA is actually a Joint, a Bone is an imaginary line connecting two Joints.

我们假设只读取COLLADA文档的骨骼动画情况,不是硬动画,所以必须从COLLADA文档读取骨骼信息

Figure 5: Skeleton Terminologies

In the following figure you can see the skeleton of our example file and the Skin attached to it.

Figure 6: Complete Character in one pose of animation

The red circles on the left in figure 5 are the joints we read from COLLADA and the lines connecting those circles are the imaginary bones, which are used to animate the skin. On the right you can see the skin attached to the skeleton in another frame.

You might remember we took some assumptions, one of which was that, all the joints are added to the skin, which will make your <library_visual_scenes> very simple to read. All you have to do is find the root Joint (bone) of the skeleton in the visual scene and then read the whole tree of joints. One of the disadvantages with this is that you will have a lot of joints considered affecting the skin, but in real they will not have any effect on the skin. And if you don’t add all the bones to the skin, then you will see s of type = “JOINT” and type =“NODE” mixed in the hierarchy. But if you add every bone to skin you will have full tree of type="JOINT"s only. This is also the default behavior for many engine exporters. When you have s with type=“JOINT” and type=“NODE” mixed in the hierarchy then you have to read <instance_controller> from <library_visual_scenes> and then read each time you have to read a joint. Those s which are not of type=“JOINT” are still Joints but they are not effectors, which means they are not effecting the skin. And that’s why we assumed everything must be attached to the skin, to keep things easy and simple.

To read the hierarchy of the bones, you need to have a data structure which can hold a number of children and a parent of its own kind. (This will get clear in the implementation part). You might also need to save the “SID” attribute of s. Once we have the data structure setup, we will find the root and start reading its children and then their children and so on (recursively) and keep saving them in the data structure. At the end of the whole read process your whole data structure should be able to answer questions like, which joint is the child of whom? And who is the parent of a joint.

Now how can we find the root joint of the skeleton? Since we know we only have one model in the COLLADA document so we don’t have to read the node to find where the scene is instanced from. We directly go to <library_visual_scenes> and for all the direct (immediate) children of the only <visual_scene> we have to find the who has a children of type <instance_controller>, read the child of this <instance_controller> and that will give you the ID of the root node. Since we have added all the bones to the skin, we will only have one child of <instance_controller>. Now this node is our root of the skeleton and everything connected to this is part of the skeleton.

If you see the structure of this in COLLADA document you will see most of the nodes have as the first child. And this contains 16 float values, which makes the Joint Matrix of the bone. This is also called the local bone transformation matrix. When we connect all the joints we have to multiply the World Matrix of the parent to the child’s Joint Matrix and save it as world bone transformation matrix for the child. For the root Joint, who doesn’t have a parent, the Joint Matrix becomes the World transformation matrix.

By now you should be able to read the skeleton and derive the bind pose of the skeleton from the Joint matrices you read for each . In the next section we read the skinning information which connects this skeleton with the skin.
Click here to go to: Implementation section in part 2, for this section of part 1

Reading Skinning information from COLLADA document
So far we have read the geometry (vertices attributes information, materials, texture filename) as well as skeleton of the model. What we need to know now is how this skeleton is connected to the skin (Geometry). We have read many Joints in the skeleton. But we still don’t know which Joint influence which vertex. Some of the joints might not even influence any vertex at all. But if you remember we took an assumption that all the Joints must be added to the skin. In that case every joint must be influencing the skin theoretically.

To properly connect the skin (geometry) to the skeleton we need skinning information, and this section will try to help you understand, where we can find skinning information in COLLADA?

There is one other thing I would like to explain before we go further. If we have a character who’s each vertex is connected to only one Joint. When ever that Joint moves the vertex connected to it must also move. And you will see very rigid animation. But this is not how things work. Almost every vertex is connected to more then one Joint. And we specify the influence of each joint on that vertex with the help of vertex weights. Each joint influences that particular vertex some percentage of the total influence which totals to 1. So vertex weights are very important part of the skinning information.

<library_controllers>
<library_controllers> contains all the influences (joints) and their connectivity through vertex weights for the whole model. According to our assumptions we only have one mesh and one skeleton. So we will have only one node in the list of children of <library_controllers>. Once we have found the one and only node, we have to find its child node . In the node, find the whose attribute’s "name"s value is “JOINT” in the child node of the child of the <technique_common>, (I will not explain all this again since we have already decoded nodes when we were reading Geometry data) and the <NAME_array> will give you the Names of all the joints in the skeleton. Now you know that you can find the number of bones used from the “count” attribute of the <NAME_array> node in this source. An example is given as follows.

Bone1 Bone2 Bone3 Bone4 Bone5

And if you go back and see your skeleton s from <library_visual_scene>, you will see that all these names of Joints you read from <NAME_array> are actually the SID’s of these s.

To properly read the skinning data, we first need to read the Bind shape matrix of the skin to the skeleton, which is usually the first child of node, if it’s not the first child we will iterate through all the children and find and save it. And then we start reading from the node called <vertex_weights> who’s “count” attribute’s value gives the number of weights, as far as I know this count must be equal to number of vertices in the model which we read earlier when we were reading Geometry data because we have to define the vertex weights for each and every vertex at least and at most once.

If you see the structure of the <vertex_weights> node you will see at least 2 nodes, one with attribute semantic=“JOINT” and second with semantic=“WEIGHT”, One node and one node.

When we have to read the weights for each vertex we iterate <vertex_weight>'s attribute “count” number of times into the node values. And each value from the is the number of Joints affecting that particular vertex, on which we are currently iterating. So we iterate nested for that specific value in the (number of joints time) and read pairs of indices from (Here I assume we have only two nodes in <vertex_weight>).
The first one index the “JOINT” name in the “JOINT” 's <NAME_array> (Here I assume that the value of “offset” attribute of the who’s semantic=“JOINT” is “0”), we mentioned how to find this source earlier as well, but here you can get the ID of this source from the “source” attribute of the child of the <vertex_weight> node with semantic=“JOINT”.
And the second one index the weight value from the who’s ID you can get from the “source” attribute of the child of the <vertex_weight> node with semantic=“WEIGHT” (Here I assume that the value of “offset” attribute of the who’s semantic=“WEIGHT” is “1”).

<vertex_weights count=“4”>


3 2 2 3

1 0 0 1 1 2
1 3 1 4
1 3 2 4
1 0 3 1 2 2

</vertex_weights>

In this example you can see that this <vertex_weight> node is defining weights (influences) for 4 vertices, first vertex has 3 influences, First vertex’s first influence’s joint index is 1 which is the index from the JOINTS s <NAME_array> values. And its weight index in the <float_array> of the of weights is 0.

There is one other very important child of node which is called and it usually have two nodes, the first one with attribute semantic=“JOINT” references the node with Joint names, through the “source” attribute. And the second with semantic=“INV_BIND_MATRIX” references the source with inverse bind matrices for each Joint through the attribute “source”. The source with inverse bind matrices contains (Joints_count * 16) values which makes Joints_count inverse bind matrices. These inverse bind matrices are needed for skinning. And will be clear when we read the implementation part.

Once we have completely read the node we should have one Bind shape matrix, a number of Joints and their Inverse bind matrices, and we have read their Joint matrices from the <visual_scene> earlier. Each vertex must be influenced by one or more then one bone (Remember this is contrary to Each Joint must be influencing at least one or more Vertex, which is not true, since their might be Joints, influencing no vertices). And we must have their weights accordingly.

Now if you have come this far, you should be able to read the geometry data, as well as the skeleton and skinning data from COLLADA documents. And you should be able to draw the model in raw triangles, as well as draw the skeleton. Although I haven’t discussed how you can accumulate the world matrices for each joint and then draw in world coordinates for debugging purposes but I think I gave a hint that we have to multiply parent joint’s world matrix with current joint’s Joint matrix and save the result in current joint’s world matrix. We have to start this process from the root bone. So that we don’t have dirty world matrices from parents, and the root Joint’s world matrix becomes the Joint matrix, since root don’t have any parent. If you are also reading the COLLADA specification version 1.5 you can find the skinning equation so you should also be able to put the model in bind shape. How can we animate this model is still not covered and will be covered in the following sections.

Click here to go to: Implementation section in part 2, for this section of part 1

Reading Animation data from COLLADA document
So far we are able to read every thing related to the static pose of the character, the only thing we still have to understand and read is the animation data part. Animation is not very strong in COLLADA and is still in its infancy, as time passes and COLLADA gets mature this will get better. But for our purposes we have a lot to worry about ?.

<library_animations>
In this library we have all the animations data saved. For each joint animated you will see an node which further have the animation data for that specific Joint. Remember that an channel replaces the transform of the target on which it applies, which in this case will be joints.

You will see three types of children nodes in , first one as usual will be s of data, second one is called and the third one is . You need and nodes to define the target on which the animation data is applied.

From node you pick the target which gives you the ID of the Object on which the Animation data will be applied. And you also get the Sampler ID from where you will pick the sources from which you will pick the animation Data.

Remember the example I am presenting here is a case which will never occur in our example COLLADA documents, because of our assumption of backing matrices. But this is an easy example to understand.

Example:

0 1.16667 < technique_common> 2.2 3.5 LINEAR LINEAR < accessor source="#astroBoy-interpolations-array" count="2" stride="1">

Now Lets read from the Bottom node.

This says that there is one Entity (in our case will be Joint) called “astroBoy_Skeleton” in the scene who’s “X translation values” is being animated by the sampler “astroSkeleton-sampler”.

So we need to know how “astroSkeleton-sampler” animated that “X translation value”, we read which says.

There are three types of inputs you must read to read the animation data.

First node is: INPUT
Second node is: OUTPUT
Third node is: INTERPOLATION

When we start reading the s from the ,

The “input” with attribute semantic = “INPUT” gives you the input for the animation
The “input” with attribute semantic = “OUTPUT” gives you the output for the animation
The “input” with attribute semantic = “INTERPOLATON” gives you the interpolation for the animation

Now when we go and read those s, we see that the source who was referred to semantic = “INPUT” by the sampler has name=“TIME” in child of child of the <technique_common> child of that , in short it says this sources has time values of the animation in floats.

The source who was referred to semantic = “OUTPUT” by the sampler has name=“TRANSFORM” in child of child of the <technique_common> child of that , which says this sources has “X translation” values in floats for those times which we read from the previous source.

The source who was referred to semantic = “INTERPOLATION” by the sampler has name=“INTERPOLATION” in child of child of the <technique_common> child of that , which says this sources has the interpolation values in Strings for those OUTPUTS which we read from the previous source. (In studio max those interpolation values are usually “LINEAR” so we will not read this source and assume they are all “LINEAR”)

What this last source means is that, if you see the s, you are given OUTPUT values for two values of time. What happens if we want to run the animation on a time which falls in between those two time values? Then we apply interpolation to find the middle OUTPUT value for the in-between time value. And in this case it’s LINEAR interpolated.

Now if you see the TIME Source, that’s actually your key frames. And the OUTPUT one is the key frames data for the “X translation” of the Entity (joint).

So you pick the corresponding OUTPUT value for any time and apply that to the “X translation factor” of that entity in your code and the entity will be animated. To calculate the in-between values for more smooth animation you use the LINEAR interpolation.

Click here to go to: Implementation section in part 2, for this section of part 1

What interpolation means?
Interpolation means calculating any in-between value between (among) two (or more) values.

Let’s say we have “X” and “Y” values, to calculate the “Middle” value in between those two values we can use an interpolation factor of “0.5” which we call “T”. And to find the 3-Quater value in-between “X” and “Y” we use “T = 0.75” and so on.

You can run a loop on “T” from lets say 0.0 to 1.0 have any increment i.e 0.001, 0.01, 0.05 etc and you can get “Loop” many in-between values.

Now Linear Interpolation is a simple form of Interpolations. And the formula is as follows

float Interpolate( float a_Value1, float a_Value2, float a_T)
{
return ((a_Value2 * a_T) + ((1 - a_T) * a_Value1));
}

If you see the code it says that if “a_T” is “Zero” then give me a_Value1, and if it’s “One” then give me a_Value2. And if it’s in-between “Zero and One” then it will give you value in-between a_Value1 and a_Value2.

Now there are other forms of Interpolations as well. Like Bezier, cubic etc. They have further complicated formulas. And they also consider more then two values to interpolate in-between. But we will only be using linear interpolations for the sake of simplicity.

Now as we discussed before. This is not the case which we will ever have in our example COLLADA documents. So let’s see what we will have?

Keeping our assumptions in mind, we will only have two types of nodes. We will either have 16 * 3 = 48 s, 16 s and 16 nodes or we will have 3 s, 1 and 1 node. In the first case the “target” attribute will have “transform (X) (Y)” after the last “/” in its value and in the second case the “target” attribute will have “transform” after the last “/” in its value.

Either

Or

In the second form the values of the matrix which we backed in, are given in one out of those 3 s, just like the one we read in reading inverse bind matrices from controller. While in the first form values of each component of the 4 x 4 matrix are given in different s. And we have to combine them in one matrix when we read the data.

Now if you remember we read the Joint matrices for each joint from the <visual_scene> node. These values (which will be matrices, since we backed matrices) which we read from nodes for those joints, which are targeted through the “target” attribute of the of that will replace the Joint matrices, we read earlier from <visual_scene> for each key frame defined in the animation. And to calculate the world transformation matrix for each joint we will have to take this new Joint Matrix and multiply it with the parent Joint’s world transformation matrix.

And that’s all pretty much it. If you have read the whole tutorial from start to end, I guess you should be able to write your own exporter for COLLADA documents now. And you are ready to start reading the next part if you haven’t read in between the implementation sections.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: collada2gltf-v2.1.5-windows-release-x64.zip是一个压缩文件,其中包含collada2gltf工具的Windows x64版本的稳定版本2.1.5。Collada2gltf是一个用于将Collada 1.4和2.0文件格式转换为glTF 2.0的转换器工具,GLTF是一种高效的3D数据交换格式,它可以将3D模型、纹理和动画等信息打包成单个文件。这个工具的优点是,它轻便、易于使用,并且有强大的功能。因此,collada2gltf-v2.1.5-windows-release-x64.zip这个文件对于需要将Collada文件转换为glTF格式的3D制作人员来说是非常有用的。要使用这个工具,只需要将文件解压缩并按照工具的说明进行操作即可。另外需要注意的是,由于这是一个稳定版本,因此它已经在多个项目中被广泛使用,因此它是相对稳定和可靠的。 ### 回答2: collada2gltf是一个跨平台的命令行工具,用于将COLLADA文件转换为GLTF文件格式。GLTF是一种针对WebGL的3D图形标准格式。该工具的最新版本是v2.1.5,适用于Windows操作系统的64位版本。 这个工具具有许多有用的功能,它可以将COLLADA文件转换为GLTF格式,包括3D几何体和纹理映射,并支持多个纹理图层。它还可以优化文件大小,并支持二进制和文本GLTF文件格式。 collada2gltf的代码是开源的,并且是经过社区验证的。该软件还支持各种开发环境和工具,包括Node.js,C++和Python,这使得它非常适合各种开发需求。同时,该工具还提供了详细的文档和使用指南,以帮助用户快速上手使用。 总的来说,collada2gltf是一个非常实用和方便的工具,用于将COLLADA模型转换为GLTF格式。无论是开发者还是设计师,都可以用这个工具来轻松快速地进行3D模型转换。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值