Closed jo-chemla closed 1 month ago
These --options
are intended for a very narrow use-case. (And maybe that could be made clearer in the description). They are only passed to the gltf-pipeline
for the case where the glTF that is contained in B3DM- or I3DM files is supposed to be upgraded from glTF 1.0 to glTF 2.0. (This in itself is only possible for very few cases - basically, cases where ~"the GLSL shader code (that was still stored in glTF 1.0!) has a certain structure"). So when you have a veeery old tileset, where the B3DMs still contain glTF 1.0 data, then the upgrade
(with --targetVersion 1.0
, the default) will try to convert this glTF 1.0 into glTF 2.0. And for rare, special cases, it can be useful to pass in additional --options
for the conversion.
Or more focussed on your question: There is no built-in mechanism for doing arbitrary invovations of gltf-pipeline
(and consequently, no mechanism to pass --options
to such an invocation).
But... in a more abstract form, this is a common use-case: "Perform operation X on every tile content". This can often be done with a few lines of custom code. And an approach for generalizing this was ... actually drafted even in one of the first versions of the 3D Tiles Tools. Namely, the concept of a "pipeline". Unfortunately, this was never really settled/solidified (even though it could be remarkably powerful). But fortunately, one of the "initial draft" functionalities that still does work could be helpful here:
Put this into some pipeline.json
:
{
"input": "C:/data/glb/tileset.json",
"output": "C:/data/gltf/tileset.json",
"tilesetStages": [
{
"name": "ConvertGlbToGltf",
"contentStages": [
{
"name": "separateGltf"
}
]
}
]
}
and run this with
npx 3d-tiles-tools pipeline -i .\pipeline.json
It will read the input, and convert each .glb
into a .gltf
+resources (updating the content.uri
s accordingly).
(The "magic" here is that "name": "separateGltf"
, which is sort of a "predefined command". Generalizing this properly, and defining a real "file format" for the pipelines, including a JSON schema and such, is ... a long-term-maybe 'TODO'...)
I'm still curious about how you intend to do the next step. I don't know how much tooling exists, in NodeJS or in general, for the required steps:
ktx transcode --target rgba8 example.ktx2 output.raw
to convert the KTX data into raw RGBA pixel values, but ... then you'd still have to convert raw RGBA pixel values into PNG. Yeah, it's doable (I just tried this out (in Java), and ... yeah, there are the usual quirks (with the image being flipped vertically and such)), but ... a bit of a hassle....gltf
files back into .glb
(there is no "opposite" of separateGltf
in the current state of that "pipeline" support 😬 )The easiest approach would be - iff glTF-Transform supported decompressing KTX2 - to just use the following skeleton for "generic tile content processing". In that callback that currently contains the log message, you could ...
Document
from the sourceEntry.value
(GLB data)transform(ktxToPng())
, if this existed...targetEntry.value
import { TilesetEntry } from "./src/tilesets";
import { BasicTilesetProcessor } from "./src/tools";
async function example() {
const source = "./input/tileset.json";
const target = "./output/tileset.json";
const overwrite = true;
const tilesetProcessor = new BasicTilesetProcessor();
await tilesetProcessor.begin(source, target, overwrite);
// Process all entries that are tile content
await tilesetProcessor.processTileContentEntries(
(uri: string) => uri,
async (
sourceEntry: TilesetEntry,
type: string | undefined
): Promise<TilesetEntry> => {
console.log("In processTileContentEntries");
console.log(" name:" + sourceEntry.key);
console.log(" type: " + type);
const targetEntry : TilesetEntry = {
key: sourceEntry.key,
value: sourceEntry.value
}
return targetEntry;
}
);
await tilesetProcessor.end();
}
example();
It might even be possible to come up with some hacky solution here, like an fs.writeFileSync(..."tempInput")
, followed by a (CLI!) call to ktx transcode
, and then some fs.readFileSync("tempOutput")
to read the RGBA pixels, and do some sharp
call to convert these pixels to PNG and sneak the result back into the glTF-Transform Texture
- but this could only be remotely reasonable iff (if and only if) all this was supposed to be a one-shot operation for one specific tileset...
Once again, thank you so much for all these details, really full of insights. This is indeed a once-in-a-lifetime workflow that shall not be applied in production, but only to one tileset we did receive that do not match our current expectations.
Combining 3d-tiles-tools tilesetProcessor.processTileContentEntries
with gltf-transform document.transform
(with a custom transform) is indeed exactly the way to go. I gave it a shot, and avoided CLI calls as much as possible - but still relied quite heavily on node synchronous operations.
Note the ktx pipeline can directly export a ktx file to image file via the extract
CLI method. There also seem to be js bindings to libktx, see here and there as wasm, but seem not yet very straightforward.
The pipeline now looks like the following:
tilesetProcessor.processTileContentEntries
GltfUtilities.upgradeGlb
since gltf-transform can only process version 2.0 gltf, and write to fileio.read
that 2.0 document, and transform
with a custom pipeline thatktx
CLI can process ktx extract TEMP_KTX2_TEX TEMP_PNG_TEX
to write to pngtexture.setImage(pngImg)
so the gltf-transform Document has a new png textureI'm ~mostly~ done with the below code in next post - fighting with sharp when trying to use cloneDocument, seems to be due to an issue that was fixed by a release of gltf-transform that is more recent that the one used in 3d-tiles-tools repo. Again, thanks a lot for your help!
OK, that looks like a fleshed-out version of that "hacky" solution that I mentioned, with that "hacky" part mainly being the necessity to call the KTX CLI tool.
I only have a rough understanding/memory of the current pain points:
About sharp:
I remember that I once stumbled over some obscure error messages related to sharp
. It was something that involved "Could not load the "sharp" module..." messages. And it was related to some version incompatibilty, caused by some native library that sharp
is using under the hood, and that somehow ~"can only be loaded in one version", meaning that it shows up when there are two different sharp
versions in the dependency tree. IIRC, I stumbled over https://github.com/lovell/sharp/issues/3870#issuecomment-1833121113 and (even though I'm on Windows) helplessly downgraded some version of sharp somewhere, and it worked...
About the CLI call: There once had been JS bindings for KTX. And technically, they are still there. But the documentation at https://github.khronos.org/KTX-Software/ktxjswrappers/index.html says that they are deprectated. But I know that they recently have been extended, somewhere in the context of https://github.com/KhronosGroup/glTF-Compressor/tree/main/source/libs . But I think that these changes did not yet make their way back into the main repo. So ... the tl;dr of all these 'but's': The KTX-Software repo (and mainly the bindings) may ... require some work. (I started cleaning up the Java bindings a while ago, at https://github.com/KhronosGroup/KTX-Software/pull/886 , but ... have to juggle with priorities here...)
The 3D Tiles Tools actually contain a KtxUtility
class (used in some of the GltfTransform....
utility classes). But being based on the BinomialLLC encoder, this currently only offers encoding into KTX, and not decoding. Maybe it will be extended in the future....
(A small aside: It's not entirely clear why you are writing the glTF 2.0 data into files. It should be possible to just read the GLB data with await io.readBinary(someBuffer);
. But I'd have to read the code more thoroughly...)
Thanks again for getting back.
io.readBinary(someBuffer);
. I'm starting to grasp better how these tileset and mesh processing libraries do work, thanks a lot for all these pointerscloneDocument(sourceDocument)
since the operations I'm doing do not seem to happen in place, so the errors fades away. I just edited the above code snippet to fix minor errors. Last thing I needed to be doing is that the document still had the KHR_texture_basisu
listed as extensionsRequired
and extensionsUsed
although the texture is now png. The way to do it via gltf-transform utility methods was to use extension.dispose()
- just spawned a discussion here. Consider this thread done, thanks again!
Hi there,
I'm trying to parse through every glb tile of a large tileset, trying to decompress textures which are compressed with KTX2. Note KTX2 decompression is not yet available within gltf-pipeline - see probable blockers in this thread for gltf-transform, and linked root issue thread to enable support within
KhronosGroup/Basis-Universal-Transcoders
- so I'll do this by myself, but would need to explode everyglb
tile togltf + bin geometry + ktx2 texture
.I've been trying several iterations of the
upgrade
command with the--options
flag passed to either--separateTextures
,--separate
to explode geometry and textures, or--json
to convert gltf to glb but they all resulted in glb tiles - with packed geometry and textures. By reading through the code, it seems that the targetVersion has to be 1.0 for thesegltfUpgradeOptions
to be passed, but I feel like I'm misunderstanding how to process every tile with gltf-pipeline.Here are some example commands I've tested. Any help would be appreciated! Best regards,