diff --git a/README.md b/README.md index f037c99..047be4b 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Additionally the repository includes a command line tool that uses these steps i ## Features The current release includes code for: -- Packing PBR material textures using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_packing_occlusionRoughnessMetallic) extension. -- Compressing textures as BC3, BC5 and BC7 and generate mip maps using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_texture_dds](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_texture_dds) extension. -- Merging multiple glTF assets into a asset with multiple levels of detail using the [MSFT_lod](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod) extension. +- Packing PBR material textures using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) and [MSFT_packing_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extensions. +- Compressing textures as BC3, BC5 and BC7 and generate mip maps using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_texture_dds](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds) extension. +- Merging multiple glTF assets into a asset with multiple levels of detail using the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension. - A command line tool that combines these components to create optimized glTF assets for the Windows Mixed Reality Home - A UWP compatible Windows Runtime component to perform conversions between GLTF and GLB, as well as optimize assets for Windows Mixed Reality at runtime diff --git a/WindowsMRAssetConverter/CommandLine.cpp b/WindowsMRAssetConverter/CommandLine.cpp index 65f2071..c3dc363 100644 --- a/WindowsMRAssetConverter/CommandLine.cpp +++ b/WindowsMRAssetConverter/CommandLine.cpp @@ -12,10 +12,25 @@ const wchar_t * PARAM_LOD = L"-lod"; const wchar_t * PARAM_SCREENCOVERAGE = L"-screen-coverage"; const wchar_t * PARAM_MAXTEXTURESIZE = L"-max-texture-size"; const wchar_t * PARAM_SHARE_MATERIALS = L"-share-materials"; +const wchar_t * PARAM_MIN_VERSION = L"-min-version"; +const wchar_t * PARAM_PLATFORM = L"-platform"; +const wchar_t * PARAM_REPLACE_TEXTURES = L"-replace-textures"; +const wchar_t * PARAM_VALUE_VERSION_1709 = L"1709"; +const wchar_t * PARAM_VALUE_VERSION_1803 = L"1803"; +const wchar_t * PARAM_VALUE_VERSION_RS3 = L"rs3"; +const wchar_t * PARAM_VALUE_VERSION_RS4 = L"rs4"; +const wchar_t * PARAM_VALUE_VERSION_LATEST = L"latest"; +const wchar_t * PARAM_VALUE_HOLOGRAPHIC = L"holographic"; +const wchar_t * PARAM_VALUE_HOLOLENS= L"hololens"; +const wchar_t * PARAM_VALUE_DESKTOP = L"desktop"; +const wchar_t * PARAM_VALUE_PC = L"pc"; +const wchar_t * PARAM_VALUE_ALL = L"all"; const wchar_t * SUFFIX_CONVERTED = L"_converted"; const wchar_t * CLI_INDENT = L" "; const size_t MAXTEXTURESIZE_DEFAULT = 512; const size_t MAXTEXTURESIZE_MAX = 4096; +const CommandLine::Version MIN_VERSION_DEFAULT = CommandLine::Version::Version1709; +const CommandLine::Platform PLATFORM_DEFAULT = CommandLine::Platform::Desktop; enum class CommandLineParsingState { @@ -25,7 +40,9 @@ enum class CommandLineParsingState ReadTmpDir, ReadLods, ReadScreenCoverage, - ReadMaxTextureSize + ReadMaxTextureSize, + ReadMinVersion, + ReadPlatform }; void CommandLine::PrintHelp() @@ -41,11 +58,14 @@ void CommandLine::PrintHelp() << std::endl << L"Optional arguments:" << std::endl << indent << "[" << std::wstring(PARAM_OUTFILE) << L" ]" << std::endl - << indent << "[" << std::wstring(PARAM_TMPDIR) << L" ]" << std::endl + << indent << "[" << std::wstring(PARAM_TMPDIR) << L" ] - default is the system temp folder for the user" << std::endl + << indent << "[" << std::wstring(PARAM_PLATFORM) << " <" << PARAM_VALUE_ALL << " | " << PARAM_VALUE_HOLOGRAPHIC << " | " << PARAM_VALUE_DESKTOP << ">] - defaults to " << PARAM_VALUE_DESKTOP << std::endl + << indent << "[" << std::wstring(PARAM_MIN_VERSION) << " <" << PARAM_VALUE_VERSION_1709 << " | " << PARAM_VALUE_VERSION_1803 << " | " << PARAM_VALUE_VERSION_LATEST << ">] - defaults to " << PARAM_VALUE_VERSION_1709 << std::endl << indent << "[" << std::wstring(PARAM_LOD) << " ]" << std::endl << indent << "[" << std::wstring(PARAM_SCREENCOVERAGE) << " ]" << std::endl - << indent << "[" << std::wstring(PARAM_MAXTEXTURESIZE) << " ]" << std::endl - << indent << "[" << std::wstring(PARAM_SHARE_MATERIALS) << " defaults to false" << std::endl + << indent << "[" << std::wstring(PARAM_SHARE_MATERIALS) << "] - disabled if not present" << std::endl + << indent << "[" << std::wstring(PARAM_MAXTEXTURESIZE) << " ] - defaults to 512" << std::endl + << indent << "[" << std::wstring(PARAM_REPLACE_TEXTURES) << "] - disabled if not present" << std::endl << std::endl << "Example:" << std::endl << indent << "WindowsMRAssetConverter FileToConvert.gltf " @@ -65,7 +85,7 @@ void CommandLine::ParseCommandLineArguments( int argc, wchar_t *argv[], std::wstring& inputFilePath, AssetType& inputAssetType, std::wstring& outFilePath, std::wstring& tempDirectory, std::vector& lodFilePaths, std::vector& screenCoveragePercentages, size_t& maxTextureSize, - bool& shareMaterials) + bool& shareMaterials, Version& minVersion, Platform& targetPlatforms, bool& replaceTextures) { CommandLineParsingState state = CommandLineParsingState::Initial; @@ -80,6 +100,9 @@ void CommandLine::ParseCommandLineArguments( screenCoveragePercentages.clear(); maxTextureSize = MAXTEXTURESIZE_DEFAULT; shareMaterials = false; + minVersion = MIN_VERSION_DEFAULT; + targetPlatforms = PLATFORM_DEFAULT; + replaceTextures = false; state = CommandLineParsingState::InputRead; @@ -117,6 +140,22 @@ void CommandLine::ParseCommandLineArguments( else if (param == PARAM_SHARE_MATERIALS) { shareMaterials = true; + state = CommandLineParsingState::InputRead; + } + else if (param == PARAM_MIN_VERSION) + { + minVersion = MIN_VERSION_DEFAULT; + state = CommandLineParsingState::ReadMinVersion; + } + else if (param == PARAM_PLATFORM) + { + targetPlatforms = PLATFORM_DEFAULT; + state = CommandLineParsingState::ReadPlatform; + } + else if (param == PARAM_REPLACE_TEXTURES) + { + replaceTextures = true; + state = CommandLineParsingState::InputRead; } else { @@ -142,6 +181,44 @@ void CommandLine::ParseCommandLineArguments( case CommandLineParsingState::ReadMaxTextureSize: maxTextureSize = std::min(static_cast(std::stoul(param.c_str())), MAXTEXTURESIZE_MAX); break; + case CommandLineParsingState::ReadMinVersion: + if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_1709) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_VERSION_RS3) == 0) + { + minVersion = Version::Version1709; + } + else if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_1803) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_VERSION_RS4) == 0) + { + minVersion = Version::Version1803; + } + else if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_LATEST) == 0) + { + minVersion = Version::Latest; + } + else + { + throw std::invalid_argument("Invalid min version specified. For help, try the command again without parameters."); + } + state = CommandLineParsingState::InputRead; + break; + case CommandLineParsingState::ReadPlatform: + if (_wcsicmp(param.c_str(), PARAM_VALUE_ALL) == 0) + { + targetPlatforms = (Platform) (Platform::Desktop | Platform::Holographic); + } + else if (_wcsicmp(param.c_str(), PARAM_VALUE_HOLOGRAPHIC) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_HOLOLENS) == 0) + { + targetPlatforms = Platform::Holographic; + } + else if (_wcsicmp(param.c_str(), PARAM_VALUE_DESKTOP) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_PC) == 0) + { + targetPlatforms = Platform::Desktop; + } + else + { + throw std::invalid_argument("Invalid platform specified. For help, try the command again without parameters."); + } + state = CommandLineParsingState::InputRead; + break; case CommandLineParsingState::Initial: case CommandLineParsingState::InputRead: default: diff --git a/WindowsMRAssetConverter/CommandLine.h b/WindowsMRAssetConverter/CommandLine.h index fa5565c..c9e48cf 100644 --- a/WindowsMRAssetConverter/CommandLine.h +++ b/WindowsMRAssetConverter/CommandLine.h @@ -8,12 +8,26 @@ namespace CommandLine { + enum Platform + { + None = 0x0, + Holographic = 0x1, + Desktop = 0x2 + }; + + enum class Version + { + Version1709, // Fall Creators Update (RS3) + Version1803, // Spring Creators Update (RS4) + Latest = Version1803 + }; + void PrintHelp(); void ParseCommandLineArguments( int argc, wchar_t *argv[], std::wstring& inputFilePath, AssetType& inputAssetType, std::wstring& outFilePath, std::wstring& tempDirectory, std::vector& lodFilePaths, std::vector& screenCoveragePercentages, size_t& maxTextureSize, - bool& sharedMaterials); + bool& sharedMaterials, Version& minVersion, Platform& targetPlatforms, bool& replaceTextures); }; diff --git a/WindowsMRAssetConverter/README.md b/WindowsMRAssetConverter/README.md index 5ba0720..7a33c68 100644 --- a/WindowsMRAssetConverter/README.md +++ b/WindowsMRAssetConverter/README.md @@ -9,23 +9,47 @@ WindowsMRAssetConverter _<path to GLTF/GLB>_ ## Optional arguments - `-o ` - - Specifies the output file name and directory for the output GLB + - Specifies the output file name and directory for the output GLB. - If the file is a GLB and the output name is not specified, the tool defaults to the same name as input + "_converted.glb". +- `-platform ` + - **Default:** `desktop` + - `desktop`: optimizes assets for immersive PC-based headsets using the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) extension. + - `holographic`: optimizes assets for HoloLens using the [MSFT_packing_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extension. + - `all`: creates assets optimized for both holographic and desktop devices, but with a larger file size. + +- `-min-version <1709 | 1803 | latest>` + - **Default:** `1709` + - Specifies the minimum version of Windows 10 supported by this asset. + - The current options are `1709` (Fall Creators Update) and `1803` (Spring Creators Update), as well as `latest` which is currently the same as `1803`. + - Supporting Windows 10 version 1709 results in assets with a larger file size. If your app is compatible with Windows 10 1803+ only, it is recommended to set `-min-version 1803`. + - This setting does not have any effect on the Holographic platform. + - `-lod ` - Specifies a list of assets that represent levels of detail, from higher to lower, that should be merged with the main asset and used as alternates when the asset is displayed from a distance (with limited screen coverage). - `-screen-coverage ` - - Specifies the maximum screen coverage values for each of the levels of detail, according to the [MSFT_lod](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod) extension specification. + - Specifies the maximum screen coverage values for each of the levels of detail, according to the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension specification. + +- `-share-materials` + - If enabled, creates assets that share materials between different levels of detail. + - This assumes all LOD documents use the same indices for each material, and uses the textures from the most detailed level. -- `-temp-directory ` +- `-temp-directory ` + - **Default:** system temp folder for the user - Allows overriding the temporary folder where intermediate files (packed/compressed textures, converted GLBs) will be placed. -- `-max-texture-size ` +- `-max-texture-size ` + - **Default:** 512 - Allows overriding the maximum texture dimension (width/height) when compressing textures. The recommended maximum dimension in the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#texture_resolutions_and_workflow) is 512, and the allowed maximum is 4096. +- `-replace-textures` + - If enabled, replaces all textures with their DDS compressed equivalents during the compression step. + - This results in a smaller file size, but the resulting file will not be compatible with most glTF viewers. + + ## Example -`WindowsMRAssetConverter FileToConvert.gltf -o ConvertedFile.glb -lod Lod1.gltf Lod2.gltf -screen-coverage 0.5 0.2 0.01` +`WindowsMRAssetConverter FileToConvert.gltf -o ConvertedFile.glb -platform all -lod Lod1.gltf Lod2.gltf -screen-coverage 0.5 0.2 0.01` The above will convert _FileToConvert.gltf_ into _ConvertedFile.glb_ in the current directory. @@ -33,18 +57,19 @@ The above will convert _FileToConvert.gltf_ into _ConvertedFile.glb_ in the curr Each asset goes through the following steps when converting for compatibility with the Windows Mixed Reality home: -1. **Conversion from GLB** - any GLB files are converted to loose glTF + assets, to simplify the code for reading resources -1. **Texture packing** - The textures that are relevant for the Windows MR home are packed according to the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#materials) using the [MSFT\_packing\_occlusionRoughnessMetallic](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_packing_occlusionRoughnessMetallic) extension if necessary +1. **Conversion from GLB** - Any GLB files are converted to loose glTF + assets, to simplify the code for reading resources +1. **Texture packing** - The textures that are relevant for the Windows MR home are packed according to the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#materials) using the [MSFT\_packing\_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) and [MSFT\_packing\_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extensions as necessary 1. **Texture compression** - All textures that are used in the Windows MR home must be compressed as DDS BC5 or BC7 according to the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#materials). This step also generates mip maps for the textures, and resizes them down if necessary -1. **LOD merging** - All assets that represent levels of detail are merged into the main asset using the [MSFT_lod](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod) extension +1. **LOD merging** - All assets that represent levels of detail are merged into the main asset using the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension 1. **GLB export** - The resulting assets are exported as a GLB with all resources. As part of this step, accessors are modified to conform to the [glTF implementation notes in the documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#gltf_implementation_notes): component types are converted to types supported by the Windows MR home, and the min and max values are calculated before serializing the accessors to the GLB ## Additional resources - [Creating 3D models for use in the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home) -- [Microsoft glTF LOD Extension Specification](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod) -- [PC Mixed Reality Texture Packing Extensions Specification](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_packing_occlusionRoughnessMetallic) -- [Microsoft DDS Textures glTF extensions specification](https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_texture_dds) +- [Microsoft glTF LOD Extension Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) +- [PC Mixed Reality Texture Packing Extensions Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) +- [Holographic Mixed Reality Texture Packing Extensions Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) +- [Microsoft DDS Textures glTF extensions specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds) - [Implementing 3D app launchers](https://developer.microsoft.com/en-us/windows/mixed-reality/implementing_3d_app_launchers) - [Implementing 3D deep links for your app in the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/implementing_3d_deep_links_for_your_app_in_the_windows_mixed_reality_home) - [Navigating the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/navigating_the_windows_mixed_reality_home) diff --git a/WindowsMRAssetConverter/WindowsMRAssetConverter.cpp b/WindowsMRAssetConverter/WindowsMRAssetConverter.cpp index e7c783b..aa1a98b 100644 --- a/WindowsMRAssetConverter/WindowsMRAssetConverter.cpp +++ b/WindowsMRAssetConverter/WindowsMRAssetConverter.cpp @@ -74,7 +74,9 @@ GLTFDocument LoadAndConvertDocumentForWindowsMR( AssetType inputAssetType, const std::wstring& tempDirectory, size_t maxTextureSize, - bool processTextures = true) + TexturePacking packing, + bool processTextures = true, + bool retainOriginalImages = true) { // Load the document std::experimental::filesystem::path inputFilePathFS(inputFilePath); @@ -110,12 +112,12 @@ GLTFDocument LoadAndConvertDocumentForWindowsMR( // 1. Texture Packing auto tempDirectoryA = std::string(tempDirectory.begin(), tempDirectory.end()); - document = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, document, TexturePacking::RoughnessMetallicOcclusion, tempDirectoryA); + document = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, document, packing, tempDirectoryA); std::wcout << L"Compressing textures - this can take a few minutes..." << std::endl; // 2. Texture Compression - document = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, document, tempDirectoryA, maxTextureSize); + document = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, document, tempDirectoryA, maxTextureSize, retainOriginalImages); } return document; @@ -143,13 +145,55 @@ int wmain(int argc, wchar_t *argv[]) std::vector screenCoveragePercentages; size_t maxTextureSize; bool shareMaterials; + CommandLine::Version minVersion; + CommandLine::Platform targetPlatforms; + bool replaceTextures; - CommandLine::ParseCommandLineArguments(argc, argv, inputFilePath, inputAssetType, outFilePath, tempDirectory, lodFilePaths, screenCoveragePercentages, maxTextureSize, shareMaterials); + CommandLine::ParseCommandLineArguments( + argc, argv, inputFilePath, inputAssetType, outFilePath, tempDirectory, lodFilePaths, screenCoveragePercentages, + maxTextureSize, shareMaterials, minVersion, targetPlatforms, replaceTextures); + + 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 + { + compatibleVersionsText += L"Desktop (version 1803+)"; + } + } + + std::wcout << L"\nThis will generate an asset compatible with " << compatibleVersionsText << L"\n" << std::endl; // Load document, and perform steps: // 1. Texture Packing // 2. Texture Compression - auto document = LoadAndConvertDocumentForWindowsMR(inputFilePath, inputAssetType, tempDirectory, maxTextureSize); + auto document = LoadAndConvertDocumentForWindowsMR(inputFilePath, inputAssetType, tempDirectory, maxTextureSize, packing, true /* processTextures */, !replaceTextures); // 3. LOD Merging if (lodFilePaths.size() > 0) @@ -166,7 +210,7 @@ int wmain(int argc, wchar_t *argv[]) 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, maxTextureSize, !shareMaterials)); + lodDocuments.push_back(LoadAndConvertDocumentForWindowsMR(lod, AssetTypeUtils::AssetTypeFromFilePath(lod), subFolder, maxTextureSize, packing, !shareMaterials, !replaceTextures)); lodDocumentRelativePaths.push_back(FileSystem::GetRelativePathWithTrailingSeparator(FileSystem::GetBasePath(inputFilePath), FileSystem::GetBasePath(lod))); } diff --git a/glTF-Toolkit.Test/GLTFTexturePackingUtilsTests.cpp b/glTF-Toolkit.Test/GLTFTexturePackingUtilsTests.cpp index 516bb9c..db8b311 100644 --- a/glTF-Toolkit.Test/GLTFTexturePackingUtilsTests.cpp +++ b/glTF-Toolkit.Test/GLTFTexturePackingUtilsTests.cpp @@ -46,35 +46,57 @@ namespace Microsoft::glTF::Toolkit::Test Assert::IsTrue(material.id == packedMaterial.id); Assert::IsTrue(doc.materials.Size() == packedDoc.materials.Size()); - // Check that the packed material has the new extension - Assert::IsTrue(material.extensions.size() + 1 == packedMaterial.extensions.size()); + size_t expectedExtensionsSize = material.extensions.size(); // Check the new extension is not empty - auto packingOrmExtension = packedMaterial.extensions.at(std::string(EXTENSION_MSFT_PACKING_ORM)); - Assert::IsFalse(packingOrmExtension.empty()); - - // Check the new extension contains an ORM texture - rapidjson::Document ormJson; - ormJson.Parse(packingOrmExtension.c_str()); - - if (packing & TexturePacking::OcclusionRoughnessMetallic) + if (packing & (TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)) { - Assert::IsTrue(ormJson["occlusionRoughnessMetallicTexture"].IsObject()); - Assert::IsTrue(ormJson["occlusionRoughnessMetallicTexture"].HasMember("index")); + expectedExtensionsSize++; + + auto packingOrmExtension = packedMaterial.extensions.at(std::string(EXTENSION_MSFT_PACKING_ORM)); + Assert::IsFalse(packingOrmExtension.empty()); + + // Check the new extension contains an ORM texture + rapidjson::Document ormJson; + ormJson.Parse(packingOrmExtension.c_str()); + + if (packing & TexturePacking::OcclusionRoughnessMetallic) + { + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_ORMTEXTURE_KEY].IsObject()); + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_ORMTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); + } + + if (packing & TexturePacking::RoughnessMetallicOcclusion) + { + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_RMOTEXTURE_KEY].IsObject()); + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_RMOTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); + } + + if (!material.normalTexture.id.empty()) + { + // Check the new extension contains a normal texture + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_NORMALTEXTURE_KEY].IsObject()); + Assert::IsTrue(ormJson[MSFT_PACKING_ORM_NORMALTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); + } } - if (packing & TexturePacking::RoughnessMetallicOcclusion) + if (packing & TexturePacking::NormalRoughnessMetallic) { - Assert::IsTrue(ormJson["roughnessMetallicOcclusionTexture"].IsObject()); - Assert::IsTrue(ormJson["roughnessMetallicOcclusionTexture"].HasMember("index")); - } + expectedExtensionsSize++; - if (!material.normalTexture.id.empty()) - { - // Check the new extension contains a normal texture - Assert::IsTrue(ormJson["normalTexture"].IsObject()); - Assert::IsTrue(ormJson["normalTexture"].HasMember("index")); + auto packingNrmExtension = packedMaterial.extensions.at(std::string(EXTENSION_MSFT_PACKING_NRM)); + Assert::IsFalse(packingNrmExtension.empty()); + + // Check the new extension contains an NRM texture + rapidjson::Document nrmJson; + nrmJson.Parse(packingNrmExtension.c_str()); + + Assert::IsTrue(nrmJson[MSFT_PACKING_NRM_KEY].IsObject()); + Assert::IsTrue(nrmJson[MSFT_PACKING_NRM_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); } + + // Check that the packed material has the new extension + Assert::IsTrue(expectedExtensionsSize == packedMaterial.extensions.size()); }); } @@ -119,6 +141,23 @@ namespace Microsoft::glTF::Toolkit::Test ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)); } + TEST_METHOD(GLTFTexturePackingUtils_PackNRM) + { + ExecutePackingTest(c_waterBottleJson, TexturePacking::NormalRoughnessMetallic); + } + + TEST_METHOD(GLTFTexturePackingUtils_PackNRMandORM) + { + // Default for RS4+ compatible with both HoloLens and Desktop + ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::NormalRoughnessMetallic)); + } + + TEST_METHOD(GLTFTexturePackingUtils_PackNRMandORMandRMO) + { + // Maximum compatibility: all packings + ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::NormalRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)); + } + TEST_METHOD(GLTFTexturePackingUtils_PackAllWithNoMaterials) { // This asset has no materials diff --git a/glTF-Toolkit.UWP.Test/UWPTest.cs b/glTF-Toolkit.UWP.Test/UWPTest.cs index 68ce397..bb93459 100644 --- a/glTF-Toolkit.UWP.Test/UWPTest.cs +++ b/glTF-Toolkit.UWP.Test/UWPTest.cs @@ -74,7 +74,7 @@ public async Task GLBConvertToWindowsMR() StorageFolder outputFolder = await CreateTemporaryOutputFolderAsync("Out_" + glbBaseName); - var converted = await WindowsMRConversion.ConvertAssetForWindowsMR(sourceGlbFile, outputFolder); + var converted = await WindowsMRConversion.ConvertAssetForWindowsMR(sourceGlbFile, outputFolder, 512, TexturePacking.OcclusionRoughnessMetallic); Assert.IsTrue(converted.Name == "WaterBottle_converted.glb"); } diff --git a/glTF-Toolkit.UWP/WindowsMRConversion.cpp b/glTF-Toolkit.UWP/WindowsMRConversion.cpp index 80eb06b..b0d0358 100644 --- a/glTF-Toolkit.UWP/WindowsMRConversion.cpp +++ b/glTF-Toolkit.UWP/WindowsMRConversion.cpp @@ -19,7 +19,9 @@ using namespace concurrency; using namespace Platform; using namespace Windows::Foundation; +using namespace Windows::Foundation::Metadata; using namespace Windows::Storage; +using namespace Windows::System::Profile; using namespace Microsoft::glTF::Toolkit::UWP; @@ -29,10 +31,27 @@ IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(Sto } IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile^ gltfOrGlbFile, StorageFolder^ outputFolder, size_t maxTextureSize) +{ + UWP::TexturePacking detectedPacking = UWP::TexturePacking::None; + + if (AnalyticsInfo::VersionInfo->DeviceFamily == "Windows.Holographic") + { + detectedPacking = UWP::TexturePacking::NormalRoughnessMetallic; + } + else + { + bool isVersion1803OrNewer = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6); + detectedPacking = isVersion1803OrNewer ? UWP::TexturePacking::OcclusionRoughnessMetallic : UWP::TexturePacking::RoughnessMetallicOcclusion; + } + + return ConvertAssetForWindowsMR(gltfOrGlbFile, outputFolder, maxTextureSize, detectedPacking); +} + +IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile ^ gltfOrGlbFile, StorageFolder ^ outputFolder, size_t maxTextureSize, TexturePacking packing) { auto isGlb = gltfOrGlbFile->FileType == L".glb"; - return create_async([gltfOrGlbFile, maxTextureSize, outputFolder, isGlb]() + return create_async([gltfOrGlbFile, maxTextureSize, outputFolder, isGlb, packing]() { return create_task([gltfOrGlbFile, isGlb]() { @@ -45,23 +64,23 @@ IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(Sto return task_from_result(gltfOrGlbFile); } }) - .then([maxTextureSize, outputFolder, isGlb](StorageFile^ gltfFile) + .then([maxTextureSize, outputFolder, isGlb, packing](StorageFile^ gltfFile) { auto stream = std::make_shared(gltfFile->Path->Data(), std::ios::in); GLTFDocument document = DeserializeJson(*stream); return create_task(gltfFile->GetParentAsync()) - .then([document, maxTextureSize, outputFolder, gltfFile, isGlb](StorageFolder^ baseFolder) + .then([document, maxTextureSize, outputFolder, gltfFile, isGlb, packing](StorageFolder^ baseFolder) { GLTFStreamReader streamReader(baseFolder); // 1. Texture Packing auto tempDirectory = std::wstring(ApplicationData::Current->TemporaryFolder->Path->Data()); auto tempDirectoryA = std::string(tempDirectory.begin(), tempDirectory.end()); - auto convertedDoc = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, document, TexturePacking::RoughnessMetallicOcclusion, tempDirectoryA); + auto convertedDoc = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, document, static_cast(packing), tempDirectoryA); // 2. Texture Compression - convertedDoc = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, document, tempDirectoryA, maxTextureSize); + convertedDoc = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, document, tempDirectoryA, maxTextureSize, false /* retainOriginalImages */); // 3. Make sure there's a default scene set if (!convertedDoc.HasDefaultScene()) diff --git a/glTF-Toolkit.UWP/WindowsMRConversion.h b/glTF-Toolkit.UWP/WindowsMRConversion.h index e84c719..4e81968 100644 --- a/glTF-Toolkit.UWP/WindowsMRConversion.h +++ b/glTF-Toolkit.UWP/WindowsMRConversion.h @@ -3,12 +3,24 @@ #pragma once +#include + namespace Microsoft::glTF::Toolkit::UWP { + [Platform::Metadata::Flags] + public enum class TexturePacking : unsigned int + { + None = Toolkit::TexturePacking::None, + OcclusionRoughnessMetallic = Toolkit::TexturePacking::OcclusionRoughnessMetallic, + RoughnessMetallicOcclusion = Toolkit::TexturePacking::RoughnessMetallicOcclusion, + NormalRoughnessMetallic = Toolkit::TexturePacking::NormalRoughnessMetallic + }; + public ref class WindowsMRConversion sealed { public: static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder); static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder, size_t maxTextureSize); + static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder, size_t maxTextureSize, UWP::TexturePacking packing); }; } diff --git a/glTF-Toolkit/inc/GLTFTexturePackingUtils.h b/glTF-Toolkit/inc/GLTFTexturePackingUtils.h index 462ea86..09444e6 100644 --- a/glTF-Toolkit/inc/GLTFTexturePackingUtils.h +++ b/glTF-Toolkit/inc/GLTFTexturePackingUtils.h @@ -4,17 +4,25 @@ #pragma once #include +#include namespace Microsoft::glTF::Toolkit { extern const char* EXTENSION_MSFT_PACKING_ORM; + extern const char* EXTENSION_MSFT_PACKING_NRM; + extern const char* MSFT_PACKING_INDEX_KEY; + extern const char* MSFT_PACKING_ORM_ORMTEXTURE_KEY; + extern const char* MSFT_PACKING_ORM_RMOTEXTURE_KEY; + extern const char* MSFT_PACKING_ORM_NORMALTEXTURE_KEY; + extern const char* MSFT_PACKING_NRM_KEY; /// Texture packing flags. May be combined to pack multiple formats at once. enum TexturePacking { None = 0x0, OcclusionRoughnessMetallic = 0x1, - RoughnessMetallicOcclusion = 0x2 + RoughnessMetallicOcclusion = 0x2, + NormalRoughnessMetallic = 0x4 }; /// diff --git a/glTF-Toolkit/src/GLTFLODUtils.cpp b/glTF-Toolkit/src/GLTFLODUtils.cpp index a03615f..1dd77a6 100644 --- a/glTF-Toolkit/src/GLTFLODUtils.cpp +++ b/glTF-Toolkit/src/GLTFLODUtils.cpp @@ -37,10 +37,10 @@ namespace { if (json.HasMember(textureId)) { - if (json[textureId].HasMember("index")) + if (json[textureId].HasMember(MSFT_PACKING_INDEX_KEY)) { - auto index = json[textureId]["index"].GetInt(); - json[textureId]["index"] = index + offset; + auto index = json[textureId][MSFT_PACKING_INDEX_KEY].GetInt(); + json[textureId][MSFT_PACKING_INDEX_KEY] = index + offset; } } } @@ -290,9 +290,9 @@ namespace { rapidjson::Document ormJson = RapidJsonUtils::CreateDocumentFromString(ormExtensionIt->second); - AddIndexOffsetPacked(ormJson, "occlusionRoughnessMetallicTexture", texturesOffset); - AddIndexOffsetPacked(ormJson, "roughnessMetallicOcclusionTexture", texturesOffset); - AddIndexOffsetPacked(ormJson, "normalTexture", texturesOffset); + AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_ORMTEXTURE_KEY, texturesOffset); + AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_RMOTEXTURE_KEY, texturesOffset); + AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_NORMALTEXTURE_KEY, texturesOffset); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -301,6 +301,21 @@ namespace ormExtensionIt->second = buffer.GetString(); } + // MSFT_packing_normalRoughnessMetallic packed texture + auto nrmExtensionIt = material.extensions.find(EXTENSION_MSFT_PACKING_NRM); + if (nrmExtensionIt != material.extensions.end() && !nrmExtensionIt->second.empty()) + { + rapidjson::Document nrmJson = RapidJsonUtils::CreateDocumentFromString(nrmExtensionIt->second); + + AddIndexOffsetPacked(nrmJson, MSFT_PACKING_NRM_KEY, texturesOffset); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + nrmJson.Accept(writer); + + nrmExtensionIt->second = buffer.GetString(); + } + gltfLod.materials.Append(std::move(material)); } } diff --git a/glTF-Toolkit/src/GLTFTextureCompressionUtils.cpp b/glTF-Toolkit/src/GLTFTextureCompressionUtils.cpp index e2edd2c..091df65 100644 --- a/glTF-Toolkit/src/GLTFTextureCompressionUtils.cpp +++ b/glTF-Toolkit/src/GLTFTextureCompressionUtils.cpp @@ -199,32 +199,46 @@ GLTFDocument GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(const compressIfNotEmpty(material.metallicRoughness.baseColorTextureId, TextureCompression::BC7); compressIfNotEmpty(material.emissiveTextureId, TextureCompression::BC7); - // Get other textures from the MSFT_packing_occlusionRoughnessMetallic extension + // Get textures from the MSFT_packing_occlusionRoughnessMetallic extension if (material.extensions.find(EXTENSION_MSFT_PACKING_ORM) != material.extensions.end()) { rapidjson::Document packingOrmContents; packingOrmContents.Parse(material.extensions[EXTENSION_MSFT_PACKING_ORM].c_str()); // Compress packed textures as BC7 - if (packingOrmContents.HasMember("roughnessMetallicOcclusionTexture")) + if (packingOrmContents.HasMember(MSFT_PACKING_ORM_RMOTEXTURE_KEY)) { - auto rmoTextureId = packingOrmContents["roughnessMetallicOcclusionTexture"]["index"].GetInt(); + auto rmoTextureId = packingOrmContents[MSFT_PACKING_ORM_RMOTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(rmoTextureId), TextureCompression::BC7); } - if (packingOrmContents.HasMember("occlusionRoughnessMetallicTexture")) + if (packingOrmContents.HasMember(MSFT_PACKING_ORM_ORMTEXTURE_KEY)) { - auto ormTextureId = packingOrmContents["occlusionRoughnessMetallicTexture"]["index"].GetInt(); + auto ormTextureId = packingOrmContents[MSFT_PACKING_ORM_ORMTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(ormTextureId), TextureCompression::BC7); } // Compress normal texture as BC5 - if (packingOrmContents.HasMember("normalTexture")) + if (packingOrmContents.HasMember(MSFT_PACKING_ORM_NORMALTEXTURE_KEY)) { - auto normalTextureId = packingOrmContents["normalTexture"]["index"].GetInt(); + auto normalTextureId = packingOrmContents[MSFT_PACKING_ORM_NORMALTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(normalTextureId), TextureCompression::BC5); } } + + // Get textures from the MSFT_packing_normalRoughnessMetallic extension + if (material.extensions.find(EXTENSION_MSFT_PACKING_NRM) != material.extensions.end()) + { + rapidjson::Document packingNrmContents; + packingNrmContents.Parse(material.extensions[EXTENSION_MSFT_PACKING_NRM].c_str()); + + // Compress packed texture as BC7 + if (packingNrmContents.HasMember(MSFT_PACKING_NRM_KEY)) + { + auto nrmTextureId = packingNrmContents[MSFT_PACKING_NRM_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); + compressIfNotEmpty(std::to_string(nrmTextureId), TextureCompression::BC7); + } + } } return outputDoc; diff --git a/glTF-Toolkit/src/GLTFTexturePackingUtils.cpp b/glTF-Toolkit/src/GLTFTexturePackingUtils.cpp index e35b984..d3d82a5 100644 --- a/glTF-Toolkit/src/GLTFTexturePackingUtils.cpp +++ b/glTF-Toolkit/src/GLTFTexturePackingUtils.cpp @@ -18,6 +18,12 @@ using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_PACKING_ORM = "MSFT_packing_occlusionRoughnessMetallic"; +const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_PACKING_NRM = "MSFT_packing_normalRoughnessMetallic"; +const char* Microsoft::glTF::Toolkit::MSFT_PACKING_INDEX_KEY = "index"; +const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_ORMTEXTURE_KEY = "occlusionRoughnessMetallicTexture"; +const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_RMOTEXTURE_KEY = "roughnessMetallicOcclusionTexture"; +const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_NORMALTEXTURE_KEY = "normalTexture"; +const char* Microsoft::glTF::Toolkit::MSFT_PACKING_NRM_KEY = "normalRoughnessMetallicTexture"; namespace { @@ -71,25 +77,28 @@ namespace return imageId; } - void AddTextureToOrmExtension(const std::string& imageId, TexturePacking packing, GLTFDocument& doc, rapidjson::Value& ormExtensionJson, rapidjson::MemoryPoolAllocator<>& a) + void AddTextureToExtension(const std::string& imageId, TexturePacking packing, GLTFDocument& doc, rapidjson::Value& packedExtensionJson, rapidjson::MemoryPoolAllocator<>& a) { - Texture ormTexture; + Texture packedTexture; auto textureId = std::to_string(doc.textures.Size()); - ormTexture.id = textureId; - ormTexture.imageId = imageId; - doc.textures.Append(std::move(ormTexture)); + packedTexture.id = textureId; + packedTexture.imageId = imageId; + doc.textures.Append(std::move(packedTexture)); - rapidjson::Value ormTextureJson(rapidjson::kObjectType); + rapidjson::Value packedTextureJson(rapidjson::kObjectType); { - ormTextureJson.AddMember("index", rapidjson::Value(std::stoi(textureId)), a); + packedTextureJson.AddMember(rapidjson::StringRef(MSFT_PACKING_INDEX_KEY), rapidjson::Value(std::stoi(textureId)), a); } switch (packing) { case TexturePacking::OcclusionRoughnessMetallic: - ormExtensionJson.AddMember("occlusionRoughnessMetallicTexture", ormTextureJson, a); + packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_ORMTEXTURE_KEY), packedTextureJson, a); break; case TexturePacking::RoughnessMetallicOcclusion: - ormExtensionJson.AddMember("roughnessMetallicOcclusionTexture", ormTextureJson, a); + packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_RMOTEXTURE_KEY), packedTextureJson, a); + break; + case TexturePacking::NormalRoughnessMetallic: + packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_NRM_KEY), packedTextureJson, a); break; default: throw GLTFException("Invalid packing."); @@ -131,7 +140,11 @@ GLTFDocument GLTFTexturePackingUtils::PackMaterialForWindowsMR(const IStreamRead // Create the JSON for the material extension element rapidjson::Document ormExtensionJson; ormExtensionJson.SetObject(); - rapidjson::MemoryPoolAllocator<>& allocator = ormExtensionJson.GetAllocator(); + rapidjson::MemoryPoolAllocator<>& ormAllocator = ormExtensionJson.GetAllocator(); + + rapidjson::Document nrmExtensionJson; + nrmExtensionJson.SetObject(); + rapidjson::MemoryPoolAllocator<>& nrmAllocator = nrmExtensionJson.GetAllocator(); std::unique_ptr metallicRoughnessImage = nullptr; uint8_t *mrPixels = nullptr; @@ -148,9 +161,11 @@ GLTFDocument GLTFTexturePackingUtils::PackMaterialForWindowsMR(const IStreamRead } } + bool packingIncludesOrm = (packing & (TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)) > 0; + std::unique_ptr occlusionImage = nullptr; uint8_t *occlusionPixels = nullptr; - if (hasOcclusion) + if (hasOcclusion && packingIncludesOrm) { try { @@ -163,6 +178,23 @@ GLTFDocument GLTFTexturePackingUtils::PackMaterialForWindowsMR(const IStreamRead } } + bool packingIncludesNrm = (packing & TexturePacking::NormalRoughnessMetallic) > 0; + + std::unique_ptr normalImage = nullptr; + uint8_t *normalPixels = nullptr; + if (hasNormal && packingIncludesNrm) + { + try + { + normalImage = std::make_unique(GLTFTextureLoadingUtils::LoadTexture(streamReader, doc, normal)); + normalPixels = normalImage->GetPixels(); + } + catch (GLTFException) + { + throw GLTFException("Failed to load normal texture."); + } + } + // Pack textures using DirectXTex if (packing & TexturePacking::OcclusionRoughnessMetallic && (hasMR || hasOcclusion)) @@ -206,7 +238,7 @@ GLTFDocument GLTFTexturePackingUtils::PackMaterialForWindowsMR(const IStreamRead ormImageId = AddImageToDocument(outputDoc, imagePath); } - AddTextureToOrmExtension(ormImageId, TexturePacking::OcclusionRoughnessMetallic, outputDoc, ormExtensionJson, allocator); + AddTextureToExtension(ormImageId, TexturePacking::OcclusionRoughnessMetallic, outputDoc, ormExtensionJson, ormAllocator); } if (packing & TexturePacking::RoughnessMetallicOcclusion && (hasMR || hasOcclusion)) @@ -239,27 +271,75 @@ GLTFDocument GLTFTexturePackingUtils::PackMaterialForWindowsMR(const IStreamRead // Add back to GLTF auto rmoImageId = AddImageToDocument(outputDoc, imagePath); - AddTextureToOrmExtension(rmoImageId, TexturePacking::RoughnessMetallicOcclusion, outputDoc, ormExtensionJson, allocator); + AddTextureToExtension(rmoImageId, TexturePacking::RoughnessMetallicOcclusion, outputDoc, ormExtensionJson, ormAllocator); + } + + if (packingIncludesNrm && (hasMR || hasNormal)) + { + auto nrm = std::make_unique(); + + // TODO: resize? + + auto sourceImage = hasMR ? *metallicRoughnessImage->GetImage(0, 0, 0) : *normalImage->GetImage(0, 0, 0); + if (FAILED(nrm->Initialize2D(sourceImage.format, sourceImage.width, sourceImage.height, 1, 1))) + { + throw GLTFException("Failed to initialize from texture."); + } + + auto nrmPixels = nrm->GetPixels(); + auto metadata = nrm->GetMetadata(); + + for (size_t i = 0; i < metadata.width * metadata.height; i += 1) + { + // Normal: N [RG] -> NRM [RG] + *GetChannelValue(nrmPixels, i, Channel::Red) = hasNormal ? *GetChannelValue(normalPixels, i, Channel::Red) : 255.0f; + *GetChannelValue(nrmPixels, i, Channel::Green) = hasNormal ? *GetChannelValue(normalPixels, i, Channel::Green) : 255.0f; + // Roughness: MR [G] -> NRM [B] + *GetChannelValue(nrmPixels, i, Channel::Blue) = hasMR ? *GetChannelValue(mrPixels, i, Channel::Green) : 255.0f; + // Metalness: MR [B] -> NRM [A] + *GetChannelValue(nrmPixels, i, Channel::Alpha) = hasMR ? *GetChannelValue(mrPixels, i, Channel::Blue) : 255.0f; + } + + auto imagePath = SaveAsPng(nrm, "packing_nrm_" + material.id + ".png", outputDirectory); + + // Add back to GLTF + auto nrmImageId = AddImageToDocument(outputDoc, imagePath); + + AddTextureToExtension(nrmImageId, TexturePacking::NormalRoughnessMetallic, outputDoc, nrmExtensionJson, nrmAllocator); } - if (hasNormal) + if (packingIncludesOrm) { - rapidjson::Value ormNormalTextureJson(rapidjson::kObjectType); + if (hasNormal) { - ormNormalTextureJson.AddMember("index", rapidjson::Value(std::stoi(normal)), allocator); + rapidjson::Value ormNormalTextureJson(rapidjson::kObjectType); + { + ormNormalTextureJson.AddMember(rapidjson::StringRef(MSFT_PACKING_INDEX_KEY), rapidjson::Value(std::stoi(normal)), ormAllocator); + } + ormExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_NORMALTEXTURE_KEY), ormNormalTextureJson, ormAllocator); } - ormExtensionJson.AddMember("normalTexture", ormNormalTextureJson, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + ormExtensionJson.Accept(writer); + + outputMaterial.extensions.insert(std::pair(EXTENSION_MSFT_PACKING_ORM, buffer.GetString())); + + outputDoc.extensionsUsed.insert(EXTENSION_MSFT_PACKING_ORM); } - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - ormExtensionJson.Accept(writer); + if (packingIncludesNrm) + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + nrmExtensionJson.Accept(writer); - outputMaterial.extensions.insert(std::pair(EXTENSION_MSFT_PACKING_ORM, buffer.GetString())); + outputMaterial.extensions.insert(std::pair(EXTENSION_MSFT_PACKING_NRM, buffer.GetString())); - outputDoc.materials.Replace(outputMaterial); + outputDoc.extensionsUsed.insert(EXTENSION_MSFT_PACKING_NRM); + } - outputDoc.extensionsUsed.insert(EXTENSION_MSFT_PACKING_ORM); + outputDoc.materials.Replace(outputMaterial); return outputDoc; }