-
-
Notifications
You must be signed in to change notification settings - Fork 35.6k
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
GLTFLoader: Extensibility #11682
Comments
Hi @Jeremy-Gaillard — I like the idea of modifying the glTF loader to support user-defined extensions, rather than just the extensions we've implemented ourselves. This would make it much easier to support draft extensions like #11577. Definite +1 from me on that. Hopefully we could do this by having a common interface for all extensions with methods like However, I think we would be implementing that extensibility on GLTF2Loader, and not GLTFLoader — are you able to upgrade to the newer loader?
This seems specific to glTF1, as glTF2 does not have custom shaders currently. If it's not too much trouble, it may be best to keep your hack as-is, and if it's still an issue when GLTF2Loader gets custom shader support later, we'll figure it out then. Most development work is on GLTF2Loader at this point. Some background on the shader thing: custom GLSL shaders were part of the core glTF1.0 specification, and the default export format in COLLADA2GLTF. Since glTF2.0, they've been moved to an optional extension, and that extension hasn't been fully defined yet. The default now is metal-rough PBR, with options for spec-gloss PBR or blinn-phong. |
Yes, that is the kind of feature that I need. But I wonder if it would be enough, especially for nested processes (like this switch). Maybe refactoring some of the code could help us. For example, we could replace the switch with something like this: // Create a list of supported attributes
// The list can be modified by the user to add new attributes
this.supportedAttributes = {};
this.supportedAttributes['POSITION'] = {
triangleAddAttribute: (geometry, bufferAttribute) => geometry.addAttribute( 'position', bufferAttribute ),
lineAddAttribute: (geometry, bufferAttribute) => geometry.addAttribute( 'position', bufferAttribute ),
replaceShader: (shaderText, regEx) => shaderText = shaderText.replace( regEx, 'position' )
};
...
// Replace switch l.1492 by this
this.supportedAttributes[semantic].replaceShader(shaderText, regEx);
// Replace switch l.2134 by this
this.supportedAttributes[attributeId].triangleAddAttribute(geometry, bufferAttribute);
// Replace switch l.2311 by this
this.supportedAttributes[attributeId].lineAddAttribute(geometry, bufferAttribute);
Good to know. Upgrading to GLTF2Loader should not be a problem. |
For custom attributes, a syntax like this might work? class MyAttributeExtension extends GLTFExtension {
constructor ( json ) {
this.json = json;
}
onAfterGeometry ( primitive, dependencies, geometry ) {
if ( primitive.attributes[ 'myAttribute' ] ) {
geometry.addAttribute( 'myAttribute', bufferAttribute );
}
}
}
const loader = new GLTFLoader();
loader.registerExtension(MyExtension);
loader.load('foo.gltf', function () {
// ...
}); As for patching the shaders, |
@stevenvergenz I'd be interested in your thoughts on this sort of extension syntax, for making it easier to experiment on the |
@donmccurdy I really like the idea of having free-standing extension support, without having to mess with the core loader. If you can define enough functionality hooks to cover the extension surface, I'd be happy to use the AVR extensions as a test. |
So I was playing around lately with the idea of @donmccurdy in the first comment
I noticed, that you have to add the draco decoder by an extra function (which seems a bit odd in my optinion btw). With the above API the user can add custom extensions.
So far the raw idea of hooks. There are a coule of more thoughts that need to be done, like how to implement calls eg. If someones interested, look at this branch vspdi/three.js/gltf-custom-extension If someone has any better ideas, tell me where to help, I would even like to implement that, because that is a use case i really would love to see 😄 |
An issue with wrapping the
Before/after hooks might solve that. I think I'd also suggest that the extension explicitly declare the hooks where it needs to be called, rather than (a) relying on an extension being attached to that point, or (b) checking for existence of particular methods. An extension's data might be on a node, but it actually needs to do its processing after the scene is constructed (e.g. for skinning). If the hook code can go exclusively in
Just a compromise – we don't want to depend on a global With the changes above, I think the API becomes something like: class MyExtension {
constructor ( json, parser ) {
},
beforeTexture ( textureDef ) { ... }
afterTexture ( textureDef, texture ) { ... }
}
loader.registerExtension( 'EXT_texture_foo', MyExtension, ['texture'] ) As a candidate to consider, I think we could move the |
Yeah, already thought about that idea. The concept i purposed just covers the "after" state. You mean, by before, you would provide that value (eg. for a texture) and what happens to that afterwards?
I guess you mean instead of Just wrap the execution here examples/js/loaders/GLTFLoader.js like this?
Ah ok, sounds right
With no checking of method existance and just giving the denpendency type which is used you will need to implement always both methods, dont you? Otherwise the type of dependency must be more specific than But, why not both? |
I'm interested in the approach discussed here and I'm locally experimenting, too, to see if the following plugins likely can be implemented and how their code look like with a new extensible system (I call plugin here).
Code is below if anyone is interested in. Repository Plugin API (WIP) Plugins (2, 3) Plugins (2, 3) registration |
The Plugin API I'm trying so far is
Return value from So case 'scene':
dependency = this._onBefore( 'Scene', json.scenes[ index ] ).then( function ( sceneDef ) {
return parser.loadScene( sceneDef ).then( function ( scene ) {
return parser._onAfter( 'Scene', scene, sceneDef );
} );
} ); In the code above, Refer to the links in the previous comment for the detail code of plugin API, plugin implementation, plugin registration, and so on. We need to discuss more for proper plugin API for sure. But from my experiment I think at least extensible system looks right direction because
Extensions maintenance can be easier while we can keep |
On a similar topic, I may need to postprocess some geometry before exporting to glTF. Would there be any interest if I proposed a similar extension mechanism for GLTFExporter? |
Yeah, I think good to open a new issue. |
I want to move But I realized PR will be big and it will be hard to review/discuss if I do everything, adding Plugin API and moving all existing extension handling to plugin system, in a single PR. So I want to do step by step. First PR is for discussing and adding the API. And in second or later PRs we move existing extensions handling to plugin system one by one. (First PR might include a few of them to help discussion.) Please note, this won't block adding a new extension support even before we complete the all. For example we may want to add basis texture extension soon. If it sounds good, I'll start to make a first PR. |
+1 for adding the API first. If existing extensions can safely use that API that would be good to know early on, but maybe we wait to merge that part of the change?
It will be a while before that extension is ready, I think. The |
Yes, knowing early on helps determine the API. But everything in a single PR is very hard to review. This is my trial branch. LOC +3000, -2000. dev...takahirox:GLTFLoaderPlugin So I'm thinking to first make a branch including everything and discuss API. And after fixing API, splitting it up into PRs, one PR per one extension moving to plugin system. |
I do not recall anyone trying that approach before... It sounds like a good idea to me. :-) |
@takahirox thank you! |
Made a new branch including existing extensions moved to plugin system. dev...takahirox:GLTFLoaderPlugin2 LOC +750 -700 now. Plugin API I'm trying is. class MyExtension {
constructor () {
this.name = 'EXT_texture_foo';
this.targets = ['Texture']; // Thinking if this property is really needed
// This means,
// if json.textures[textureIndex].extensions['EXT_texture_foo'] exists
// the following methods will be called while parsing json.textures[textureIndex]
},
// If onBeforeXXX method is defined,
// it is called before parser.loadXXX()
// @return {GLTF.Texture||Promise<GLTF.Texture>}
onBeforeTexture (textureDef, parser) {
// override textureDef
return textureDef;
},
// If onXXX method is defined
// it is called in parser.loadXXX() to create an XXX instance
// on behalf of entire or part of parser.loadXXX().
// Currently Texture, Material, Geometry and Node suppots onXXX
// @return {THREE.Texture||Promise<THREE.Texture>}
onTexture (textureDef, parser) {
var texture = new THREE.Texture();
// set up texture
return texture;
},
// If onAfterXXX method is defined
// it is called after parser.loadXXX()
// @return {THREE.Texture||Promise<THREE.Texture>}
onAfterTexture (texture, textureDef, parser) {
// override texture properties or
// creating a new texture
return texture;
}
}
loader.registerPlugin(new MyExtension()); Feedback is welcome. |
@takahirox the API looks good to me, but are both Replacing this... dependency = this.loadCamera( index ); ... with this ... dependency = this._onBefore( 'Camera', json.cameras[ index ] ).then( ... ); ... feels a bit strange to me. Mainly just that |
Thanks for the comment. Sorry I'm a bit busy. I'll comment later, hopefully this weekend. |
Sorry for the very late response. @donmccurdy Yeah, that's what I'm struggled with. I thought "modifying definition" somehow can be used but yes I realized any existing extension we support doesn't use. So removing onBefore now and thinking that when needed? But onBeforeGLTF, modifying json before starting to parse, seems necessary. I know some people requested. |
@garyo Any updates on the exporter extensibility mechanism? A coworker of mine is interested in. |
FWIW Babylon.JS implements an extensibility mechanism by allowing extensions to replace a set of existing loading functions with custom versions. Here’s meshoptimizer support implemented with it: Note that if the extension implementation decides it can’t/doesn’t want to perform the custom processing, it returns null and falls back to the next extension or, ultimately, to baseline implementation. It could also decide to call baseline implementation manually. Overall I like the explicit override mechanism a bit better than before/after callbacks due to more explicit and obvious control flow but maybe after/before fit existing code style better? Definitely +1 on extensibility in general. I currently maintain a fork of upstream loader and synchronizing changes gets tiring and error prone. Even setting up a simple framework with just a few extendable entrypoints coild be super valuable. We can iterate from this point on... |
Sorry for delaying so long but I'll try to work on again. I think I have time in Dec. |
Opened #18421 |
Should be solved via #19144 (which was merged in |
Hi,
We are currently implement b3dm (a format that encapsulates glTF) support in our project and have encountered a few problems with the current GLTFLoader implementation.
We need to support addition attribute (
batchid
, used to identify the different objects packed in the geometry), extensions (CESIUM_RTC) and potentially additional uniforms. However, there does not seem to be any way to add these new features without modifying the GLTFLoader file itself.So my question is: how to support the extensible nature of glTF? Do we contribute directly to the source and add the attributes/extensions/uniforms that we need? Or does the loader need to be modified so extensions can be "plugged in" without changing the source?
Related question: when loading glTFs in a scene rendered using a logarithmic depth buffer, the model's shader are not modified to account for it, resulting in depth issues: http://jsfiddle.net/x6ufnz3y/. I implemented a small hack to fix the problem. Should this process be added to the loader natively? Or could Three provide a helper function to patch the materials?
The text was updated successfully, but these errors were encountered: