Files
UnrealEngine/Engine/Source/ThirdParty/Windows/glTF-Toolkit/WindowsMRAssetConverter/WindowsMRAssetConverter.cpp
2025-05-18 13:04:45 +08:00

294 lines
10 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
#include "stdafx.h"
#include <GLTFSDK/GLTF.h>
#include <GLTFSDK/Deserialize.h>
#include <GLTFSDK/IStreamWriter.h>
#include <GLTFSDK/GLBResourceReader.h>
#include <GLTFSDK/ExtensionsKHR.h>
#include <GLTFTexturePackingUtils.h>
#include <GLTFTextureCompressionUtils.h>
#include <GLTFSpecularGlossinessUtils.h>
#include <GLTFLODUtils.h>
#include <SerializeBinary.h>
#include <GLBtoGLTF.h>
#include <GLTFMeshCompressionUtils.h>
#include "CommandLine.h"
#include "FileSystem.h"
#include "GLTFTextureUtils.h"
using namespace Microsoft::glTF;
using namespace Microsoft::glTF::Toolkit;
class GLTFStreamReader : public IStreamReader
{
public:
GLTFStreamReader(std::wstring uriBase) : m_uriBase(uriBase) {}
virtual ~GLTFStreamReader() override {}
virtual std::shared_ptr<std::istream> GetInputStream(const std::string& filename) const override
{
std::wstring filenameW = std::wstring(filename.begin(), filename.end());
wchar_t uriAbsoluteRaw[MAX_PATH];
// Note: PathCchCombine will return the last argument if it's an absolute path
if (FAILED(::PathCchCombine(uriAbsoluteRaw, ARRAYSIZE(uriAbsoluteRaw), m_uriBase.c_str(), filenameW.c_str())))
{
throw std::invalid_argument("Could not get the base path for the GLTF resources. Try specifying the full path.");
}
return std::make_shared<std::ifstream>(uriAbsoluteRaw, std::ios::binary);
}
private:
const std::wstring m_uriBase;
};
class GLBStreamWriter : public Microsoft::glTF::IStreamWriter
{
public:
GLBStreamWriter(const std::wstring& filename) :
m_stream(std::make_shared<std::ofstream>(filename, std::ios_base::binary | std::ios_base::out))
{ }
std::shared_ptr<std::ostream> GetOutputStream(const std::string&) const override
{
return m_stream;
}
private:
std::shared_ptr<std::ofstream> m_stream;
};
Document ProcessTextures(
size_t maxTextureSize,
TexturePacking packing,
bool retainOriginalImages,
const std::wstring& tempDirectory,
const Document& document,
const std::shared_ptr<GLTFStreamReader>& streamReader)
{
Document resultDocument(document);
std::string tempDirectoryA(tempDirectory.begin(), tempDirectory.end());
std::wcout << L"Specular Glossiness conversion..." << std::endl;
// 0. Specular Glossiness conversion
resultDocument = GLTFSpecularGlossinessUtils::ConvertMaterials(streamReader, resultDocument, tempDirectoryA);
std::wcout << L"Removing redundant textures and images..." << std::endl;
// 1. Remove redundant textures and images
resultDocument = GLTFTextureUtils::RemoveRedundantTexturesAndImages(resultDocument);
std::wcout << L"Packing textures..." << std::endl;
// 2. Texture Packing
resultDocument = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, resultDocument, packing, tempDirectoryA);
std::wcout << L"Compressing textures - this can take a few minutes..." << std::endl;
// 3. Texture Compression
resultDocument = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, resultDocument, tempDirectoryA, maxTextureSize, retainOriginalImages);
return resultDocument;
}
Document LoadAndConvertDocumentForWindowsMR(
std::wstring& inputFilePath,
AssetType inputAssetType,
const std::wstring& tempDirectory,
bool meshCompression)
{
// Load the document
std::experimental::filesystem::path inputFilePathFS(inputFilePath);
std::wstring inputFileName = inputFilePathFS.filename();
std::wcout << L"Loading input document: " << inputFileName << L"..." << std::endl;
std::string tempDirectoryA(tempDirectory.begin(), tempDirectory.end());
if (inputAssetType == AssetType::GLB)
{
// Convert the GLB to GLTF in the temp directory
std::string inputFilePathA(inputFilePath.begin(), inputFilePath.end());
// inputGltfName is the path to the converted GLTF without extension
std::wstring inputGltfName = inputFilePathFS.stem();
std::string inputGltfNameA = std::string(inputGltfName.begin(), inputGltfName.end());
GLBToGLTF::UnpackGLB(inputFilePathA, tempDirectoryA, inputGltfNameA);
inputFilePath = tempDirectory + inputGltfName + EXTENSION_GLTF;
}
auto stream = std::make_shared<std::ifstream>(inputFilePath, std::ios::in);
Document document = Deserialize(*stream, KHR::GetKHRExtensionDeserializer());
// Get the base path from where to read all the assets
auto streamReader = std::make_shared<GLTFStreamReader>(FileSystem::GetBasePath(inputFilePath));
if (meshCompression)
{
std::wcout << L"Compressing meshes - this can take a few minutes..." << std::endl;
document = GLTFMeshCompressionUtils::CompressMeshes(streamReader, document, {}, tempDirectoryA);
}
return document;
}
int wmain(int argc, wchar_t *argv[])
{
if (argc < 2)
{
CommandLine::PrintHelp();
return 0;
}
// Initialize COM
CoInitialize(NULL);
try
{
// Arguments
std::wstring inputFilePath;
AssetType inputAssetType;
std::wstring outFilePath;
std::wstring tempDirectory;
std::vector<std::wstring> lodFilePaths;
std::vector<double> screenCoveragePercentages;
size_t maxTextureSize;
bool shareMaterials;
CommandLine::Version minVersion;
CommandLine::Platform targetPlatforms;
bool replaceTextures;
bool meshCompression = false;
CommandLine::ParseCommandLineArguments(
argc, argv, inputFilePath, inputAssetType, outFilePath, tempDirectory, lodFilePaths, screenCoveragePercentages,
maxTextureSize, shareMaterials, minVersion, targetPlatforms, replaceTextures, meshCompression);
TexturePacking packing = TexturePacking::None;
std::wstring compatibleVersionsText;
if ((targetPlatforms & CommandLine::Platform::Holographic) > 0)
{
compatibleVersionsText += L"HoloLens";
// Holographic mode: NRM
packing = (TexturePacking)(packing | TexturePacking::NormalRoughnessMetallic);
}
if ((targetPlatforms & CommandLine::Platform::Desktop) > 0)
{
if (!compatibleVersionsText.empty())
{
compatibleVersionsText += L" and ";
}
// Desktop 1803+ mode: ORM
packing = (TexturePacking)(packing | TexturePacking::OcclusionRoughnessMetallic);
if (minVersion == CommandLine::Version::Version1709)
{
// Desktop 1709 mode: RMO
packing = (TexturePacking)(packing | TexturePacking::RoughnessMetallicOcclusion);
compatibleVersionsText += L"Desktop (version 1709+)";
}
else if (minVersion == CommandLine::Version::Version1803)
{
compatibleVersionsText += L"Desktop (version 1803+)";
}
else
{
compatibleVersionsText += L"Desktop (version 1809+)";
}
}
std::wcout << L"\nThis will generate an asset compatible with " << compatibleVersionsText << L"\n" << std::endl;
// Load document, and perform steps:
// 1. Mesh Compression
auto document = LoadAndConvertDocumentForWindowsMR(inputFilePath, inputAssetType, tempDirectory, meshCompression);
// 2. LOD Merging
if (!lodFilePaths.empty())
{
std::wcout << L"Merging LODs..." << std::endl;
std::vector<Document> lodDocuments;
std::vector<std::wstring> lodDocumentRelativePaths;
lodDocuments.push_back(document);
for (size_t i = 0; i < lodFilePaths.size(); i++)
{
// Apply the same optimizations for each LOD
auto lod = lodFilePaths[i];
auto subFolder = FileSystem::CreateSubFolder(tempDirectory, L"lod" + std::to_wstring(i + 1));
lodDocuments.push_back(LoadAndConvertDocumentForWindowsMR(lod, AssetTypeUtils::AssetTypeFromFilePath(lod), subFolder, meshCompression));
lodDocumentRelativePaths.push_back(FileSystem::GetRelativePathWithTrailingSeparator(FileSystem::GetBasePath(inputFilePath), FileSystem::GetBasePath(lod)));
}
document = GLTFLODUtils::MergeDocumentsAsLODs(lodDocuments, screenCoveragePercentages, lodDocumentRelativePaths, shareMaterials);
}
// 3. Texture Packing
// 4. Texture Compression
auto streamReader = std::make_shared<GLTFStreamReader>(FileSystem::GetBasePath(inputFilePath));
document = ProcessTextures(maxTextureSize, packing, !replaceTextures, tempDirectory, document, streamReader);
// 5. Make sure there's a default scene
if (!document.HasDefaultScene())
{
document.defaultSceneId = document.scenes.Elements()[0].id;
}
// 6. GLB Export
std::wcout << L"Exporting as GLB..." << std::endl;
// The Windows MR Fall Creators update has restrictions on the supported
// component types of accessors.
AccessorConversionStrategy accessorConversion = [](const Accessor& accessor)
{
if (accessor.type == AccessorType::TYPE_SCALAR)
{
switch (accessor.componentType)
{
case ComponentType::COMPONENT_BYTE:
case ComponentType::COMPONENT_UNSIGNED_BYTE:
case ComponentType::COMPONENT_SHORT:
return ComponentType::COMPONENT_UNSIGNED_SHORT;
default:
return accessor.componentType;
}
}
else if (accessor.type == AccessorType::TYPE_VEC2 || accessor.type == AccessorType::TYPE_VEC3)
{
return ComponentType::COMPONENT_FLOAT;
}
return accessor.componentType;
};
SerializeBinary(document, streamReader, std::make_shared<GLBStreamWriter>(outFilePath), accessorConversion);
std::wcout << L"Done!" << std::endl;
std::wcout << L"Output file: " << outFilePath << std::endl;
}
catch (std::exception ex)
{
std::cerr << ex.what() << std::endl;
return 1;
}
return 0;
}