Assimp库中文文档

翻译自:https://assimp-docs.readthedocs.io/en/latest/about/introduction.html

概述

简介

Asset-Importer-Lib(简称assimp)是一个从各种3d数据格式加载和处理几何场景的库。它主要是针对典型的游戏场景,通过支持节点层次结构、静态或蒙皮网格、材料、骨骼动画和潜在的纹理数据。但也支持一些3d打印和CAD格式。这个库并不是为了速度而设计的,它主要用于一次性从各种来源导入资产,并将其存储在特定于引擎的格式中,以便于每天轻松快速地加载。Assimp还能够对导入的数据应用各种后期处理步骤,如转换为索引网格,计算法线或切线/双切线,或从右手坐标系转换为左手坐标系。

assimp连接器库和查看器应用程序是在BSD 3条款许可下提供的。这基本上意味着您可以自由地在开源或闭源项目中使用它,用于商业或非商业目的,只要您保留许可信息并对使用它的行为承担自己的责任。具体请参见LICENSE文件

您可以在< assimp_root > / test / model目录中找到几乎所有格式的测试模型。小心,他们是免费的,但并不是所有的都是开源的。如果有一个伴随的'<file>source.txt '文件,不要忘记阅读它。

下载

Assimp-Lib有两种用法:

  1. 链接到预构建的库
  2. 通过cmake将库集成到您的项目中。

这两种方法在构建指令中都有描述

vcpkg

Vcpkg 可帮助您在 Windows、 Linux 和 MacOS 上管理 C 和 C++ 库。
您可以使用vcpkg依赖管理器下载和安装assimp

git clone  https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
vcpkg install assimp

vcpkg的assimp端口由Microsoft团队成员和社区贡献者保持更新。如果版本过期,请在vcpkg存储库创建一个问题或拉请求。

使用Assimp

导入模型

通过c++类接口访问
Asset-Importer-Lib可以通过类或平面函数接口访问。c++类接口是交互的首选方式:您创建一个类Assimp::Importer的实例,可能调整它的一些设置,然后调用

Assimp::Importer::ReadFile().

该类将读取文件并处理其数据,将导入的数据作为指向aiScene的指针返回给您。现在可以从文件中提取所需的数据。Importer为自己管理所有资源。如果Importer被销毁,由它创建/读取的所有数据也将被销毁。因此,使用Importer最简单的方法是在本地创建一个实例,使用它的结果,然后让它超出范围(销毁)。

#include <assimp/Importer.hpp>      // C++ importer interface
#include <assimp/scene.h>           // Output data structure
#include <assimp/postprocess.h>     // Post processing flags

bool DoTheImportThing( const std::string& pFile) {
  // Create an instance of the Importer class
  Assimp::Importer importer;

  // And have it read the given file with some example postprocessing
  // Usually - if speed is not the most important aspect for you - you'll
  // probably to request more postprocessing than we do in this example.
  const aiScene* scene = importer.ReadFile( pFile,
    aiProcess_CalcTangentSpace       |
    aiProcess_Triangulate            |
    aiProcess_JoinIdenticalVertices  |
    aiProcess_SortByPType);

  // If the import failed, report it
  if (nullptr != scene) {
    DoTheErrorLogging( importer.GetErrorString());
    return false;
  }

  // Now we can access the file's contents.
  DoTheSceneProcessing( scene);

  // We're done. Everything will be cleaned up by the importer destructor
  return true;
}

导出模型Exporting models

一个有效的aiScene实例可以被用于导出为请求的资产格式。

bool exporterTest() override {
    ::Assimp::Importer importer;
    ::Assimp::Exporter exporter;
    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure);
    exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.obj");
    return true;
}

数据结构Data Structures

  • 如果您需要将导入的数据置于一个左坐标系中,请向ReadFile()函数调用提供# aiprocess_makelefthandflag标志。
  • 输出面绕组是顺时针的。使用# aiProcess_FlipWindingOrder获取CW数据。
  • 输出多边形可以是任何东西:它们可能是凹的、自交叉的或非平面的,尽管我们的内置三角剖分(#aiProcess_Triangulate后处理步骤)不能处理后两个。
  • 输出UV坐标系统的原点在左下角:
0x  | 1y ---------- 1x  | 1y
    |					|
    | 					|
    | 					|
0x  | 0y ---------- 1x  | 0y

使用#aiProcess_FlipUVs标志获得带有左上角原点的UV坐标。
在这里插入图片描述

节点层级 The Node-Hierarchy

节点是场景中具有相对于父节点的位置和方向的小命名实体。从场景的根节点开始,所有节点可以有0到x个子节点,从而形成一个层次结构。它们构成了场景的基础:一个节点可以引用0…X网格,可以通过网格的骨来引用,也可以通过动画的关键序列来动画。DirectX称之为帧,其他人称之为对象,我们称之为aiNode一个节点可能会引用单个或多个网格。这些网格并不存储在节点中,而是存储在aiScene中的aiMesh数组中。节点仅通过它们的数组索引引用它们。 这也意味着多个节点可以引用同一个网格,这提供了一种简单的实例化形式。用这种方法表示的网格存在于节点的局部坐标系中。如果你想要网格在全局空间的方向,你必须连接从参考节点和它的所有父节点的转换。大多数文件格式实际上并不支持复杂的场景,但是只支持单一的模型。但是还有更复杂的格式,如.3ds,.x或.collada场景,它们可能包含任意复杂的节点和网格层次结构。我个人建议使用递归过滤器函数,比如下面的伪代码

void CopyNodesWithMeshes( aiNode node, SceneObject targetParent, Matrix4x4 accTransform) {
  SceneObject parent;
  Matrix4x4 transform;

  // if node has meshes, create a new scene object for it
  if( node.mNumMeshes > 0) {
        SceneObjekt newObject = new SceneObject;
        targetParent.addChild( newObject);
        // copy the meshes
        CopyMeshes( node, newObject);

        // the new object is the parent for all child nodes
        parent = newObject;
        transform.SetUnity();
  } else {
        // if no meshes, skip the node, but keep its transformation
        parent = targetParent;
        transform = node.mTransformation * accTransform;
  }

  // continue for all child nodes
  for( all node.mChildren) {
        CopyNodesWithMeshes( node.mChildren[a], parent, transform);
  }
}

这个函数将有子节点的节点复制到场景图中。如果是,则为导入节点创建一个新的场景对象,并复制该节点的网格。如果没有,则不创建对象。潜在的子对象将被添加到旧的targetParent中,但在全局空间中进行转换将是正确的。这个函数在过滤骨骼节点方面也很有效——这些节点形成了另一个网格/节点的骨骼层次结构,但它们本身没有任何网格。

网格 Meshes

导入场景的所有网格都存储在aiScene中的aiMesh*数组中。节点通过它们在数组中的索引来引用它们,并为它们提供坐标系统。 一个网格只使用一个单一的材料-如果模型的不同部分使用不同的材料,这些部分会被移动到同一节点中的多个分离的网格。 网格引用它的材质的方式与节点引用它的网格的方式相同:在aiScene中,材质存储在一个数组中,网格只存储这个数组的一个索引。aiMesh是由一系列数据通道定义的。这些数据通道的存在是由导入文件的内容定义的:默认情况下,网格中只有那些在文件中也可以找到的数据通道。唯一保证始终存在的通道是aiMesh::mVertices和aiMesh::mFaces。你可以通过测试指针是否为NULL或者使用aiMesh提供的辅助函数来测试其他数据的存在。你也可以在Importer::ReadFile()中指定几个后期处理标志,让assimp为你计算或重新计算额外的数据通道。目前,单个aiMesh可能包含一组三角形和多边形。单个顶点总是有位置的。此外,它可能有一个法线,一个切线和bitangent, 0到ai_max_number_of_texturecords(4在此刻)纹理坐标和0到AI_MAX_NUMBER_OF_COLOR_SETS(4)顶点颜色。此外,一个网格可能有也可能没有一组由aiBone结构数组描述的骨骼。如何解释骨骼信息将在后面介绍。

Materials

See the materials Material System Page

Bones

一个网格可以以aiBone对象的形式有一组骨骼。骨骼(Bones )是根据骨骼(skeleton)的运动来变形网格的一种手段。每块骨头都有一个名字和一组它可以影响的顶点。它的偏移矩阵声明了从网格空间转换到骨骼局部空间所需的变换。
使用骨骼名称,您可以在节点层次结构中找到相应的节点。这个节点相对于其他骨骼节点定义了网格的骨架。不幸的是,网格中也可能有一些节点不是由骨骼使用的,但仍然会影响骨骼的姿态,因为它们有子节点也是骨骼。所以当为一个网格创建骨架层次结构时,我建议使用以下方法

  1. 创建一个映射或类似的容器来存储骨架所需要的节点。用“no”对所有节点进行预初始化。
  2. For each bone in the mesh:
    • 通过比较它们的名称来找到场景层次结构中对应的节点
    • 在necessityMap中将这个节点标记为“yes"
    • 用同样的方法给所有的parent做标记,直到
      • 找到网格的节点或
      • 网格节点的父结点。
  3. 递归地遍历节点层次结构。
    • 如果节点被标记为necessary,则将其复制到骨架并检查其儿童。
    • 如果节点被标记为not necessary,则跳过它,不遍历它的子节点。

原因:您需要所有的父节点来保持转换链的完整性。对于大多数文件格式和建模包,骨架的节点层次结构要么是网格节点的子节点,要么是网格节点的兄弟节点,但这绝不是一个要求,所以你不应该依赖它。最接近根节点的节点是骨架根,从那里开始复制层次结构。您可以跳过每个分支,而不需要一个节点作为网格中的骨架—这就是为什么如果节点被标记为“不必要”,算法会跳过整个分支。

现在,您的引擎中应该有一个网格,它的骨架是导入的层次结构的子集。

动画 Animations

一个导入的场景可以包含0到x个aiAnimation条目。在这种情况下,动画是一组关键帧序列,其中每个序列描述了 在有限的时间跨度内 层次结构中 单个节点的方向。这种类型的动画通常用于动画蒙皮网格的骨架,但也有其他用途。

aiAnimation有一个持续时间。持续时间以及所有时间戳都是用 ticks 给出的。为了获得正确的时间,所有的时间戳都必须被aiAnimation::mTicksPerSecond分割。但是要注意,文件格式和导出器的某些组合并不总是在导出的文件中存储此信息。在本例中,mTicksPerSecond被设置为0,表示缺乏信息。

aiAnimation由一系列的aiNodeAnim组成。每个骨骼动画只影响节点层次结构中的单个节点,这个名称指定哪个节点受影响。对于这个节点,结构存储了三个单独的关键序列:用于位置的向量关键序列,用于旋转的四元数关键序列,以及用于缩放的另一个向量关键序列。所有3d数据在父节点的坐标空间中都是局部的,这意味着节点的变换矩阵在同一个空间中。可能在某些情况下,动画轨迹通过它们的名称引用一个不存在的节点,但在您的日常数据中不应该出现这种情况。

要应用这样的动画,你需要识别指向网格中实际骨骼的动画轨迹。然后对于每个轨道:

  • 找到位于当前动画时间之前的帧。
  • 可选:插入这些帧和如下帧。
  • 结合计算的(位置,旋转和缩放)到一个变换矩阵
  • 将受影响节点的转换设置为计算的矩阵。

如果你需要关于如何转换或从四元数的提示,看看矩阵和四元数常见问题。如果你碰巧需要缩放键,我建议你使用对数插值——通常你根本不需要它们。

Blenshapes

ToDo!

纹理 Textures

通常,资源使用的纹理存储在单独的文件中,然而,有文件格式将它们的纹理直接嵌入到模型(Model)文件中。这样的纹理被加载到一个aiTexture结构中。

在以前的版本中,AI_MATKEY_TEXTURE(textureType, index)的查询路径是在aiScene::mTextures中纹理的索引。现在这个调用将返回.fbx文件中嵌入纹理的文件路径。要测试它是否是一个嵌入纹理,请使用aiScene::GetEmbeddedTexture。如果返回的指针不为空,它是嵌入的,可以从数据结构中加载。如果为空,则搜索一个单独的文件。其他文件类型仍然使用旧的行为。

如果你依赖旧的行为,你可以使用Assimp::Importer::SetPropertyBool与Key #AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING强制旧的行为。

有两种情况:

  • 纹理没有被压缩。它的颜色数据直接存储在aiTexture 结构,是一个aiTexture::mWidth * aiTexture::mHeight 大小的aiTexel数组结构。每个aiTexel代表纹理图像的一个像素(或“texel”)。颜色数据存储在一种无符号(unsigned)的RGBA8888格式中,它可以很容易地用于Direct3D和OpenGL(可能需要调整颜色组件的顺序)。选择RGBA8888是因为它很有名,易于使用,并且几乎所有图形api都支持它。
  • 这适用于aiTexture::mHeight == 0。之后,纹理被存储在一个压缩格式中,如DDS或PNG。术语“压缩”并不意味着纹理数据必须实际上被压缩,但是纹理在模型文件中就像它被存储在硬盘上的一个单独的文件中。需要适当的解码器(如libjpeg, libpng, D3DX, DevIL)来加载这些纹理。aiTexture::mWidth以字节为单位指定纹理数据的大小,aiTexture::pcData是一个指向原始图像数据的指针,aiTexture::achFormatHint要么为零,要么是嵌入纹理格式最常见的文件扩展。如果assimp能够确定文件格式,则只设置这个值。

材料系统 Material-System

所有材料都存储在aiScene内的aiMaterial数组中。
每个aiMesh通过它在数组中的索引指向一个材质。由于材料参数的定义和使用存在着很大的差异,因此对材料结构没有明确的定义。相反,材料是由一组可通过名称访问的属性定义的。看一下assimp/material.h,看看定义了哪些类型的属性。在这个文件中,还定义了各种函数来测试材料中是否存在某些属性并检索它们的值。

Textures

纹理被组织在堆栈中,每个堆栈被独立地评估。一个特定纹理堆栈的最终颜色值被用于阴影方程(shading equation)。例如,漫射纹理堆栈(aiTextureType_DIFFUSE)的计算颜色值与入射漫射光的数量相乘,以获得一个像素的最终漫射颜色。

Constants

所有材料关键常量以’ AI_MATKEY '作为前缀开始。

NameData TypeDefault ValueMeaning
NAMEaiStringn/aThe name of the material, if available.
COLOR_DIFFUSEaiColor3Dblack (0,0,0)Diffuse color of the material. This is typically scaled by the amount of incoming diffuse light (e.g. using gouraud shading)

具体见文档

C++ API

从材料中检索属性是使用各种实用函数完成的。对于c++,它只是简单地调用aiMaterial::Get()

aiMaterial* mat = .....

// The generic way
if(AI_SUCCESS != mat->Get(<material-key>,<where-to-store>)) {
   // handle epic failure here
}

很简单,不是吗?来获取你要使用的材料的名称

aiString name;
mat->Get(AI_MATKEY_NAME,name);

或者对于漫反射颜色(如果没有设置属性,color将不会被修改)

aiColor3D color (0.f,0.f,0.f);
mat->Get(AI_MATKEY_COLOR_DIFFUSE,color);

注意: Get()实际上是一个对aiColor3D、aiColor4D、aiString、float、int和其他类型有显式特殊化(explicit specializations)的模板。确保第二个参数的类型与material属性的预期数据类型匹配(还没有编译时检查!)。如果你希望遇到非常奇怪的结果,就不要听从这个建议。

C API

详看文档

如何将UV通道映射到纹理(MATKEY_UVWSRC)

MATKEY_UVWSRC只会在源格式没有指定从纹理到UV通道的显式映射时存在。许多格式不这样做,assimp也没有意识到一个完美的规则。
因此,您对UV通道的处理需要灵活。我们的建议是使用这样的逻辑来正确处理大多数情况:

  • 只有一个UV通道:将通道0分配给所有纹理和中断
  • 所有的纹理
    • 对于这个纹理有uvwsrc :分配uvwsrc中指定的通道
    • 否则按升序为所有纹理堆栈分配通道,例如,diffuse1获得通道1,opacity0获得通道0。

伪代码列表

为了完整性,下面是一个非常粗糙的伪代码示例,展示了如何在您的渲染管线(shading pipeline)中评估Assimp材料。你可能想要简化(limit)你从所有这些材料关键属性(Key)到一个适合你的目的的合理子集的处理(例如,大多数3d引擎不会支持高度复杂的多层材料,但许多3d modellers都是这样做的)。

还要注意,这个示例的目标是一个(基于渲染的( shader-based))渲染管道实时图形。

// ---------------------------------------------------------------------------------------
// Evaluate multiple textures stacked on top of each other
float3 EvaluateStack(stack) {
  // For the 'diffuse' stack stack.base_color would be COLOR_DIFFUSE
  // and TEXTURE(aiTextureType_DIFFUSE,n) the n'th texture.

  float3 base = stack.base_color;
  for (every texture in stack) {
    // assuming we have explicit & pretransformed UVs for this texture
    float3 color = SampleTexture(texture,uv);

    // scale by texture blend factor
    color *= texture.blend;

    if (texture.op == add)
      base += color;
    else if (texture.op == multiply)
      base *= color;
    else // other blend ops go here
  }
  return base;
}

// ---------------------------------------------------------------------------------------
// Compute the diffuse contribution for a pixel
float3 ComputeDiffuseContribution() {
  if (shading == none)
     return float3(1,1,1);

  float3 intensity (0,0,0);
  for (all lights in range) {
    float fac = 1.f;
    if (shading == gouraud)
      fac =  lambert-term ..
    else // other shading modes go here

    // handling of different types of lights, such as point or spot lights
    // ...

    // and finally sum the contribution of this single light ...
    intensity += light.diffuse_color * fac;
  }
  // ... and combine the final incoming light with the diffuse color
  return EvaluateStack(diffuse) * intensity;
}

// ---------------------------------------------------------------------------------------
// Compute the specular contribution for a pixel
float3 ComputeSpecularContribution() {
  if (shading == gouraud || specular_strength == 0 || specular_exponent == 0)
    return float3(0,0,0);

  float3 intensity (0,0,0);
  for (all lights in range) {
    float fac = 1.f;
    if (shading == phong)
      fac =  phong-term ..
    else // other specular shading modes go here

    // handling of different types of lights, such as point or spot lights
    // ...

    // and finally sum the specular contribution of this single light ...
    intensity += light.specular_color * fac;
  }
  // ... and combine the final specular light with the specular color
  return EvaluateStack(specular) * intensity * specular_strength;
}

// ---------------------------------------------------------------------------------------
// Compute the ambient contribution for a pixel
float3 ComputeAmbientContribution() {
  if (shading == none)
     return float3(0,0,0);

  float3 intensity (0,0,0);
  for (all lights in range) {
    float fac = 1.f;

    // handling of different types of lights, such as point or spot lights
    // ...

    // and finally sum the ambient contribution of this single light ...
    intensity += light.ambient_color * fac;
  }
  // ... and combine the final ambient light with the ambient color
  return EvaluateStack(ambient) * intensity;
}

// ---------------------------------------------------------------------------------------
// Compute the final color value for a pixel
// @param prev Previous color at that position in the framebuffer
float4 PimpMyPixel (float4 prev) {
  // .. handle displacement mapping per vertex
  // .. handle bump/normal mapping

  // Get all single light contribution terms
  float3 diff = ComputeDiffuseContribution();
  float3 spec = ComputeSpecularContribution();
  float3 ambi = ComputeAmbientContribution();

  // .. and compute the final color value for this pixel
  float3 color = diff + spec + ambi;
  float3 opac  = EvaluateStack(opacity);

  // note the *slightly* strange meaning of additive and multiplicative blending here ...
  // those names will most likely be changed in future versions
  if (blend_func == add)
       return prev+color*opac;
  else if (blend_func == multiply)
       return prev*(1.0-opac)+prev*opac;

   return color;
}

如何访问着色器代码

你可以获得shadercodetexture 一样(AI_MATKEY_GLOBAL_SHADERLANG和AI_MATKEY_SHADER_VERTEX,…)你可以通过使用下面的材质键来获得分配的着色器源:

  • AI_MATKEY_GLOBAL_SHADERLANG获取使用的着色器语言。
  • AI_MATKEY_SHADER_VERTEX指定的顶点着色代码存储为字符串。
  • AI_MATKEY_SHADER_FRAGMENT指定的片段着色器代码存储为一个字符串。
  • AI_MATKEY_SHADER_GEO指定的几何着色器代码存储为一个字符串。
  • AI_MATKEY_SHADER_TESSELATION分配的镶嵌着色器代码存储为一个字符串。
  • AI_MATKEY_SHADER_PRIMITIVE赋值的原始着色器代码存储为一个字符串。
  • AI_MATKEY_SHADER_COMPUTE指定的计算着色器代码存储为一个字符串。

Animation Overview

This external tutorial has working code to get started implementing animations using bone matrix array in the vertex shader. (If using glm OpenGL math library, cross-reference with this page which has useful tips on converting between assimp and glm objects)
这个外部教程有工作代码,开始在顶点着色器中使用骨矩阵阵列实现动画。(如果使用glm OpenGL数学库,请与本页面进行交叉引用,该页有关于在assimp和glm对象之间转换的有用提示)

Performance 性能

概述

这一页讨论了与assimp相关的一般性能问题。

剖析研究

Assimp has built-in support for very basic profiling and time measurement. To turn it on, set the GLOB_MEASURE_TIME configuration switch to true (nonzero). Results are dumped to the log file, so you need to setup an appropriate logger implementation with at least one output stream first (see the @link logging Logging Page @endlink for the details.).

Note that these measurements are based on a single run of the importer and each of the post processing steps, so a single result set is far away from being significant in a statistic sense. While precision can be improved by running the test multiple times, the low accuracy of the timings may render the results useless for smaller files.

Assimp对非常 基本的分析和时间测量有内置的支持。要打开它,将GLOB_MEASURE_TIME配置切换到true(非零)。结果被转储到日志文件中,因此您需要首先设置一个适当的logger实现,首先是一个输出流(参见日志页面的详细信息)。

请注意,这些度量是基于导入器的单个运行和每一个post处理步骤,因此一个结果集在统计意义上是非常重要的。虽然通过多次运行测试可以提高精度,但计时器的低精度可能会使较小的文件变得无用。

一个样本报告看起来像这样(一些不相关的日志消息被省略,条目分组为清晰):

Debug, T5488: START `total`
Info,  T5488: Found a matching importer for this file format


Debug, T5488: START `import`
Info,  T5488: BlendModifier: Applied the `Subdivision` modifier to `OBMonkey`
Debug, T5488: END   `import`, dt= 3.516 s


Debug, T5488: START `preprocess`
Debug, T5488: END   `preprocess`, dt= 0.001 s
Info,  T5488: Entering post processing pipeline


Debug, T5488: START `postprocess`
Debug, T5488: RemoveRedundantMatsProcess begin
Debug, T5488: RemoveRedundantMatsProcess finished
Debug, T5488: END   `postprocess`, dt= 0.001 s


Debug, T5488: START `postprocess`
Debug, T5488: TriangulateProcess begin
Info,  T5488: TriangulateProcess finished. All polygons have been triangulated.
Debug, T5488: END   `postprocess`, dt= 3.415 s


Debug, T5488: START `postprocess`
Debug, T5488: SortByPTypeProcess begin
Info,  T5488: Points: 0, Lines: 0, Triangles: 1, Polygons: 0 (Meshes, X = removed)
Debug, T5488: SortByPTypeProcess finished

Debug, T5488: START `postprocess`
Debug, T5488: JoinVerticesProcess begin
Debug, T5488: Mesh 0 (unnamed) | Verts in: 503808 out: 126345 | ~74.922
Info,  T5488: JoinVerticesProcess finished | Verts in: 503808 out: 126345 | ~74.9
Debug, T5488: END   `postprocess`, dt= 2.052 s

Debug, T5488: START `postprocess`
Debug, T5488: FlipWindingOrderProcess begin
Debug, T5488: FlipWindingOrderProcess finished
Debug, T5488: END   `postprocess`, dt= 0.006 s


Debug, T5488: START `postprocess`
Debug, T5488: LimitBoneWeightsProcess begin
Debug, T5488: LimitBoneWeightsProcess end
Debug, T5488: END   `postprocess`, dt= 0.001 s


Debug, T5488: START `postprocess`
Debug, T5488: ImproveCacheLocalityProcess begin
Debug, T5488: Mesh 0 | ACMR in: 0.851622 out: 0.718139 | ~15.7
Info,  T5488: Cache relevant are 1 meshes (251904 faces). Average output ACMR is 0.718139
Debug, T5488: ImproveCacheLocalityProcess finished.
Debug, T5488: END   `postprocess`, dt= 1.903 s


Info,  T5488: Leaving post processing pipeline
Debug, T5488: END   `total`, dt= 11.269 s

在这个特殊的例子中,只有四分之一的导入时间花费在实际的导入上,而剩下的时间被#aiProcess_Triangulate#aiProcess_JoinIdenticalVertices#aiProcess_ImproveCacheLocality后期处理步骤所消耗。因此 ,明智地选择后处理步骤对于获得良好的性能至关重要。当然,这取决于应用程序的个别需求,在许多典型的assimp用例中,性能并不重要(即在脱机内容管道中)。

线程处理

本页面讨论了线程环境下的可伸缩性,以及在多个线程中同时使用它的注意事项。

线程安全/在多个线程中并发使用Assimp

库可以被多个线程同时访问,只要满足以下先决条件:

  • c++ -API的用户应该确保他们为每个线程使用专用的#Assimp::Importer实例。构造#Assimp::Importer的实例是昂贵的,所以让每个线程维护自己的线程本地实例(可以用来加载尽可能多的文件)可能是一个好主意。
  • C-API是线程安全的。
  • 在提供定制IO逻辑时,必须确保底层实现是线程安全的。
  • 自定义日志流或日志记录器替换也必须是线程安全的。

但是,多个并发导入可能有好处,也可能没有好处。对于某些文件格式,加上很少或没有后期处理IO时间,往往会成为性能瓶颈。密集的后期处理加上像X或Collada这样的“慢”文件格式,可以很好地适应多个并发导入。

内部多线程 Internal threading

内部多线程目前没有实现。

Resources

这个页面列出了一些有用的assimp资源。请注意,即使核心团队关注他们,我们也不能保证第三方信息的准确性。如果有疑问,最好在邮件列表或SF.net论坛上询问。
Assimp附带了一些示例应用程序,可以在 ./samples文件夹中找到它们。不要忘记阅读 README 文件。
Assimp-GL-Demo - OpenGl动画示例,使用库的动画导入工具。
Assimp-Animation-Loader是另一个简化动画播放的工具。
Assimp-Animations - Tutorial“使用Open Asset Import Library加载模型”,出自一系列OpenGl教程。

导入说明 Importer Notes

Blender

本节包含Blender3D导入器的实现说明。

概述

assimp提供了一个独立的对Blender所谓的SDNA系统的重新实现(“在SDNA 上有说明 http://www.blender.org/development/architecture/notes-on-sdna/”_)。SDNA允许Blender完全向后和向前兼容,并跨所有平台交换文件。因此,BLEND格式是一个二进制庞然大物,加载器试图读取它的大部分内容,自然受到#aiScene输出数据结构范围的限制。因此,如果Blender是您资产工作流程中唯一的建模工具,那么如果assimps格式覆盖不满足要求,考虑从Blender编写一个自定义导出程序。

目前状况

Blender加载器还不支持动画,但它被认为是相对稳定的。
当在Blender加载器上提出bug时,总是要给出Blender的版本(或者,更好的做法是,发布导致错误的文件)。

IFC

本节包含关于IFC-STEP导入器的实现说明。

概述

该库提供了IFC2x3行业标准的部分实现,用于自动化交换CAE/架构数据集。有关格式的更多信息,请参见IFC。我们的目标是从文件中获得尽可能多的3D数据。

详见文档

Ogre

注意:Ogre加载器目前正在开发中,在这个文档被写完之后,很多事情都发生了变化,但他们还没有最终去重写文档。所以现在情况可能已经改变了!
本节包含OgreXML导入器的实现说明。

什么会被载入

(Mesh)网格:面,位置,法线和所有的texcoord。Materialname将用于加载材质。

(Material)材料:在文件中查找正确的材料,导入器应该与材质工作。从那里,texturename (1 color- 1 normalmap)和materialcolors(但不是在自定义材质中)将被加载。同样,材质名也将被设置。

(Skeleton)骨骼:骨骼与骨骼层次结构(位置和旋转,但不支持骨骼中的缩放),名称和转换,旋转,翻译和动画

如何从Blender导出文件

你可以自己找到关于如何使用Ogreexporter 的信息,所以这里只是你需要的一些选项,所以assimp进口商将正确加载一切:

  • 使用“渲染材质”或“自定义材质”参见@ref Material
  • 不要使用“翻转轴到Y(Flip Up Axies to Y)”
  • 使用“Skeleton name follow mesh”

XML-格式

Ogre有一个二进制和一个XML网格格式。这个加载器只能处理xml文件,但是不要惊慌,有一个命令行转换器,您可以使用它从二进制文件创建xml文件。去Ogre页面找就行了。

目前你只能加载网格。所以你将需要导入.mesh.xml文件,加载器将尝试查找附件材料和骨架文件。

骨架文件必须与网格文件同名,例如:fish.mesh.xml和fish.skeleton.xml。

材质文件可以和网格文件有相同的名字(如果文件是模型。加载器将尝试加载model.material),或者你可以使用

Importer::Importer::SetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "materiafile.material")

指定材料文件的名称。如果将材料存储在单个文件中,这一点尤其有用。导入器将第一次尝试用与网格相同的名称加载材料,只有在不打开的情况下,试着装载备用的材料文件。默认的材质文件名是“properties .材质”。
我们建议您使用自定义材料,因为它们支持多个纹理(如colormap和normalmap)。首先,你应该在Ogre Blender exporter 帮助文件中阅读自定义材料的sektion,而不是使用assimp.tlp模板,您可以在scripts/OgreImpoter/Assimp.tlp 中找到它。如果你不设置所有的值,不要担心,它们在导入过程中会被忽略。
如果你想在定制材质中添加更多属性,你可以轻松地扩展ogre材质加载器,每个属性只需要几行。看看ogreimportermaterial.cpp

  • IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME:正常情况下,如果材质文件中没有指定目标,纹理将被加载为colormap。如果这个开关被启用,以_n, _l, _s结尾的纹理名称将被用作normalmaps, lightmaps或specularmap。
    属性类型:Bool。默认值:false。
  • Ogre mesh只包含MaterialName,而不是MaterialFile。如果没有与该材料同名的材料文件,Ogre Importer将尝试加载该文件并搜索其中的材料。
    属性类型:字符串。默认值:guessed。

Properties

Notes for text importers

Notes for binary importers

Utilities

Filling materials

在#aiMaterial结构中所需的设置/删除/查询键的定义在MaterialSystem.h中声明,在一个名为#aiMaterial的#aiMaterial派生版本中。头部包含在AssimpPCH.h中,所以你不需要麻烦。

aiMaterial* mat = new aiMaterial();

const float spec = 16.f;
mat->AddProperty(&spec, 1, AI_MATKEY_SHININESS);

//set the name of the material:
NewMaterial->AddProperty(&aiString(MaterialName.c_str()), AI_MATKEY_NAME);//MaterialName is a std::string

//set the first diffuse texture
NewMaterial->AddProperty(&aiString(Texturename.c_str()), AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));//again, Texturename is a std::string

附录 A

基本导入的抽象方法模板

// -------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool xxxxImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler,
      bool checkSig) const {
    const std::string extension = GetExtension(pFile);
    if(extension == "xxxx") {
        return true;
    }
    if (!extension.length() || checkSig) {
        // no extension given, or we're called a second time because no
        // suitable loader was found yet. This means, we're trying to open
        // the file and look for and hints to identify the file format.
        // #Assimp::BaseImporter provides some utilities:
        //
        // #Assimp::BaseImporter::SearchFileHeaderForToken - for text files.
        // It reads the first lines of the file and does a substring check
        // against a given list of 'magic' strings.
        //
        // #Assimp::BaseImporter::CheckMagicToken - for binary files. It goes
        // to a particular offset in the file and and compares the next words
        // against a given list of 'magic' tokens.

        // These checks MUST be done (even if !checkSig) if the file extension
        // is not exclusive to your format. For example, .xml is very common
        // and (co)used by many formats.
    }
    return false;
}

// -------------------------------------------------------------------------------
// Get list of file extensions handled by this loader
void xxxxImporter::GetExtensionList(std::set<std::string>& extensions) {
    extensions.insert("xxx");
}

// -------------------------------------------------------------------------------
void xxxxImporter::InternReadFile( const std::string& pFile,
      aiScene* pScene, IOSystem* pIOHandler) {
    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));

    // Check whether we can read from the file
    if( file.get() == NULL) {
        throw DeadlyImportError( "Failed to open xxxx file ", pFile, ".");
    }

    // Your task: fill pScene
    // Throw a ImportErrorException with a meaningful (!) error message if
    // something goes wrong.
}

The Developer Guide

写你自己的导入导出器
在本章中,你将学习如何编写你自己的进口商。您只需要实现#Assimp::BaseImporter类,它定义了一些抽象方法,注册加载器,仔细地测试它,并为它提供测试模型。

详见文档

The API-Documentation

Common Datatypes

struct aiString

template<typename TReal> classaiVector2t
template<typename TReal> classaiVector3t
template<typename TReal> classaiColor4t
template<typename TReal> classaiQuaterniont//表示四维向量中的四元数。
template<typename TReal> classaiMatrix3x3t
template<typename TReal> classaiMatrix4x4t
struct aiRay
struct aiPlane
struct aiMetadata
struct aiTexture
struct aiAABB

template<typename T, unsigned int Capacity> classAssimp::SmallVector
//带有就地存储的小矢量。
当列表较短时,减少堆分配。它使用小数组作为专用大小。当增长大于这个小缓存时,将使用动态增长算法。

Scene Datatypes

struct aiFace
struct aiMesh
struct aiScene
struct aiNode
struct aiCamera
struct aiLight
struct aiMaterial

Animation Datatypes

struct aiAnimation
struct aiAnimMesh
struct aiNodeAnim
struct aiMeshKey
struct aiBone

Exceptions

classDeadlyImportError : public DeadlyErrorBase
classDeadlyExportError : public DeadlyErrorBase

Logging API

classAssimp::Logger : public Intern::AllocateFromAssimpHeap
classAssimp::LogStream : public Intern::AllocateFromAssimpHeap
classAssimp::DefaultLogger : public Assimp::Logger
classAssimp::NullLogger : public Assimp::Logger

IO-System API

classAssimp::IOSystem : public Intern::AllocateFromAssimpHeap
classAssimp::IOStream : public Intern::AllocateFromAssimpHeap
template<class T>classAssimp::IOStreamBuffer
classAssimp::MemoryIOStream : public Assimp::IOStream
classAssimp::DefaultIOSystem : public Assimp::IOSystem
classAssimp::DefaultIOStream : public Assimp::IOStream
classAssimp::MemoryIOSystem : public Assimp::IOSystem
classAssimp::MemoryIOStream : public Assimp::IOStream
classAssimp::BlobIOStream : public Assimp::IOStream
classAssimp::BlobIOSystem : public Assimp::IOSystem

Import/Export API

classAssimp::BaseImporter
classAssimp::Importer
classAssimp::Exporter

Parsing API

template<class TNodeType>
classAssimp::TXmlParser

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值