KhronosGroup / glTF

glTF – Runtime 3D Asset Delivery
Other
7.14k stars 1.14k forks source link

Best practices for .gltf / .glb #1117

Open pjcozzi opened 6 years ago

pjcozzi commented 6 years ago

Documentation for when to export/import .gltf / .glb for different types of apps, e.g., desktop vs. web, consumer vs. developer, etc.

Not 100% sure where this should live but it should be easily discoverable. Perhaps an implementation section in the spec. Or maybe also a glTF Tutorial.

CC @sbtron

emackey commented 6 years ago

Thanks for starting this thread. Lately I've started to think that end users should generally always prefer .glb over .gltf. There are a few reasons: .glb is a single file, or a single network request, and much smaller than .gltf's base64-encoded "embedded" version. An individual file (or server asset) is easier to copy and transport (should I say transmit?) than multiples, and there's a lot less to worry about with broken relative URIs and such.

That said, a few apps still prefer .gltf: Substance Painter (since the app is primarily concerned with exporting separate textures, not finished models) and my own VSCode extension (since VSCode is a text editor). Are there any others? It seems like the whole rest of the ecosystem is likely to embrace .glb primarily.

It's too late to change now, but .glb isn't the greatest extension name, when you think about it. It doesn't stand for anything on its own (Graphics Library... Binary?) and isn't the name of this repo or the format. Had I figured this out a year ago I would have lobbied for .gtf, Graphics Transmission Format. Still, COLLADA became known as .dae, so, I guess we can keep .glb and it will do fine.

Anyway, regardless of the name, I think the self-contained binary form of glTF is going to win over the ecosystem in the long run.

xelatihy commented 6 years ago

For desktop use and if you are concerned about editing and versioning, .gltf is a must. In fact, we found it to be bext used with one binary blog per mesh, so meshes are like textures. Obviously other apps will likely do it differently.

emackey commented 6 years ago

Yes, exactly: During creation, editing (and possibly debugging) of the glTF, the .gltf file with separate textures is best, and I hadn't even thought of separate re-usable bin files, that's an interesting twist. The examples I gave (Substance Painter and VSCode) are both on the editing side, which makes sense in this light.

Once the final, deliverable asset is finished though, the self-contained .glb file is best for end users who don't need to edit it.

Certainly we need an easy path for users to convert one to the other.

jtorresfabra commented 6 years ago

For 3D Tiles is also a must in my opinion. And in general where bandwith is a priority.

sakrist commented 6 years ago

I think .gltf more like for web and development stage. And .glb for final model which is for use in desktop and mobile apps. Basically the same as above.

zellski commented 6 years ago

I think the .glb format is clearly superior for any kind of final delivery. There are only upsides, right? It's not as if you give up the ability to externally load additional buffers. You just get one conveniently available as part of the initial payload, without need for secondary HTTP round-trips. You can use it for a little bit of data (maybe enough to render a low-fidelity preview) or for all of it. Most of the time, it's likely to be the latter, because as @emackey suggested up there, the distinction between "here is a single atomic deliverable that you can toss at any loader" and "here's a starting point from which to make further relative URL requests" is pretty huge.

javagl commented 6 years ago

Although I cannot speak for real users, there are several aspect that might be relevant for dicussing the "best practices", and maybe even affect of further development or recommendations.

One of them is the "JSON refer other JSON?" issue, https://github.com/KhronosGroup/glTF/issues/37 , which has occasionally brought up. Somewhat related to that: I think there also was a discussion about the guidelines for external references in GLB in general, although the question is only relevant for images and buffers now, and not for other assets.

The main point is: A GLB file is not necessarily self-contained. From just having a GLB, you never know whether it contains references to external data or not.

Or should one of the "best practices" be exactly this recommendation - namely, to not have external references in GLB? (That would sound like a considerable restriction of use-cases for me...)

donmccurdy commented 6 years ago

It seems easiest and perhaps most important to suggest a best practice on what should be the default output of consumer applications, e.g. future tools like Sketchfab, Substance Painter, and Minecraft's exporter. For this case, I think a self-contained file is the essential thing, whether it's .gltf or .glb. More likely .glb.

As a recommendation for developer workflows and final delivery, we should probably do some real-world tests before proposing something here. .glb seems preferable in theory, but for delivery on the web there may be some advantage to being able to load images in parallel. There's some interest in three.js to support asynchronous texture request and GPU upload, in which case textures shouldn't block geometry. In a couple very non-scientific tests of the Boombox sample, the metal/rough .glb loaded marginally (~5-15%) slower than than the (non-embedded) .glTF metal/rough and spec/gloss samples.

boombox_profile

lexaknyazev commented 6 years ago

Servers could use transport-level compression on pure JSON .gltf files. It may be less efficient with .glb.

We may also evaluate extending GLB with LZ (brotli?) compression of JSON chunk.

cg-cnu commented 6 years ago

@donmccurdy Valid points there. For developer/artist sanity a single .glb make sense but when it comes to delivering to the end user I don't see the single network request provided by .glb as a huge advantage, especially with the upcoming http2 protocol.

Wondering how does texture re-usability works πŸ€” If i have two models using the same texture, exporting the models as a separate .glb files includes the texture in both the .glb files ?

lexaknyazev commented 6 years ago

If i have two models using the same texture, exporting the models as a separate .glb files includes the texture in both the .glb files ?

Yes, unless common texture is stored separately (as an image file or as a part of shared binary buffer).

jbouny commented 6 years ago

In Substance Painter, as said, we are currently only exporting a .gltf with an external buffer and png files (I am one of the developer of this software). It is not really a spoiler, but it is just our first step to support gltf ecosystem.

Even if we are only exporting static objects, I think being able to directly export an 'engine ready' asset as a .glb self-contained file is also important to provide to our users. With gltf, there are many export options, which is great as they all can have their benefits (network issues, re-edition, reuse, etc.). I am personally seduced by the asynchronous texture request possibility in http2 (as example: glb + external textures).

We should soon expose full control as .gltf/.glb and external/contained textures; but it could indeed be dependent of the format evolution and usage.

zellski commented 6 years ago

Pardon the partial digression, but it seems to me that the importance of exporting on just the right format would diminish significantly if there were a dependable, free, swiss-army-knife type utility that could convert omnidirectionally between format variations. It could be npm-installed locally for lightning fast execution, or in the cloud (for tiny amounts of money) for convenience. The most obvious candidate would seem to be https://github.com/AnalyticalGraphicsInc/gltf-pipeline, but I'm not sure a web-based GUI is on their roadmap? Anyone else?

javagl commented 6 years ago

A big +1 @zellski . Af course, such a tool should definitely exist. But still, the questions about file sizes, number of requests etc. are still relevant, and the answers not obvious. There should probably be an overview about the pros/cons of each representation for each application case.


Somehow off-topic, regarding the conversion tool:

One important question regarding this conversion utility is the one that you already referred to, namely the form of delivery and availability. I think some sort of standalone executable would be nice, to be used in at the command line or for batch processing. But it could also expose the command-line functionality via a trivial interface so that it could be used as a library.

A broader question would be about the form or feature set of this tool. I think that there may be different levels of granularity to answer the question of what should be exported how:

  1. Just convert a whole glTF+BIN+Images into a GLB
  2. Convert a glTF+BIN into a GLB, but keep the images as external references
  3. Convert a glTF+BIN+Images into a GLB, but keep some specific (user-selected) images as external references

(just examples, different combinations may be possible). Some of these conversion options are already covered in the gltf-pipeline, but others are not yet direclty supported (by any tool, as far as I know). The most fine-grained export options might look a bit complicated, though...

exporting

I already considered to wrap a small GUI like this around some of the https://github.com/javagl/JglTF libraries, and will probably give it a try when I have some more time. But the gltf-pipeline would be the better candidate for an official solution, due to its broader community support.

donmccurdy commented 6 years ago

Once glTF-pipeline fully supports glTF 2.0 (progress), it should be very possible to make a drag-and-drop web-based tool, or an Electron desktop GUI, for doing the conversions. Could be very handy. :)

javagl commented 6 years ago

Just a short cross-reference that I just stumbled over - the Best Practices section of the pbrSpecularGlossiness Extension :

To get the best of both worlds a glTF asset can include both metallic-roughness and specular-glossiness in a single glTF asset. [ ... ] Since such an approach requires including both material models it is best suited for a web scenario where a client can choose to download the appropriate material model from a server hosting the glTF asset.

We should be careful to not make toooo contradicting statements here: Including both in a GLB may cause it to become prohibitively large.

emackey commented 6 years ago

πŸŽ‰ UPDATE πŸŽ‰

The VSCode extension got an external contribution in AnalyticalGraphicsInc/gltf-vscode#35, and now includes a command to export the glTF file you're editing to GLB format. Currently, this export includes an implicit bundling of all external references to embedded GLB chunks, which I think is not unreasonable for most use cases. I'm exited about this one if you can't tell. πŸ˜ƒ

sbtron commented 6 years ago

I wanted to give a big +1 to @emackey 's comment on β€œend users” (not developers or technical artists) only seeing a self-contained .glb file especially when interacting with it as a file on disk. We are seeing feedback from end users being confused by the .gltf and associated files. Specifically there have been many instances of trying to share a .gltf json file over email or cloud share without the associated geometry or textures resulting in the recipient not being able to view the asset.

I would strongly encourage any tool or service exporting .gltf also provide a self-contained .glb export option. It will be lot easier for end users to learn to view and share a single .glb file.

If your tool is already writing out a .gltf it should be quite straightforward to package that out into a self- contained glb. The .glb export by @najadojo for VSCode extension is a very good starting point if you are looking for examples on how this can be done- https://github.com/AnalyticalGraphicsInc/gltf-vscode/blob/master/src/exportProvider.ts
(Hint @jbouny , @AurL :) )

AurL commented 6 years ago

On our side, it will obviously make sense to export .glb instead of the classic .gltf+ dependencies. It was more convenient for us to keep a human readable version to easily debug and spot any issue, and it will probably be the case while we include glTF more and more in our pipelines, but once we have something stable, we will obviously switch to the binary packed version everywhere. (BTW, we recently added animation in our glTF export , or here )

emackey commented 6 years ago

@AurL That animated Swiss Army Knife from your second link is amazing.

sbtron commented 6 years ago

It was more convenient for us to keep a human readable version to easily debug and spot any issue

Good point we need more debugging tools around glb :). The recent vscode extension update is quite useful in breaking apart a glb for editing/debugging. Curious to know if there are any other requirements?

AurL commented 6 years ago

Nice! This tool is great (thanks @emackey for this btw) and I guess it now answers all the requirement, I don't have any other reason in mind to keep the current .gltf layout, I'll make a note. Thanks

Update: I hope the VS Code 5MB size limitation will be configurable soon, since it's not possible to inspect most of the .glb here

Fabrice3D commented 6 years ago

Back to the gltf fun after few months client work. I've started work on the .glb exports. So far so goed, bin+json are in and validator says he's happy.

Now, next is to embed the images... on the doc I could find, for images embed, its a bit unclear how I need to define the pointer that replaces the previous "uri" to the alternative that points to the images chunks...

Me naive, I was expecting something logic and simple like {chunk:2, mimeType:"image/jpeg"} as I do know from json its an image, and that its being stored, I even expected it would keep the uri value, so I could even save locally the image using the same name/path. This also would prevent to have multiple exporters/variations, as @javagl commented on oct 14 to select the export type. Basically I was expecting same json, only the addition of the integer to pick the right image to read it. As its parsed from glb extension, you know in image that there would be either uri or chunk integer...

Short story, as its not the expected to me, could someone tell me how do I replace the uri to point to the chunk and/or a ref where this is described? thx!

lexaknyazev commented 6 years ago

@Fabrice3D There's a bit more indirection with storing images in GLB. For each image an asset needs to have a bufferView object. These views will point to some buffer object. This allows flexible packing of data β€” you can use one .bin file for geometry and the other .bin file for all images.

BIN chunk from GLB is represented by buffer object with undefined uri.

lexaknyazev commented 6 years ago

(Cont.) Images accessed via bufferViews have image.bufferView instead of image.uri.

For one-file delivery, all bufferViews (both with geometry and with image data) must point to the same buffer.

zellski commented 6 years ago

It sounds like @Fabrice3D expected a GLB chunk for each image. Instead, as @lexaknyazev says, just slam the encoded image bytes into a dedicated BufferView and point to that. It's not much more work, presumably your exporter already has some infrastructure in place to do similar operations.

Fabrice3D commented 6 years ago

thx for the fast reply @lexaknyazev I was afraid It would not be the expected... why do simple when you can do complicated :)

Any specifics props on the buffers and views objects I need to specify? or it is similar to the bin one?

lexaknyazev commented 6 years ago

why do simple when you can do complicated

Some people wanted to keep text-based JSON as a separate file while putting all binary data into one blob.

Fabrice3D commented 6 years ago

Oh wait I see edits... you mean all the images become one chunk ref, and the buffer views are then defining the offsets just like for the bin data... so its in fact seen as a 3 pieces file, json, bin, and depenciesbytes. I see the attempt to be consistent, but really overkill, again, this may be my import/export strategies..

Fabrice3D commented 6 years ago

Also, its not necessary to add padding per image then no?

zellski commented 6 years ago

I think you're confusing some interrelated concepts (while criticising them, which is a weird). You can use one or more buffers, although if you are building a GLB you are best off sticking with the "freebie" one you get from there. I would stop talking about 'chunks' entirely, as that's specific to GLB creation and you will almost always just have two: JSON and the binary one.

For GLB creation you typically just slam everything into your one buffer. It's a complete jumble of data. That's why there are BufferViews. They are just views into the Buffer jumble that start at a given offset and have a certain length. Once you've copied your binary image data into the main Buffer one, and configured your new BufferView to point at the right position, you're good to go -- just reference the view it much as you yourself suggested above, except not with chunks -- more something like {bufferView: 3, mimeType: "image/jpeg"}.

All BufferViews need to be 4-byte aligned within the Buffer, yes.

lexaknyazev commented 6 years ago

All BufferViews need to be 4-byte aligned within the Buffer, yes.

Not necessarily. If one puts all images after vertex buffers then each image don't have to be aligned.

zellski commented 6 years ago

Really! Huh. My apologies. (Though I think I'll continue to do it in my own code.)

Fabrice3D commented 6 years ago

Its not a critic, as said, I see the wish to be consistent. As I followed the doc to add each images, I was expecting the strategy to first isolate the json, bin and "images"/rest, as they are stored in logical order, when I load a glb, I have these "images" already separated, simply because you must first extract the json and the bin, so continuing to isolate each parts is logic to me. From this logic, I do no need again to look at buffer views, to get the offset and extract the data, I have them already. In fact, while the exporter will define this, my parser would totally skip that part.

Fabrice3D commented 6 years ago

Ok, followed @zellski advise, and added the images to the bin vs add a new buffer. I ran the validator and looks ok. screen shot 2018-03-07 at 16 35 35

As I've added the "injection" of the images bytes to the exporter routine. I wonder if need to keep the type and componenType. { "name": "bytes_img_4", "bufferView": 28, "componentType": 5120, "count": 170698, "type": "SCALAR" }

lexaknyazev commented 6 years ago

@Fabrice3D Looks like your buffer has buffer.uri defined, so GLB binary chunk is unused.

lexaknyazev commented 6 years ago

Also, you don't need to create accessors for images, since image.bufferView refers to bufferViews directly. Btw, most of glTF sample models have GLB versions, e.g., https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/BarramundiFish/glTF-Binary/BarramundiFish.glb

Fabrice3D commented 6 years ago

As usual thx for the fast reply @lexaknyazev ! You lost me a bit. Can you explain: "so GLB binary chunk is unused". Meanwhile will go study the link...

lexaknyazev commented 6 years ago

Validation report above shows that an asset refers to full.bin - an external file.

Fabrice3D commented 6 years ago

you mean this? where uri should be something else? "buffers": [ { "byteLength": 3949602, "uri": "full.bin" } ]

lexaknyazev commented 6 years ago

@Fabrice3D see this section: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#glb-stored-buffer

Fabrice3D commented 6 years ago

ah ok... now validator gives me this: { "pointer": "/buffers/0", "mimeType": "application/gltf-buffer", "storage": "glb", "byteLength": 3949604 }, { "pointer": "/images/0", "mimeType": "image/jpeg", "storage": "bufferView", "image": { "width": 2048, "height": 2048, "format": "RGB", "bits": 8 } }, + more images

now the "image.bufferView refers to bufferViews directly." part...

Fabrice3D commented 6 years ago

Just to be sure, when parsing the glb, the bufferViews.byteOffset are expected to be counted from 0 as in external .bin case or from 12 bytes header + (0x4E4F534A ) json.length?

lexaknyazev commented 6 years ago

From 0, so 0 means the first byte of BIN chunk.

Fabrice3D commented 6 years ago

ok thank you, so encoding both gltf and glb at same time, I do not need to offset their values for the glb version. I have a ACCESSOR_NON_UNIT error that I try figure out... all fine in regular gltf export.

lexaknyazev commented 6 years ago

Probably, that's because you didn't validate .bin file before. You can drag-n-drop it with .gltf into the validator.

Fabrice3D commented 6 years ago

if I drop the gltf , no errors. The glb file only gives the errors.

lexaknyazev commented 6 years ago

Are you dropping both .gltf and .bin files (simultaneously)?

Fabrice3D commented 6 years ago

ah a secret feature! :) I do get the same error now. I can load the gltf file in my app and just tried loading it with threejs, no prob either. All displays fine. What can it be??

donmccurdy commented 6 years ago

If it's helpful, here is a glTF chatroom: https://gitter.im/KhronosGroup/glTF πŸ˜