/** * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #include #include #include #include #include #if defined( __unix__ ) || defined( __APPLE__ ) #include #define _stricmp strcasecmp #endif #include #include "FBX2glTF.h" #include "utils/String_Utils.h" #include "utils/File_Utils.h" #include "Fbx2Raw.h" #include "RawModel.h" #include "Raw2Gltf.h" bool verboseOutput = false; int main(int argc, char *argv[]) { cxxopts::Options options( "FBX2glTF", "FBX2glTF 2.0: Generate a glTF 2.0 representation of an FBX model."); std::string inputPath; std::string outputPath; std::vector> texturesTransforms; GltfOptions gltfOptions{ -1, // keepAttribs false, // outputBinary false, // embedResources false, // useDraco false, // useKHRMatCom false, // usePBRMetRough false // usePBRSpecGloss }; options.positional_help("[]"); options.add_options() ( "i,input", "The FBX model to convert.", cxxopts::value(inputPath)) ( "o,output", "Where to generate the output, without suffix.", cxxopts::value(outputPath)) ( "e,embed", "Inline buffers as data:// URIs within generated non-binary glTF.", cxxopts::value(gltfOptions.embedResources)) ( "b,binary", "Output a single binary format .glb file.", cxxopts::value(gltfOptions.outputBinary)) ( "d,draco", "Apply Draco mesh compression to geometries.", cxxopts::value(gltfOptions.useDraco)) ("flip-u", "Flip all U texture coordinates.") ("flip-v", "Flip all V texture coordinates.") ( "khr-materials-common", "(WIP) Use KHR_materials_common extensions to specify Unlit/Lambert/Blinn/Phong shaders.", cxxopts::value(gltfOptions.useKHRMatCom)) ( "pbr-metallic-roughness", "(WIP) Try to glean glTF 2.0 native PBR attributes from the FBX.", cxxopts::value(gltfOptions.usePBRMetRough)) ( "pbr-specular-glossiness", "(WIP) Experimentally fill in the KHR_materials_pbrSpecularGlossiness extension.", cxxopts::value(gltfOptions.usePBRSpecGloss)) ( "k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.", cxxopts::value()) ("v,verbose", "Enable verbose output.") ("h,help", "Show this help.") ("V,version", "Display the current program version."); try { options.parse_positional("input"); options.parse(argc, argv); } catch (const cxxopts::OptionException &e) { fmt::printf(options.help()); return 1; } if (options.count("version")) { fmt::printf( R"( FBX2glTF version 2.0 Copyright (c) 2016-2017 Oculus VR, LLC. )"); return 0; } if (options.count("help")) { fmt::printf(options.help()); return 0; } if (!options.count("input")) { fmt::printf("You must supply a FBX file to convert.\n"); fmt::printf(options.help()); return 1; } if (options.count("verbose")) { verboseOutput = true; } if (!gltfOptions.useKHRMatCom && !gltfOptions.usePBRSpecGloss && !gltfOptions.usePBRMetRough) { if (verboseOutput) { fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n"); } gltfOptions.usePBRMetRough = true; } if (options.count("flip-u") > 0) { texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); }); } if (options.count("flip-v") > 0) { texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); }); } if (options.count("keepAttribute")) { gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; for (const auto &attribute : options["keepAttribute"].as>()) { if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; } else if (attribute == "normal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; } else if (attribute == "tangent") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; } else if (attribute == "binormal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL; } else if (attribute == "color") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR; } else if (attribute == "uv0") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0; } else if (attribute == "uv1") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1; } else if (attribute == "auto") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO; } else { fmt::printf("Unknown --keepAttribute: %s\n", attribute); fmt::printf("Valid choices are: position, normal, tangent, binormial, color, uv0, uv1, auto,\n"); return 1; } } } if (gltfOptions.embedResources && gltfOptions.outputBinary) { fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n"); } if (options.count("output") == 0) { // if -o is not given, default to the basename of the .fbx outputPath = "./" + Gltf::StringUtils::GetFileBaseString(inputPath); } std::string outputFolder; // the output folder in .gltf mode, not used for .glb std::string modelPath; // the path of the actual .glb or .gltf file if (gltfOptions.outputBinary) { // in binary mode, we write precisely where we're asked modelPath = outputPath + ".glb"; } else { // in gltf mode, we create a folder and write into that outputFolder = outputPath + "_out/"; modelPath = outputFolder + Gltf::StringUtils::GetFileNameString(outputPath) + ".gltf"; } if (!FileUtils::CreatePath(modelPath.c_str())) { fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str()); return 1; } ModelData *data_render_model = nullptr; RawModel raw; if (verboseOutput) { fmt::printf("Loading FBX File: %s\n", inputPath); } if (!LoadFBXFile(raw, inputPath.c_str(), "tga;bmp;png;jpg")) { fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath); return 1; } if (!texturesTransforms.empty()) { raw.TransformTextures(texturesTransforms); } raw.Condense(); std::ofstream outStream; // note: auto-flushes in destructor const auto streamStart = outStream.tellp(); outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary); if (outStream.fail()) { fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str()); return 1; } data_render_model = Raw2Gltf(outStream, raw, gltfOptions); if (gltfOptions.outputBinary) { fmt::printf( "Wrote %lu bytes of binary glTF to %s.\n", (unsigned long) (outStream.tellp() - streamStart), modelPath); delete data_render_model; return 0; } fmt::printf( "Wrote %lu bytes of glTF to %s.\n", (unsigned long) (outStream.tellp() - streamStart), modelPath); if (gltfOptions.embedResources) { // we're done: everything was inlined into the glTF JSON delete data_render_model; return 0; } assert(!outputFolder.empty()); const std::string binaryPath = outputFolder + extBufferFilename; FILE *fp = fopen(binaryPath.c_str(), "wb"); if (fp == nullptr) { fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath); return 1; } const unsigned char *binaryData = &(*data_render_model->binary)[0]; unsigned long binarySize = data_render_model->binary->size(); if (fwrite(binaryData, binarySize, 1, fp) != 1) { fmt::fprintf(stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath); fclose(fp); return 1; } fclose(fp); fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath); delete data_render_model; return 0; }