vpenades / SharpGLTF

glTF reader and writer for .NET Standard
MIT License
457 stars 72 forks source link

Reference to external .bin buffer #106

Closed christjt closed 1 year ago

christjt commented 2 years ago

According to the GLB specification a .glb can reference one BIN chunk that will be embedded in the GLB file as well as other buffers specified with a uri field.

Is there any way to do this using this library? As far as I can tell there is no way to create a Buffer that specifies a uri. I'd be willing to contribute this functionality, but just want to make sure this is indeed not possible today.

The case I am trying to solve is that I am creating multiple .glb files that should (among other things) reference the same .bin file.

vpenades commented 2 years ago

there's two separate issues in your question.

  1. If you want to reference external BIN files, you have to use .glTF extension, not .glb extension. GLB exists to allow packing all the resource files into a single file, so there's no point in writing a .glb and then reference external .bin files.

  2. SharpGLTF is designed to cover the most commonly used case scenarios, and provides an API to many it easy with these use cases. In exchange, uncommon case scenarios are not well covered, or not covered at all.

Now, for possible solutions: Assuming you want to share binary buffers between multiple glTFs, it can be achieved in a number of ways, based on the needs of different developers, so your proposed solution might not fit other developers requirements. Keep in mind your solution must work at a high level, not a low level solution.

The best approach for what you want to do is to add a binary buffer write hook in WriteSettings, similar to the ImageWriteHook.

There's an example of sharing a texture between two models here but notice that this solution is only for textures, it would need to be extended for binary buffers.

Also, I would refrain from letting users simply write the Uri of the buffer directly; it will conflict with many other features of the library.

christjt commented 2 years ago

I'll have to respectfully disagree with the first point, the case with embedded BIN and external BIN using the .glb extension is defined by the spec and it even contains an example with exactly this use-case (https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-stored-buffer). I definitely see a point where you bundle all independent resources for a given asset and have some shared resources. For example if you have many different assets that share some subset of the geometry.

In any case I respect not cluttering the API with uncommon use-cases and the API is very clean and nice to work with currently which is great.

Thanks for the feedback.

vpenades commented 2 years ago

Yes, the spec does support a GLB referencing external files.... another matter is if it makes sense in most scenarios. Personally, if I would need to do something like that, I would simply switch to glTF altogether.

This doesn't mean it's not worth to explore how to do stuff like this. The problem is that it's hard to mix low level features with high level features. Usually high level features require an API to drop low level support if you don't want to overcomplicate things.

My understanding is that the main purpose of this requirement (sharing BIN files) is to reduce memory footprint of a collection of glTFs by letting them share as many bits as possible. I was planning to do something like that by allowing to configure a number of directories to look for matching assets, so whenever you save a glTF into one of those "sharing" directories, the runtime would look for matching binary files and reuse them.

christjt commented 2 years ago

That sounds like a good solution, I am not quite sure what that would look like in practice though.

For my specific case I am leveraging the gpu instancing extension and I have multiple assets that share multiple instances of a sub-mesh. And this is indeed to limit the memory footprint.

If you are able to scope out the task I might be able to implement some of it.

vpenades commented 2 years ago

A different solution would be to write a custom tool just to do that.

Using Newtonsoft it could be possible to load the json as a browsable document, then gather the BufferViews of multiple glTFs, identify shareable blocks, and then rewrite the buffers and bufferviews of all the glTFs.

This kind of tool would have the advantage of working with any glTF, even those using extensions currently unsupported by SharpGLTF

RagingKore commented 2 years ago

@vpenades first and foremost, awesome work you did here. On topic, I would say that given that this capability is in the specification, I implemented in the core would feel more complete and meaningful.

Using Newtonsoft would destroy performance, but I guess the idea behind it is what matters. And if I understood correctly, we would need to somehow scan or browse before deserializing the data.

If so, perhaps System.Text.Json could be used to achieve the same result as shown here.

Deserialize subsections of a JSON payload

vpenades commented 2 years ago

@RagingKore SharpGLTF does use System.Text.Json internally for performance.

The idea of using Newtonsoft instead is because what you want is to blindly load the Json into a json DOM, preserving all the original nodes, and adding/removing elements by editing the DOM itself. Buffer and BufferView entries are simple enough to do so without altering the rest of the glTF document.

Actually, if I would be me, I would add this feature that way, because doing so within SharpGLTF architecture would overcomplicate things in the serializer.