Skip to content
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

GLTF Serializer rework. #15869

Draft
wants to merge 134 commits into
base: master
Choose a base branch
from

Conversation

SergioRZMasson
Copy link
Contributor

@SergioRZMasson SergioRZMasson commented Nov 21, 2024

This PR changes fundamentally how the Babylon serializer to GLTF works. It changes the way the serializer handles left hand to right hand conversion.

Left hand to Right hand conversion

We no longer set mesh nodes with a negative scaling to handle left vs right hand conversion. We instead change every single vertex information when needed (normal, positions, and tangents) if the mesh was created in a left handed scene. We do the same for animation data and node transforms.

This should help decrease the file size of the exported glTF by removing the need for a root node to convert handedness, essentially reverting #13909.

Cameras and Lights

We now collapse cameras and lights with their transform nodes that are added by the Babylon importer. The goal is for the scene to be able to roundtrip without infinitely expanding.

Preserve vertex data

We now avoid as much as possible changing the type of vertex data. If the Babylon scene is holding vertex data using a particular type we will avoid at all cost converting it to float or to any other type. We might apply transformations to the data (such as left to right hand system conversion) but those modifications will be done in place, avoiding as much as possible creating a new buffer.

We also allow meshes that share the same geometry to point to the same accessors in the generated GLTF, se we no longer do any buffer duplication.

This should help significantly decrease some glTF file sizes on roundtrip, as we no longer make copies of buffers nor needlessly convert smaller buffer types to float buffers.

Forum issues

This PR also fixes a couple of bugs from the forum:

Wrong flipping of animated cameras
Inconsistent transform serialization
Instances exporting as separate meshes and #14056

packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Outdated Show resolved Hide resolved
packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts Outdated Show resolved Hide resolved
packages/dev/core/src/Buffers/buffer.ts Outdated Show resolved Hide resolved
packages/dev/core/src/Buffers/buffer.ts Outdated Show resolved Hide resolved
packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Outdated Show resolved Hide resolved
packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Outdated Show resolved Hide resolved
@sebavan sebavan enabled auto-merge (squash) November 26, 2024 17:17
@SergioRZMasson SergioRZMasson marked this pull request as draft November 26, 2024 17:40
@SergioRZMasson
Copy link
Contributor Author

I'm temporarily converting this back to draft since we are waiting on @ryantrem, the plan is to merge it next week.

@kzhsw
Copy link
Contributor

kzhsw commented Dec 3, 2024

Any considerations on keeping the original texture image instead of re-encoding it?
Forum post: https://forum.babylonjs.com/t/glb-export-changes-original-image/36777

@Jeggery
Copy link

Jeggery commented Dec 3, 2024

I'm temporarily converting this back to draft since we are waiting on @ryantrem, the plan is to merge it next week.

Hi, is there any progress. I see that the merger has been delayed for a long time

@sebavan
Copy link
Member

sebavan commented Dec 3, 2024

@Jeggery, please be mindful of everyone's time. It has only been delayed a week due to vacation...

If you need something faster, this is open source, feel free to make a PR for it.

@Jeggery
Copy link

Jeggery commented Dec 3, 2024

@sebavan OK, sorry I didn't know you guys were on holiday, I was looking forward to it

@sebavan
Copy link
Member

sebavan commented Dec 3, 2024

nw, the team is fully back this week

@SergioRZMasson
Copy link
Contributor Author

Any considerations on keeping the original texture image instead of re-encoding it? Forum post: https://forum.babylonjs.com/t/glb-export-changes-original-image/36777

The PR is already very big, we plan to merge the changes we have so far and follow it up with smaller improvements. Maybe this change can be added in a following PR.

}
break;
}
case AccessorComponentType.BYTE: {
for (let i = 0; i != buffer.length; i++) {
binaryWriter.setByte(buffer[i] * 127);
binaryWriter.writeUInt8(buffer[i] * 127);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be writeInt8?

// glTF lights have variable rotation and constant direction. Therefore,
// compute a quaternion that aligns the Babylon light's direction with glTF's constant one.
if (lightType !== KHRLightsPunctual_LightType.POINT) {
const direction = babylonNode.direction.normalize();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalize normalizes in place, so babylonNode.direction will be normalized: I'm not sure it's expected? You can use normalizeToNew() to create a new normalized vector.

private _checkGrowBuffer(byteLength: number): void {
const newByteLength = this.byteOffset + byteLength;
if (newByteLength > this._data.byteLength) {
const newData = new Uint8Array(newByteLength * 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the size of the DataWriter buffer / the number of DataWriter instances in memory at the same time, we could consume much more memory than necessary with this scheme of doubling the buffer size when it's too small. I assume that getOutputData is called when we've finished filling the buffer? In that case, you could recreate the internal buffer at the final size and not waste memory.

If there is only one instance of DataWriter (for the entire serialized scene), this can be even more problematic, as some glb files can be quite large, and, on average, the data buffer will be 50% larger than necessary.

Perhaps it would be better simply to add a fixed constant value?

map3.set(offset, map4);
}

map4.set(flip, accessorIndex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that an entry already exists for flip in map4? If so, is it a problem / an error? Same question for the other methods below that are implemented in the same way.

packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Outdated Show resolved Hide resolved
}

/**
* Rotation by 180 as glTF has a different convention than Babylon.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that if our conversion from LH to RH consisted simply of negating Z (and not X), we wouldn't need this conversion, because the difference between the glTF coordinate system and Babylon is that the Z axis is reversed (see https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units).

packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts Outdated Show resolved Hide resolved
parentMatrix.multiplyToRef(matrix, matrix);
matrix.decompose(parentScale, parentRotation, parentTranslation);

if (parentTranslation.equalsToFloats(0, 0, 0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't equalsWithEpsilon be used instead? There are other occurrences in this and other files that could benefit from the use of equalsWithEpsilon to take floating-point calculation errors into account.

* @returns True if the parent node was added by the GLTF importer.
*/
export function IsParentAddedByImporter(babylonNode: Node, parentBabylonNode: Node): boolean {
return parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more robust for the loader to add metadata specific to the node it creates, which we could check afterwards, as I think the above condition could also apply to ordinary user-created nodes?

}

export function IsNoopNode(node: Node, useRightHandedSystem: boolean): boolean {
if (!(node instanceof TransformNode)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. It would be more robust if the loader added specific metadata. This would also allow us to handle the case where the user updates this node (which has already happened in PGs), meaning we can no longer check if the matrix is the identity matrix to determine if the node is a “noop” node.

@alexchuber
Copy link
Contributor

Any considerations on keeping the original texture image instead of re-encoding it? Forum post: https://forum.babylonjs.com/t/glb-export-changes-original-image/36777

The PR is already very big, we plan to merge the changes we have so far and follow it up with smaller improvements. Maybe this change can be added in a following PR.

Ditto. I can look into using original textures if they're available, but it'll have to be after this PR. Thanks for bringing this up! :)

@bjsplat
Copy link
Collaborator

bjsplat commented Dec 3, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants