Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[glTF2] Importer + Spec Conformity #1423

Merged
merged 56 commits into from
Sep 13, 2017

Conversation

dhritzkiv
Copy link
Contributor

@dhritzkiv dhritzkiv commented Sep 8, 2017

This support adds glTF2 Importing, and improves conformance to the spec.

New Features

  • glTF 2 Import
    • the current functionality only supports exporting glTF2 files
    • glTF 1 import and export functionality is maintained
  • glTF 2 unit test (very simple import and export tests)
  • Export and import of more properties, including but not limited to:
    • scale property on Normal textures
    • strength property on Occlusion textures
  • Support the core material format (pbrMetallicRoughness - see below)
  • Add support for the pbrSpecularGlossiness material extension

Major changes

  • Removal of KHR_materials_common extension in export
    • This version of KHR_materials_common was coded against the extension spec from glTF1 and was incorrect.
    • The glTF 2 version is still in development (glTF 2.0: New/modified KHR_materials_common extension KhronosGroup/glTF#947), and is not ratified yet. There are several conflicting work in progress specs, and it may ultimately even be replaced by KHR_materials_cmnBlinnPhong
    • The three.js project has begun implementing one of the early work-in-progress specs, but it I think it makes sense to wait for the spec to be completed and ratified before assimp supports it. Ditto for the other extensions (lights, techniqueWebGL, Draco, etc.)
  • Removal of Open3DGC compression from glTF2.
    • There's no spec for that extension in glTF2, and the current one (for glTF1) doesn't work. The Draco extension will accomplish the same task of compression -perhaps even better- once it's complete.
  • Removal of KHR_binary_glTF code
    • Binary glTFs are no longer an extension, instead are now part of the spec.
    • However, there's a lot of work to support the new format. I've started here, but there's still a ways to go.
  • Output meshes on the mesh property of a node and not meshes
    • glTF2 spec only allows up to one mesh to be defined per node

Minor changes

  • Don't write out default values where possible:
    • byteStride on bufferView only gets written if it's > 0
    • wrapS and wrapT in samplers only get written if not Repeat
    • doubleSided and many other material properties
  • Import/Export of material, sampler names
  • magFilter and minFilter can be undefined, and now properly import and export (previously mag was always set to Linear and min was always to Linear-Linear, no matter the input.
  • version is now stored as a string (reduces the amount of string<->number comparisons/conversions)

To dos (maybe someone else can take these on)

  • Animations, Joints, Skins
    • I've kept these in the glTF2 exporter, however, the output is incorrect.
  • Implement binary_glTF properly
    • Again, working on it here
  • Implement the other extensions (lights, webgl technique, material_common, Draco) once they are completed
  • Don't export resources that are referenced in duplicate, or where an undefined value can achieve the same effect.
    • An example of this is a texture that references a sampler that is empty (for default behaviour), or a texture that reference samplers that are identical duplicates of other samplers.
  • Serialize and persist extras values for each resource.
  • Add more tests (with more models, testing different features, etc.)
  • Backport some of the improvements to glTF1

Notes

pbrMetallicRoughness

glTF 2 focuses on supporting PBR materials with its default material definition (as well as additional PBR properties via the specularGlossiness extension). Because these material properties are not equivalent to existing (Assimp) material properties, such as diffuse, specular, etc., I had to create lots of custom named material properties, such as:

#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0,0

This facilitates persisting those values in a glTF2 -> glTF2 export flow. These properties are scoped to only glTF2 importers/exporters. In the future, perhaps Assimp can define more material properties (such as metallicFactor, roughnessTexture, etc.), and this special naming can be removed to allow cross format PBR support. I believe Blender supports PBR in its format now.

Importing from glTF2 and exporting to another format does a pretty good job of maintaining appearance of materials (e.g. baseColorFactor is used for diffuse)

Exporting glTF2 from another format also does a pretty good job of maintaining appearance.

pbrSpecularGlossiness

Like with pbrMetallicRoughness, I've added support for the pbrSpecularGlossiness extension to allow for glTF2 -> glTF2 exporting in assimp, where specularGlossiness properties are persisted.

Other

I'm not a C++ programmer, so apologies if I didn't follow convention in a lot of this code. I tried to maintain consistency wherever possible, but I'm sure I made many mistakes.


Resources

@jamesgk
Copy link
Contributor

jamesgk commented Sep 8, 2017

I'm against the removal of KHR_materials_common for practical reasons. I'd rather have an implementation of a WIP spec than an approximation of these materials with PBR, which if I understand correctly is what the glTF2 exporter does here.

Animations, Joints, Skins [...] the output is completely incorrect.

Can you expand on this? Assimp -> glTF2 -> Three.js was working fine for me.

@dhritzkiv
Copy link
Contributor Author

dhritzkiv commented Sep 8, 2017

I'd rather have an implementation of a WIP spec than an approximation of these materials with PBR[...]

Thing is, I can't find a repo or a doc that details the KHR_materials_common spec. It currently lives among at least a few (1: KhronosGroup/glTF#965, 2 KhronosGroup/glTF#947) incongruent GitHub issues, which I don't think is suitable to code against.

In fact, most recently, there seems to be talk of not having a single KHR_materials_common extension, but a KHR_materials_cmnBlinnPhong extension instead.

Note: the materials_common models in the KhronosGroup's Sample Model repo also have incorrect KHR_materials_common variations of the models. There are issues open to address this. So don't think we can just copy those.

As further guidance, the Unity Exporter and Blender Exporter from KhronosGroup do not implement KHR_materials_common in their exporters, yet. They do, however implement pbrMetallicRoughness, and Unity Exporter implements pbrSpecularGlossiness.

[...] which if I understand correctly is what the glTF2 exporter does here.

The glTF2 Exporter in assimp's master doesn't output KHR_materials_common properly (according to any of the current Work-in-Progress specs). One WIP spec suggests that the values "should" be be ambientFactor, diffuseFactor, diffuseTexture, specularFactor, specularTexture, etc. However, assimp master currently outputs the following keys ambient, diffuse, specular, emission. There are many more such inconsistencies.

I'm against the preservation of KHR_materials_common since it doesn't align with any work-in-progress spec, and there is no finalized -let alone ratified- spec. We should wait until the dust settles on the material extensions front.

Meanwhile, pbrMetallicRoughness is part of the core spec, and pbrSpecularGlossiness is a ratified extension. We should target these for the moment.


Animations, Joints, Skins [...] the output is completely incorrect.

Can you expand on this? Assimp -> glTF2 -> Three.js was working fine for me.

Using the glTF validator from Khronos Group on an exported glTF2 suggests that much is invalid/incorrectly implemented via these errors

{
    "type": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
    "path": "#/meshes/0/primitives/0/attributes",
    "message": "Invalid attribute name `JOINT`."
}
{
    "type": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
    "path": "#/meshes/0/primitives/0/attributes",
    "message": "Invalid attribute name `WEIGHT`."
}
{
     "type": "NODE_WITH_NON_SKINNED_MESH",
     "path": "#/nodes/34",
     "message": "Node has `skin` defined, but `mesh` has no joints data."
}
{
     "type": "BUFFER_VIEW_TARGET_OVERRIDE",
     "path": "#/animations/31/samplers/2/output",
      "message": "Override of previously set bufferView target or usage. Initial: `VertexBuffer`, new: `Other`."
}
{
     "type": "ANIMATION_CHANNEL_TARGET_NODE_MATRIX",
     "path": "#/animations/0/channels/0/target",
     "message": "Animation channel can not target TRS properties of node with defined `matrix`."
}

…and many more

That being said, some of these errors are easy to fix (JOINT should be JOINTS_0), but I don't know how far down these issues go.

It's possible Three.js has implemented the animations importing incorrectly. Just last week I found a small issue with their Mesh importing logic that was not to spec.

@jamesgk
Copy link
Contributor

jamesgk commented Sep 8, 2017

My issue is, if I have an asset with this kind of "common" material, how can I (today) export that to glTF2 with assimp and successfully load it with loaders that are supporting a WIP spec? Why not just code against those loaders? I'm thinking only of Three.js here, so maybe that's a bit limited. But still, I don't see the harm in including the extension when the alternative is that people who need this functionality simply can't use assimp (or more realistically, that we have to maintain a branch).

That being said, some of these errors are easy to fix (JOINT should be JOINTS_0), but I don't know how far down these issues go.

My guess is not very far, since I was successfully exporting animations. I definitely don't think it should be removed just because the validator is complaining.

@dhritzkiv
Copy link
Contributor Author

dhritzkiv commented Sep 8, 2017

[...] if I have an asset with this kind of "common" material, how can I (today) export that to glTF2 with assimp and successfully load it with loaders that are supporting a WIP spec?

You probably cannot do that today, and there should be no expectation that you can. For all intents and purposes, KHR_materials_common does not exist today. It may exist in a few weeks/months, or it may never exist. KHR_materials_cmnBlinnPhong may end up replacing it, or it may also never come to fruition.

Besides the point, assimp currently doesn't export KHR_materials_common properly against ANY of the WIP specs.

The only argument for keeping in (some form of) KHR_materials_common is that three.js supports it, but that's not a good argument. Especially since a few weeks ago they removed another extension (~~~Lights, I believe~~~ webgl_techniques, I believe) since its spec was incomplete. I can see three.js's implementation of KHR_materials_common being changed in the future.

There's no sense in generating glTF2 files that are, plain and simple, invalid.


That being said, some of these errors are easy to fix (JOINT should be JOINTS_0), but I don't know how far down these issues go.

My guess is not very far, since I was successfully exporting animations. I definitely don't think it should be removed just because the validator is complaining.

You might be right. I don't think it should be removed, but the fixes should definitely be made to appease the validator and the spec. I might take a crack at the fixes shortly.

@jamesgk
Copy link
Contributor

jamesgk commented Sep 8, 2017

You probably cannot do that today

Yes I can. Otherwise I wouldn't oppose taking out the extension! It's fine if Three.js is going to change its implementation -- all this stuff is obviously in flux. I don't see any practical reason not to at least try to keep up with the WIP spec.

assimp currently doesn't export KHR_materials_common properly against any of the WIP specs.

I can certainly believe that, but I think the solution is to fix the implementation.

@dhritzkiv
Copy link
Contributor Author

dhritzkiv commented Sep 8, 2017

[...] I don't see any practical reason not to at least try to keep up with the WIP spec.

One reason is because it's generating objectively invalid glTF files. Whether it's the current assimp output (coded against glTF 1.0 KHR_materials_common), or models generated with the format from KhronosGroup/glTF#947, or the the format from KhronosGroup/glTF#965, or the spec from KHR_materials_cmnBlinnPhong. They can -and likely will- all change or go away.

pbrMetallicRoughness (part of core spec) and pbrSpecularGlossiness (official, ratified extension) are at least set in stone and will continue to work as long as there are loaders for glTF2.

assimp currently doesn't export KHR_materials_common properly against any of the WIP specs.

I can certainly believe that, but I think the solution is to fix the implementation.

As in my first paragraph: which work-in-progress spec do you target against, then?

@jamesgk
Copy link
Contributor

jamesgk commented Sep 8, 2017

One reason is because it's generating objectively invalid glTF files.

Practical was the key word. As long as I can load assimp output in Three.js, I think there's value to including the extension. What value does removing it have? Thus:

which work-in-progress spec do you target against, though?

I was targeting Three.js's implementation, and would continue doing so.

pbrMetallicRoughness (part of core spec) and pbrSpecularGlossiness (official, ratified extension) are at least set in stone and will continue to work as long as there are loaders for glTF2.

I certainly have no objection to supporting these.

@dhritzkiv
Copy link
Contributor Author

dhritzkiv commented Sep 8, 2017

As long as I can load assimp output in Three.js, I think there's value to including the extension.

But that's just it: you can no longer load current assimp glTF2 output in the current version (r87) of three.js. They may load for you, because your version of Three.js loads the incorrect implementation of KHR_materials_common, but not for others.

I believe your KHR_materials_common implementation "targeted" an early (r85 or earlier) release of three.js, where it was using glTF 1.0's KHR_materials_common spec. Compare:

And even if we updated the implementation (if a work-in-progress spec could even be agreed upon): that spec is bound to change in the future, again breaking exported models.

It's not wise to:

  1. implement an unfinished, unratified spec; and,
  2. use three.js (one importer, which itself has implemented incorrect versions of the spec) as the guide for how to implement it.

You're arguing for keeping something so that your pipeline will work, even if it's objectively incorrect.

What value does removing it have?

The value is that it won't produce invalid glTF files that will be obsolete in the future (and are already obsolete)

@jamesgk
Copy link
Contributor

jamesgk commented Sep 8, 2017

Sure, the current implementation is out of date. And if it's updated it will go out of date again. But I disagree that it's unwise to keep doing so. I need a pipeline that works now, so I'm very grateful that a) Three.js has implemented an incomplete spec and b) assimp has accepted an implementation of an incomplete spec. Without these implementations I wouldn't be able to use glTF 2. And it's not hard to imagine that there are other people in the same boat.

But if you insist on taking this out, I'll just use my fork. It's not such a big deal regardless.

And anyways, instead of just complaining about things I should also say: thank you for these additions and fixes! I'll look forward to using them when the KHR_materials_common/cmnBlinnPhong extension is ratified. 😛

collada can appear in many files, such as glTFs via the “generator” field (in the form of collada2gltf)
Binary glTF is now part of the glTF2 spec. However, it’s implemented incorrectly, so will be temporarily removed
Per spec

TEXCOORD -> TEXCOORD_0
COLOR -> COLOR_0
JOINTS -> JOINTS_0
WEIGHTS -> WEIGHTS_0

Remove JOINTMATRIX since it’s not supported (and doesn’t seem to be output, anyway)

TANGENT should be added at a later date
@kimkulling
Copy link
Member

The linux build seems to be broken. Could you take a look which kind of issue has occurred? And how should we handle the removal of the KHR_materials_common-extension.

Would it be possible to provide it as a special "post-process" ?

Segfaults on Linux for some reason. No other tests test exporting, so it’s fine
@dhritzkiv
Copy link
Contributor Author

dhritzkiv commented Sep 11, 2017

The linux build seems to be broken. Could you take a look which kind of issue has occurred?

For some reason, it segfaults during an scene export test when run on linux. OS X is fine. I removed that test, since no other format unit tests test exporting. This seems to have fixed the linux builds.


And how should we handle the removal of the KHR_materials_common-extension.

I removed KHR_materials_common extension code (opting for the built-in glTF 2 material spec as well as the complete KHR_materials_specularGlossiness extension) because the current version (in master) was based on an incorrect implementation found in another loader (three.js r85), which was based on the glTF 1.0 KHR_materials_common spec (which is not compatible with glTF 2.0). There are a few work-in-progress specs for a glTF 2.0 KHR_materials_common extension, however, they are undergoing many syntax changes, and may be replaced by another, similar extension: KHR_materials_cmnBlinnPhong (which is also a work-in-progress). The current KHR_materials_common output won't work anywhere (except that one older version of three.js) and newer output based on current rough version of KHR_materials_common spec may not work in the future.


Would it be possible to provide it as a special "post-process" ?

I'm not sure how to achieve this, as I'm not too familiar with the post process filter system.

However, once KHR_materials_common/KHR_materials_cmnBlinnPhong is complete, we could discuss what the default behaviour should be for exporting materials.

@turol
Copy link
Member

turol commented Sep 12, 2017

You can't just blindly disable unit tests because they fail. That's how you get PHP. Here's a Valgrind backtrace of the failure:

Invalid read of size 1
   at 0x4C2FE63: strcmp (vg_replace_strmem.c:846)
   by 0x4FD6F7C: aiGetMaterialProperty (MaterialSystem.cpp:77)
   by 0x53A6343: Get<unsigned int> (material.inl:120)
   by 0x53A6343: Assimp::glTF2Exporter::GetMatTexProp(aiMaterial const*, unsigned int&, char const*, aiTextureType, unsigned int) (glTF2Exporter.cpp:281)
   by 0x53A85EA: Assimp::glTF2Exporter::ExportMaterials() (glTF2Exporter.cpp:408)
   by 0x53AB5DF: Assimp::glTF2Exporter::glTF2Exporter(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*, bool) (glTF2Exporter.cpp:106)
   by 0x53ABD82: Assimp::ExportSceneGLTF2(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*) (glTF2Exporter.cpp:77)
   by 0x4FAAEBC: Assimp::Exporter::Export(aiScene const*, char const*, char const*, unsigned int, Assimp::ExportProperties const*) (Exporter.cpp:404)
   by 0x1880E0: exporterTest (utglTF2ImportExport.cpp:64)
   by 0x1880E0: utglTF2ImportExport_exportglTF2FromFileTest_Test::TestBody() (utglTF2ImportExport.cpp:78)
   by 0x15BF09: HandleSehExceptionsInMethodIfSupported<testing::Test, void> (gtest.cc:2402)
   by 0x15BF09: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (gtest.cc:2438)
   by 0x152819: testing::Test::Run() (gtest.cc:2474)
   by 0x1528FE: testing::TestInfo::Run() (gtest.cc:2656)
   by 0x152A34: testing::TestCase::Run() (gtest.cc:2774)
 Address 0x7503400 is 0 bytes inside a block of size 31 free'd
   at 0x4C2D31B: operator delete(void*) (vg_replace_malloc.c:576)
   by 0x53A6308: deallocate (new_allocator.h:125)
   by 0x53A6308: deallocate (alloc_traits.h:462)
   by 0x53A6308: _M_destroy (basic_string.h:210)
   by 0x53A6308: _M_dispose (basic_string.h:205)
   by 0x53A6308: ~basic_string (basic_string.h:620)
   by 0x53A6308: Assimp::glTF2Exporter::GetMatTexProp(aiMaterial const*, unsigned int&, char const*, aiTextureType, unsigned int) (glTF2Exporter.cpp:279)
   by 0x53A85EA: Assimp::glTF2Exporter::ExportMaterials() (glTF2Exporter.cpp:408)
   by 0x53AB5DF: Assimp::glTF2Exporter::glTF2Exporter(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*, bool) (glTF2Exporter.cpp:106)
   by 0x53ABD82: Assimp::ExportSceneGLTF2(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*) (glTF2Exporter.cpp:77)
   by 0x4FAAEBC: Assimp::Exporter::Export(aiScene const*, char const*, char const*, unsigned int, Assimp::ExportProperties const*) (Exporter.cpp:404)
   by 0x1880E0: exporterTest (utglTF2ImportExport.cpp:64)
   by 0x1880E0: utglTF2ImportExport_exportglTF2FromFileTest_Test::TestBody() (utglTF2ImportExport.cpp:78)
   by 0x15BF09: HandleSehExceptionsInMethodIfSupported<testing::Test, void> (gtest.cc:2402)
   by 0x15BF09: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (gtest.cc:2438)
   by 0x152819: testing::Test::Run() (gtest.cc:2474)
   by 0x1528FE: testing::TestInfo::Run() (gtest.cc:2656)
   by 0x152A34: testing::TestCase::Run() (gtest.cc:2774)
   by 0x152EDF: testing::internal::UnitTestImpl::RunAllTests() (gtest.cc:4649)
 Block was alloc'd at
   at 0x4C2C25F: operator new(unsigned long) (vg_replace_malloc.c:334)
   by 0x5F5AF89: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (basic_string.tcc:317)
   by 0x5F5C51A: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long) (basic_string.tcc:370)
   by 0x53A62AE: append (basic_string.h:1221)
   by 0x53A62AE: operator+<char, std::char_traits<char>, std::allocator<char> > (basic_string.h:5792)
   by 0x53A62AE: Assimp::glTF2Exporter::GetMatTexProp(aiMaterial const*, unsigned int&, char const*, aiTextureType, unsigned int) (glTF2Exporter.cpp:279)
   by 0x53A85EA: Assimp::glTF2Exporter::ExportMaterials() (glTF2Exporter.cpp:408)
   by 0x53AB5DF: Assimp::glTF2Exporter::glTF2Exporter(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*, bool) (glTF2Exporter.cpp:106)
   by 0x53ABD82: Assimp::ExportSceneGLTF2(char const*, Assimp::IOSystem*, aiScene const*, Assimp::ExportProperties const*) (glTF2Exporter.cpp:77)
   by 0x4FAAEBC: Assimp::Exporter::Export(aiScene const*, char const*, char const*, unsigned int, Assimp::ExportProperties const*) (Exporter.cpp:404)
   by 0x1880E0: exporterTest (utglTF2ImportExport.cpp:64)
   by 0x1880E0: utglTF2ImportExport_exportglTF2FromFileTest_Test::TestBody() (utglTF2ImportExport.cpp:78)
   by 0x15BF09: HandleSehExceptionsInMethodIfSupported<testing::Test, void> (gtest.cc:2402)
   by 0x15BF09: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (gtest.cc:2438)
   by 0x152819: testing::Test::Run() (gtest.cc:2474)
   by 0x1528FE: testing::TestInfo::Run() (gtest.cc:2656)

Here we can see a use-after-free bug. Looking at the code in glTF2Exporter.cpp the reason is that you're creating a temporary std::string but using its .c_str() after it's been destroyed. You need to keep the string alive if you need the value. The other variant of GetMatTexProp has the same bug.

@dhritzkiv
Copy link
Contributor Author

@turol Thanks for pointing out the source of the segfault. I couldn't reproduce it on macOS using clang, so I didn't know where to start. I also didn't know about Valgrind. So thanks for showing me!

The reason I removed the test (that I introduced) was that I figured since nearly all the other format unit tests didn't have exporting tests, then maybe there was a reason for that.

Anyway, seems the CI tests are still segfaulting. I'll keep looking for other causes.

@turol
Copy link
Member

turol commented Sep 12, 2017

On macOS you should be able to use AddressSanitizer.

There's still more bugs in there, some of which probably aren't your fault. On pastebin because it's damn long.

@johnmaf
Copy link
Contributor

johnmaf commented Sep 12, 2017

Hey @turol. I took stab at addressing a couple of the glTF2-specific issues. The segfault in the export unit test was caused by an incorrect use of the aiMaterial::Get function, which was overwriting the alphaMode string. Let us know if you see any other issues with the code!

@turol
Copy link
Member

turol commented Sep 12, 2017

I've fixed one of the issues with glTFAsset as part of #1427. Feel free to cherry-pick that commit into this one if you want, that PR might be a while since it enables Asan on Travis and cleaning up the issues will take some effort.

@turol
Copy link
Member

turol commented Sep 12, 2017

Forget that, it seems that MSVC can't deal with shared_ptr of an array while clang can.

@kimkulling
Copy link
Member

So, I guess I will merge it. We can use the 4.0.1 version for the HR_materials_common-extension. So when we want to support it we can get the code from there.

@kimkulling kimkulling merged commit d139b4d into assimp:master Sep 13, 2017
@kimkulling
Copy link
Member

Thanks a lot for the contribution. Please create issues for all the missing extensions.

@dhritzkiv dhritzkiv changed the title glTF 2 Importer + glTF 2 Spec Conformity [glTF2] Importer + Spec Conformity Nov 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants