3D-tiles get very dark textures

I changed the value in notepad.

Binary files (like B3DM) cannot be modified with a text editor. They can be modified with a hex editor, as long as the size of the file does not change (and in this particular example, the modification would only be changing a 1 to a 0, so that would be possible very easily).

But for the task of modifying this value in thousands of files, one will need some sort of batch processing.


Note: The following description is intended as a workaround. It should work in this particular case, but it might not work for other tilesets that show the same problem. It is not intended a long-term solution.

  • Clone the current state of the 3D Tiles Tools (i.e. the current main state, which is this state at the time of writing this)
  • run npm install
  • Add the code that is shown below as a file called ModifyMaterials.ts in the project root directory
  • Run npx ts-node ModifyMaterials.ts

The code that does the modification is shown here (adjust the tilesetSourceName and tilesetTargetName according to your input/output directories):

//
// NOTE: None of the functionality that is shown here is part of the
// public API of the 3D Tiles tools. The functions that are shown here
// use an INTERNAL API that may change at any point in time.
//
import { TileContentProcessing } from "./src/tilesetProcessing/TileContentProcessing";
import { GltfUtilities } from "./src/contentProcessing/GtlfUtilities";
import { GltfTransform } from "./src/contentProcessing/GltfTransform";
import { TileFormats } from "./src/tileFormats/TileFormats";
import { ContentDataTypes } from "./src/contentTypes/ContentDataTypes";

// Read a glTF-Transform document from the given input GLB buffer,
// set the 'metallicFactor' of all materials to 0, and return
// a new buffer that was created from the modified document
async function modifyMaterialsInGlb(inputGlb: Buffer) {
  const io = await GltfTransform.getIO();
  const document = await io.readBinary(inputGlb);
  const root = document.getRoot();
  const materials = root.listMaterials();
  for (const material of materials) {
    material.setMetallicFactor(0);
  }
  const outputGlb = await io.writeBinary(document);
  return outputGlb;
}

// Read the tile data from the given input B3DM buffer,
// extract the payload (GLB data), modify it by calling
// `modifyMaterialsInGlb`, and create a new B3DM buffer
// with the modified GLB data
async function modifyMaterialsInB3dm(inputB3dm: Buffer) {
  const inputTileData = TileFormats.readTileData(inputB3dm);
  const inputGlb = inputTileData.payload;
  const glb = await GltfUtilities.replaceCesiumRtcExtension(inputGlb);
  const outputGlb = await modifyMaterialsInGlb(glb);
  const outputTileData = TileFormats.createB3dmTileDataFromGlb(
    Buffer.from(outputGlb),
    inputTileData.featureTable.json,
    inputTileData.featureTable.binary,
    inputTileData.batchTable.json,
    inputTileData.batchTable.binary
  );
  const outputB3dm = TileFormats.createTileDataBuffer(outputTileData);
  return outputB3dm;
}

async function runConversion() {
  const tilesetSourceName = "./input/tileset.json";
  const tilesetTargetName = "./output/tileset.json";
  const overwrite = true;

  // Create a `TileContentProcessor` that calls modifyMaterialsInB3dm
  // for all B3DM files
  const tileContentProcessor = async (
    content: Buffer,
    type: string | undefined
  ) => {
    if (type !== ContentDataTypes.CONTENT_TYPE_B3DM) {
      return content;
    }
    // A pragmatic try-catch block for the actual modification.
    // In a real application, different error handling could
    // be used.
    console.log("Modifying materials...");
    try {
      const modifiedB3dm = await modifyMaterialsInB3dm(content);
      console.log("Modifying materials... DONE");
      return modifiedB3dm;
    } catch (e) {
      console.log(`ERROR: ${e}`);
      return content;
    }
  };
  // Process the tileset source, and write it to the tileset target,
  // applying the `TileContentProcessor` to all tile contents
  await TileContentProcessing.process(
    tilesetSourceName,
    tilesetTargetName,
    overwrite,
    tileContentProcessor
  );
}

runConversion();

This should fix the wrong metallicFactor that seems to be written by FME in this case. If similar problems with tilesets come up more frequently in the future, then this might become part of an official functionality, but for now, it is only a temporary workaround.

2 Likes