Skip to content

Commit

Permalink
Merge pull request #148 from CesiumGS/decode-3d-texture-coordinates-i…
Browse files Browse the repository at this point in the history
…n-upgrade

Decode 3D texture coordinates in upgrade
  • Loading branch information
lilleyse authored Aug 19, 2024
2 parents 95dd93b + c310f4a commit 49d9b29
Showing 1 changed file with 148 additions and 10 deletions.
158 changes: 148 additions & 10 deletions src/tools/migration/GltfUpgrade.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
import { Accessor, Document, Logger } from "@gltf-transform/core";
import { Document } from "@gltf-transform/core";
import { Accessor } from "@gltf-transform/core";
import { Logger } from "@gltf-transform/core";

import { prune } from "@gltf-transform/functions";

import { GltfUtilities } from "../contentProcessing/GltfUtilities";
import { GltfTransform } from "../contentProcessing/GltfTransform";

import { Loggers } from "../../base";
import { prune } from "@gltf-transform/functions";
const logger = Loggers.get("migration");

/**
* Methods for obtaining a valid glTF-Transform document from
* inputs that may contain legacy data.
*
* This is intended for cases where the data has to be upgraded
* by preprocessing the input (for example, to convert a glTF 1.0
* GLB to glTF 2.0), or by post-processing the document (for example,
* to convert legacy representations of data (like 2D oct-encoded
* normals) into the expected representation).
*
* @internal
*/
export class GltfUpgrade {
static async obtainDocument(glb: Buffer) {
/**
* Obtain a glTF-Transform document from the given GLB buffer.
*
* This is intended for cases where the input may contain various
* forms of "legacy" data, and where it may be necessary to
* preprocess the input or postprocess the document, in order
* to obtain a valid glTF 2.0 document.
*
* The preprocessing steps that may be applied to the buffer are:
*
* - glTF 1.0 data will be upgraded to glTF 2.0 with gltf-pipeline
* - The CESIUM_RTC extension from glTF 1.0 will be converted into
* a translation of a (newly inserted) root node of the document
*
* The postprocessing steps that may be applied to the document are:
* - Decode oct-encoded normals into the standard representation
* (for details, see `octDecode2DNormals`)
* - Decode compressed 3D texture coordinates into 2D
* (for details, see `decode3DTexCoords`)
*
* @param glb The GLB buffer
* @returns A promise to the glTF-Transform `Document`
*/
static async obtainDocument(glb: Buffer): Promise<Document> {
// Upgrade the GLB buffer to glTF 2.0 if necessary,
// and convert the CESIUM_RTC extension into a root
// node translation if necessary
Expand All @@ -39,9 +59,11 @@ export class GltfUpgrade {
asset.generator = "glTF-Transform";

// When the input contained glTF 1.0 data, decode any
// oct-encoded (2D) normals into 3D
// oct-encoded (2D) normals into 3D, and compressed
// (3D) texture coordinates into 2D
if (gltfVersion < 2.0) {
await document.transform(GltfUpgrade.octDecode2DNormals);
await document.transform(GltfUpgrade.decode3DTexCoords);
document.setLogger(new Logger(Logger.Verbosity.WARN));
await document.transform(prune());
}
Expand Down Expand Up @@ -166,4 +188,120 @@ export class GltfUpgrade {
v[2] *= invLen;
return v;
}

/**
* Check each mesh primitive in the given document, to see if it
* contains a VEC3/BYTE or VEC3/SHORT accessor for the TEXCOORD_X.
* If it does, then this accessor will be replaced by one that
* contains the decoded 2D texture coordinates.
*
* (Note that the old accessors might become unused by that.
* The document should afterwards be cleaned up with
* glTF-Transform 'prune()')
*
* @param document - The glTF-Transform document
*/
private static decode3DTexCoords(document: Document) {
const root = document.getRoot();
const meshes = root.listMeshes();
let decodedAccessorsCounter = 0;
for (const mesh of meshes) {
const primitives = mesh.listPrimitives();
for (const primitive of primitives) {
const semantics = primitive.listSemantics();
for (const semantic of semantics) {
if (semantic.startsWith("TEXCOORD_")) {
const texcoordAccessor = primitive.getAttribute(semantic);
if (texcoordAccessor) {
const type = texcoordAccessor.getType();
const componentType = texcoordAccessor.getComponentType();
const GL_BYTE = 5120;
const GL_SHORT = 5122;
if (type === "VEC3" && componentType === GL_BYTE) {
logger.debug("Decoding (VEC3/BYTE) texture coordinates...");
const decodedTexCoordsAccessor =
GltfUpgrade.decodeTexCoordAccessor(
document,
texcoordAccessor,
127.0
);
primitive.setAttribute(semantic, decodedTexCoordsAccessor);
decodedAccessorsCounter++;
} else if (type === "VEC3" && componentType === GL_SHORT) {
logger.debug("Decoding (VEC3/SHORT) texture coordinates...");
const decodedTexCoordsAccessor =
GltfUpgrade.decodeTexCoordAccessor(
document,
texcoordAccessor,
32767.0
);
primitive.setAttribute(semantic, decodedTexCoordsAccessor);
decodedAccessorsCounter++;
}
}
}
}
}
}
if (decodedAccessorsCounter > 0) {
logger.info(
`Decoded ${decodedAccessorsCounter} texture coordinate accessors to 2D`
);
}
}

/**
* Decode the encoded (3D) texture coordinates from the given accessor, and
* return the result as a new accessor.
*
* @param document - The glTF-Transform document
* @param encodedAccessor - The (VEC3) accessor containing the
* encoded 3D texture coordinate data
* @param range - The decoding range: 127 for BYTE, 32767 for SHORT
* @returns The decoded (VEC2/FLOAT) accessor.
*/
private static decodeTexCoordAccessor(
document: Document,
encodedAccessor: Accessor,
range: number
) {
const decodedData: number[] = [];

const count = encodedAccessor.getCount();
for (let i = 0; i < count; i++) {
const encoded = [0, 0, 0];
encodedAccessor.getElement(i, encoded);
const decoded = GltfUpgrade.decodeTexCoord(encoded, range);
decodedData.push(...decoded);
}

const decodedAccessor = document.createAccessor();
decodedAccessor.setType("VEC2");
decodedAccessor.setArray(new Float32Array(decodedData));
return decodedAccessor;
}

/**
* Decode the given 3D texture coordinates from the given value
* range into 2D texture coordinates
*
* @param encoded - The encoded coordinates as a 3-element array
* @param range - The decoding range: 127 for BYTE, 32767 for SHORT
* @returns The decoded texture coordinates as a 2-element array
*/
private static decodeTexCoord(encoded: number[], range: number): number[] {
// Note: The deconding of 3D texture coordinates into 2D that
// took place in some shaders in glTF 1.0 was implemented
// like this:
// const float uvMultiplier = 0.0000305185; // 1/32767
// v_texcoord0 = a_texcoord0.xy * uvMultiplier * (a_texcoord0.z+32767.0);
// This is an attempt to emulate this, involving some guesses:

const uvMultiplier = 1.0 / range;
const zFactor = encoded[2] + range;
const x = encoded[0] * uvMultiplier * zFactor;
const y = encoded[1] * uvMultiplier * zFactor;
const result = [x, y];
return result;
}
}

0 comments on commit 49d9b29

Please sign in to comment.