-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
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
Updated LegacyGLTFLoader.js shader parsing #13339
Updated LegacyGLTFLoader.js shader parsing #13339
Conversation
This is an updated version of #13332 that aims to modify the existing LegacyGLTFLoader code as little as possible. |
@donmccurdy, I like your GLTF viewer - I see it loads BoxTextured.gltf just fine. You must be doing something different than the three.js editor does, because I did a quick check on my (reasonably recent nvidia laptop) hardware, and it seems for both GLTF versions 1.0 and 2.0, though BoxTextured.gltf and Duck.gltf both load, they each have an attractive but incorrect all-black material. The code included in this patch fixes that issue for the editor's loading of BoxTextured.gltf and Duck.gltf versions 1.0. Do you want me to file a bug report for the 2.0 versions? |
(Actually, this code doesn't fix the editor directly, but when I modded the editor to use LegacyGLTFLoader, the models loaded with their textures intact.) |
I think what this patch fixes is when there are user-defined attributes in a shader (anything that's not position, normal, color, texture coordinate, joint or weight), the loader is forgetting to add them. |
@@ -714,11 +714,15 @@ THREE.LegacyGLTFLoader = ( function () { | |||
|
|||
switch ( semantic ) { | |||
|
|||
case "POSITION_0": | |||
case "POSITION0": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POSITION0
syntax is not permitted by the glTF spec:
Valid attribute semantic property names include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, and WEIGHT. Attribute semantic property names can be of the form [semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc.
So I don't think that case needs to be added here, similarly below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll submit yet another version that leaves this section alone entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, you can also just make new commits on your branch and git push
, they'll appear in this PR automatically. If it's easier to start a new branch and new PR that's fine too, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's good to know.
@@ -1616,26 +1662,74 @@ THREE.LegacyGLTFLoader = ( function () { | |||
geometry.addAttribute( 'uv', bufferAttribute ); | |||
break; | |||
|
|||
case 'TEXCOORD_1': | |||
geometry.addAttribute( 'uv2', bufferAttribute ); | |||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep this TEXCOORD_1
case here (see further comments below).
case 'TEXCOORD_1': | ||
|
||
shaderText = shaderText.replace( regEx, 'uv2' ); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments below, I would keep this TEXCOORD_1
case.
} | ||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this particular default:
block can be removed, actually — it is not necessary to rename attributes that three.js does not make direct use of. More explanation below.
geometry.addAttribute( attributeName, bufferAttribute ); | ||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we decide not to rename attributes three.js does not use itself, I believe this default:
block can be written more simply:
geometry.addAttribute( attributeId, bufferAttribute );
I imagine you're renaming attributes for consistency with the existing code, but it isn't strictly necessary for attributes whose names don't have inherent meaning in three.js, and I'd be more comfortable merging this PR if we can avoid adding two new nested switch
statements to accomplish it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
attributeId represents constants like 'POSITION' and 'TEXCOORD_0', but the first argument to geometry.addAttribute() needs to be the name of the corresponding variable in the shader text. That's why I need to do a couple handstands to find the name of the variable in the GLTF file through the material's technique. But I can simplify the code to do only that, which will be a maybe five-line change total.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, thanks!
Thanks for the updated PR! Added some comments inline. |
I think importing models that require multiple files, like external textures, is a bit experimental in the threejs editor maybe? Using a .ZIP might work... I have some workarounds for my own viewer there. Otherwise see #10800 and #6680, and if either comment there or open a new issue 👍 |
Any editor's console error message for 2.0 models? I suppose the editor also fails to load external |
You're totally right, I just needed to add a light. So, never-mind! But yes, it'll be great if by default the editor gets the support files from the same location as the gltf. |
@donmccurdy looks good? |
editor/js/Loader.js
Outdated
|
||
try { | ||
|
||
extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( contents ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extensions
is a copy/paste artifact, I imagine? In fact this line will throw a specific error if the asset has a version <=2
. If it parses successfully it is glTF 2.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I see now GLTFBinaryExtension is defined in both LegacyGLTFLoader and GLTFLoader, within closures... that seems like an issue, will this code run correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will take that section out entirely.
I need to double-check the spec before approving, but changes to LegacyGLTFLoader look generally fine. There are issues with the way LegacyGLTFLoader is added to the editor, I've left some comments there. But more generally, first, @mrdoob what do you think about legacy glTF 1.0 support in the editor? |
I think it's a good thing; it'll help people rescue files. I still have to add support to .gltf + .bin, but that'll come later. |
I should ask, too, are you just using the editor as a quick way of testing your glTF 1.0 files? If that's the case, I might rather create a special gltf-viewer-legacy.donmccurdy.com page, or something like that. For example I wouldn't want people to test a model in the three.js editor, see that it works, and assume they can upload it to Facebook... EDIT: Just saw comment above. Sounds good, pending suggested changes on the PR. Although people won't be able to convert files using GLSL to glTF 2.0 this way... |
default: | ||
var material = json.materials[ primitive.material ]; | ||
var attributeNames = json.techniques[ material.technique ].attributes; | ||
var attributeName = Object.keys( attributeNames )[ attributeIndex ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This depends on primitive.attributes
having the same order as technique.attributes
, which is not guaranteed. Would this work?
var attributeMap = invertObject( json.techniques[ material.technique ].attributes );
var attributeName = attributeMap[ attributeId ];
function invertObject ( input ){
var output = {};
for( var key in input ){
output[ input[ key ] ] = key;
}
return output;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, I see I've missed the indirection on the parameters
array... I guess it is not quite this simple. I haven't got time right now but may need to take another look in a day or two. 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right. I haven't looked at the spec to see if that's always the case, just saw it held in all the glTF files I looked at. If that's really not guaranteed, then yeah, the complexity increases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will attempt to update that to look through the attributes to find a match with the current attributeId.
|
||
var magic = THREE.LoaderUtils.decodeText( new Uint8Array( contents, 0, 4 ) ); | ||
|
||
if ( magic === 'glTF' ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably want magic !== 'glTF'
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be resolved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does yeah; the first four bytes are glTF
for both 1.0 and 2.0. The next four bytes are a version, which must be 1
in glTF 1.0. Will add a comment below.
@@ -1634,6 +1634,20 @@ THREE.LegacyGLTFLoader = ( function () { | |||
geometry.addAttribute( 'skinIndex', bufferAttribute ); | |||
break; | |||
|
|||
default: | |||
var material = json.materials[ primitive.material ]; | |||
var parameters = json.techniques[ material.technique ].parameters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primitive may not have a material, the material may not have a technique, and the technique may not have parameters. In any of those cases we should break
rather than throwing an error, I think.
Couple safety checks to add, otherwise this is looking close. Is there a model you can share to verify this works, by any chance? |
Hm. It turns out that glTF 1.0 does not allow custom attributes, and so this is not technically valid... I'm leaning toward saying (1) adding glTF 1.0 support to the Editor is fine, but (2) adding support for custom semantics should be left to your own separate fork of the loader. If your goal is to have a quick way of testing glTF 1.0 files, I'd suggest forking this viewer and swapping out the loader. glTF 2.0 does support custom attributes, so this should not be an issue there. |
Agreed. Do you mind leaving the custom semantics part for a different PR? |
Custom semantics, no. But custom attributes should definitely be allowed. So for instance, a custom attribute with name myColor and semantic "COLOR_2" would be valid, assuming I'm reading the spec correctly. I can update the code to enforce that [semantic]_[index] pattern. |
|
||
for( var attributeName in parameters ) { | ||
|
||
if( parameters[attributeName]['semantic'] === attributeId ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could you add whitespace to match other code —
if ( parameters [ attributeName ][ 'semantic' ] === attributeId ) {
Ok, that sounds fine then. No need to validate names against the And, technically, if you wanted to use custom semantics they should still "just work" here. To be technically valid you should add a vendor extension to your file, but since it's presumably for your company's internal use that's your call. :) |
Remaining items:
|
Sorry, tight deadlines dragged me away from this. I'll make those changes ASAP. |
Seems three methods are required check the version, depending upon whether the input is a string, a text file, or a binary. If it's a glb, the second four bytes is the version number. If it's a string, it needs parsing as JSON, and then json.asset.version[0] contains the version. If it's a text file, the input is an object, which needs decoding to text and then JSON parsing. |
I'm not sure what you mean by a text file (is something creating a Blob or File instance?) but the other two cases are right. You can always use And then if it's GLB and the first four bytes are not |
"text file" meaning, for example, "Box.gltf". (as opposed to "Box.glb") |
editor/js/Loader.js
Outdated
var u32bytes = new Uint8Array( contents, 4, 4 ); | ||
var versionNumber = new Uint32Array(u32bytes)[0]; | ||
|
||
return ( versionNumber < 2 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can simplify these three lines:
var version = new DataView( contents ).getUint32( 4, true );
return version < 2;
editor/js/Loader.js~RF6842b54.TMP
Outdated
@@ -0,0 +1,622 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think this file snuck into Git accidentally 🤔
|
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more nit (sorry!), rather than nesting these could you use break
? Example:
if ( ! primitive.material ) break;
var material = ...;
if ( ! material.technique ) break;
var parameters = json.techniques[ material.technique ].parameters || {};
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem! And yeah, TMP file, whoops. I'll get rid of that when I make the changes above.
Demo: https://cdn.rawgit.com/1d2d3d/three.js/486f7b91bc2ef02ef29dd6a2683446cef6b07ba7/editor/index.html The changes all look good to me, but I can't open any glTF 1.0
Shouldn't be caused by this PR, but might be something we need to fix before this goes out in the editor. Maybe merge this after r91 and then fix the other issue separately before r92? |
I'm reading about the issue - looks like this might apply: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem |
In the newer GLTFLoader we don't use var blob = new Blob( [ bufferView ], { type: source.mimeType } );
sourceURI = URL.createObjectURL( blob ); ... which should be much faster, and avoid this issue. But anyway it's not related to this PR. Might be good to fix before this is live in the Editor, but there are not many sources of |
Thanks! |
Nice work @1d2d3d ! |
Great! Thanks to you both! |
This is a recap of #13332 - At my workplace, we need to deliver some assets with shaders in GLTF 1.0 format. Since they need attributes that weren't handled by default in the GLTF 1.0 loader, I modified the LegacyGLTFLoader.js file to handle the extra attributes (on this git branch), and tested it by slightly modifying the three.js editor (on another git branch). I realize they're working on adding shader support to GLTF 2, but in the meantime, if we want to use our own shaders, GLTF 1 is all we've got.
The legacy loader now seems to do a better job of loading the sample GLTF 1.0 files (e.g., BoxTextured.gltf and Duck.gltf) on the Khronos web site. This loader fixes two issues; one, handling of indexed vertex attributes with types such as COLOR_1 and COLOR_2, and two, passing user-defined vertex attributes through to three.js.
The argument order for the parse() function's second and third arguments was reversed between the editor's and the legacy loader's code, so in order to test with the editor, I patched that. A separate branch with the editor changes that allow it to open both GLTF 1 and 2 files is in #13333. Please note that branch assumes the parse() argument-swap implemented in this branch's LegacyGLTFLoader.js.