// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License. #include "VrmConvertTexture.h" #include "VrmConvert.h" #include "VrmUtil.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Modules/ModuleManager.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "PixelFormat.h" #include "RenderUtils.h" #include "Engine/Texture.h" #include "Engine/Texture2D.h" #include "Materials/Material.h" #include "Engine/SubsurfaceProfile.h" #include "Materials/MaterialInstanceConstant.h" #include "VrmAssetListObject.h" #include "Async/ParallelFor.h" #include "UObject/UObjectHash.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #if WITH_EDITOR #include "Factories.h" #include "Factories/TextureFactory.h" #endif #include #include #include #include #include #include #include #if UE_VERSION_OLDER_THAN(4,23,0) #define TRACE_CPUPROFILER_EVENT_SCOPE(a) #else #endif namespace { bool bDefaultVrmMaterial = false; bool LocalIsOriginalVrmMaterial() { return bDefaultVrmMaterial; } void LocalSetOriginalVrmMaterial(bool b) { bDefaultVrmMaterial = b; } void LocalMaterialSetParent(UMaterialInstanceConstant *material, UMaterialInterface *parent) { #if WITH_EDITOR if (VRMConverter::IsImportMode()) { material->SetParentEditorOnly(parent); } else { material->Parent = parent; } FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::Default, GMaxRHIShaderPlatform); UpdateContext.AddMaterialInstance(material); #else material->Parent = parent; #endif } void LocalTextureSet(UMaterialInstanceConstant *dm, FName name, UTexture2D * tex) { #if WITH_EDITOR if (VRMConverter::IsImportMode()) { dm->SetTextureParameterValueEditorOnly(name, tex); }else{ FTextureParameterValue *v = new (dm->TextureParameterValues) FTextureParameterValue(); v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = name; v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; v->ParameterValue = tex; } #else FTextureParameterValue *v = new (dm->TextureParameterValues) FTextureParameterValue(); v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = name; v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; v->ParameterValue = tex; #endif } void LocalScalarParameterSet(UMaterialInstanceConstant *dm, FName name, float f) { bool bSet = false; #if WITH_EDITOR if (VRMConverter::IsImportMode()) { dm->SetScalarParameterValueEditorOnly(name, f); bSet = true; } #endif if (bSet == false) { FScalarParameterValue *v = nullptr; for (auto &a : dm->ScalarParameterValues) { if (a.ParameterInfo.Name == name) { v = &a; } } if (v == nullptr) { v = new (dm->ScalarParameterValues) FScalarParameterValue(); } v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = name; v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; v->ParameterValue = f; } } void LocalVectorParameterSet(UMaterialInstanceConstant *dm, FName name, FLinearColor c) { bool bSet = false; #if WITH_EDITOR if (VRMConverter::IsImportMode()) { dm->SetVectorParameterValueEditorOnly(name, c); bSet = true; } #endif if (bSet == false) { FVectorParameterValue *v = nullptr; for (auto &a : dm->VectorParameterValues) { if (a.ParameterInfo.Name == name) { v = &a; } } if (v == nullptr) { v = new (dm->VectorParameterValues) FVectorParameterValue(); } v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = name; v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; v->ParameterValue = c; } } void LocalMaterialFinishParam(UMaterialInstanceConstant *material) { #if WITH_EDITOR if (VRMConverter::IsImportMode()) { material->PreEditChange(NULL); material->PostEditChange(); } else { material->PostLoad(); } #else material->PostLoad(); #endif } void createSmallThumbnail(UVrmAssetListObject *vrmAssetList, const aiScene *aiData) { #if WITH_EDITORONLY_DATA UTexture2D *src = nullptr; VRM::VRMMetadata *meta = reinterpret_cast(aiData->mVRMMeta); if (meta == nullptr) { return; } int TextureID = -1; for (int i = 0; i < meta->license.licensePairNum; ++i) { auto &p = meta->license.licensePair[i]; if (FString(TEXT("texture")) == p.Key.C_Str()) { int t = FCString::Atoi(*FString(p.Value.C_Str())); if (t >= 0 && t < vrmAssetList->Textures.Num()) { src = vrmAssetList->Textures[t]; TextureID = t; break; } } } if (src == nullptr) { // todo small texture vrm1license } if (src == nullptr) { return; } const int W = src->GetSurfaceWidth(); const int H = src->GetSurfaceHeight(); int dW = FMath::Min(256, W); int dH = FMath::Min(256, H); vrmAssetList->SmallThumbnailTexture = src; if (W == dW && H == dH) { return; } if (W != H) { return; } if (TextureID < 0 || TextureID >= (int)aiData->mNumTextures) { return; } if (aiData->mTextures[TextureID] == nullptr) { return; } TArray sData; sData.SetNum(W * H * 4); TArray dData; dData.SetNum(256*256 * 4); FString baseName = (src->GetFName()).ToString(); baseName += TEXT("_small"); { VRMUtil::FImportImage img; auto *a = aiData->mTextures[TextureID]; if (VRMLoaderUtil::LoadImageFromMemory(a->pcData, a->mWidth, img) == false) { return; } if (img.RawData.GetData() == nullptr) { return; } FMemory::Memcpy(sData.GetData(), img.RawData.GetData(), img.RawData.Num()); } auto* pkg = vrmAssetList->Package; if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) { pkg = VRM4U_CreatePackage(vrmAssetList->Package, *baseName); } UTexture2D* NewTexture2D = VRMLoaderUtil::CreateTexture(W, H, baseName, pkg); // scale texture { ParallelFor(dH, [&](int32 y){ //for (int32 y = 0; y < dH; y++){ float s = (float)H / dH; for (int32 x = 0; x < dW; x++){ int32 xx = (s * (0.5f + x)); int32 yy = (s * (0.5f + y)); uint8* dp = &dData[(y * dW + x) * sizeof(uint8) * 4]; int c = 0; int tmp[4] = {}; int n = (float)W / dW / 2; for (int ry = yy - n; ry <= yy + n; ry++) { for (int rx = xx - n; rx <= xx + n; rx++) { if (rx < 0 || ry < 0) continue; if (rx >= W || ry >= H) continue; const uint8* rp = &sData[(ry * W + rx) * sizeof(uint8) * 4]; tmp[0] += rp[0]; tmp[1] += rp[1]; tmp[2] += rp[2]; tmp[3] += rp[3]; ++c; } } dp[0] = tmp[0] / c; dp[1] = tmp[1] / c; dp[2] = tmp[2] / c; dp[3] = tmp[3] / c; } }); // Set options NewTexture2D->SRGB = true;// bUseSRGB; NewTexture2D->CompressionSettings = TC_Default; NewTexture2D->AddressX = TA_Wrap; NewTexture2D->AddressY = TA_Wrap; NewTexture2D->CompressionNone = false; NewTexture2D->DeferCompression = true; if (VRMConverter::Options::Get().IsMipmapGenerateMode() && FMath::IsPowerOfTwo(dW) && FMath::IsPowerOfTwo(dH)) { NewTexture2D->MipGenSettings = TMGS_FromTextureGroup; } else { NewTexture2D->MipGenSettings = TMGS_NoMipmaps; } NewTexture2D->Source.Init(dW, dH, 1, 1, ETextureSourceFormat::TSF_BGRA8, dData.GetData()); //NewTexture2D->Source.Compress(); // Update the remote texture data NewTexture2D->UpdateResource(); NewTexture2D->PostEditChange(); vrmAssetList->SmallThumbnailTexture = NewTexture2D; } #endif } bool createAndAddMaterial(UMaterialInstanceConstant *dm, int matIndex, UVrmAssetListObject *vrmAssetList, const VRMConverter *vc, const TArray &TextureTypeToIndex ) { auto i = matIndex; // default set function auto SetLocalParamsForVector = [](auto* dm) { LocalVectorParameterSet(dm, TEXT("mtoon_Color"), FLinearColor(1, 1, 1, 1)); LocalVectorParameterSet(dm, TEXT("mtoon_ShadeColor"), FLinearColor(1, 1, 1, 1)); LocalVectorParameterSet(dm, TEXT("mtoon_OutlineColor"), FLinearColor(0, 0, 0, 1)); }; auto SetLocalParamsForScaler = [](auto* dm) { LocalScalarParameterSet(dm, TEXT("mtoon_BumpScale"), 1.f); LocalScalarParameterSet(dm, TEXT("mtoon_NormalScale"), 1.f); LocalScalarParameterSet(dm, TEXT("mtoon_ReceiveShadowRate"), 1.f); LocalScalarParameterSet(dm, TEXT("mtoon_OutlineLightingMix"), 1.f); LocalScalarParameterSet(dm, TEXT("mtoon_OutlineWidth"), 0.1f); LocalScalarParameterSet(dm, TEXT("mtoon_OutlineWidthMode"), 1.f); }; // default for not vrm material if (dm) { if (LocalIsOriginalVrmMaterial() == false) { SetLocalParamsForVector(dm); SetLocalParamsForScaler(dm); } } // VRM1 if (VRMConverter::Options::Get().IsVRM10Model()) { LocalScalarParameterSet(dm, TEXT("bVRM10Mode"), 1.f); } VRM::VRMMaterial vrmMat; if (vc->GetMatParam(vrmMat, i) == false) { return false; } { struct TT { FString key; float* value; }; TT tableParam[] = { {TEXT("_Color"), vrmMat.vectorProperties._Color}, {TEXT("_ShadeColor"), vrmMat.vectorProperties._ShadeColor}, {TEXT("_MainTex"), vrmMat.vectorProperties._MainTex}, {TEXT("_ShadeTexture"), vrmMat.vectorProperties._ShadeTexture}, {TEXT("_BumpMap"), vrmMat.vectorProperties._BumpMap}, {TEXT("_ReceiveShadowTexture"), vrmMat.vectorProperties._ReceiveShadowTexture}, {TEXT("_ShadingGradeTexture"), vrmMat.vectorProperties._ShadingGradeTexture}, {TEXT("_RimColor"), vrmMat.vectorProperties._RimColor}, {TEXT("_RimTexture"), vrmMat.vectorProperties._RimTexture}, {TEXT("_SphereAdd"), vrmMat.vectorProperties._SphereAdd}, {TEXT("_EmissionColor"), vrmMat.vectorProperties._EmissionColor}, {TEXT("_EmissionMap"), vrmMat.vectorProperties._EmissionMap}, {TEXT("_OutlineWidthTexture"), vrmMat.vectorProperties._OutlineWidthTexture}, {TEXT("_OutlineColor"), vrmMat.vectorProperties._OutlineColor}, {TEXT("_UvAnimMaskTexture"), vrmMat.vectorProperties._UvAnimMaskTexture}, }; for (auto &t : tableParam) { LocalVectorParameterSet(dm, *(TEXT("mtoon") + t.key), FLinearColor(t.value[0], t.value[1], t.value[2], t.value[3])); } // default for not vrm material if (LocalIsOriginalVrmMaterial() == false) { SetLocalParamsForVector(dm); } } { struct TT { FString key; float& value; }; TT tableParam[] = { {TEXT("_Cutoff"), vrmMat.floatProperties._Cutoff}, {TEXT("_BumpScale"), vrmMat.floatProperties._BumpScale}, {TEXT("_NormalScale"), vrmMat.floatProperties._BumpScale}, // VRM4U Custom {TEXT("_ReceiveShadowRate"), vrmMat.floatProperties._ReceiveShadowRate}, {TEXT("_ShadeShift"), vrmMat.floatProperties._ShadeShift}, {TEXT("_ShadeToony"), vrmMat.floatProperties._ShadeToony}, {TEXT("_LightColorAttenuation"), vrmMat.floatProperties._LightColorAttenuation}, {TEXT("_IndirectLightIntensity"), vrmMat.floatProperties._IndirectLightIntensity}, {TEXT("_RimLightingMix"), vrmMat.floatProperties._RimLightingMix}, {TEXT("_RimFresnelPower"), vrmMat.floatProperties._RimFresnelPower}, {TEXT("_RimLift"), vrmMat.floatProperties._RimLift}, {TEXT("_OutlineWidth"), vrmMat.floatProperties._OutlineWidth}, {TEXT("_OutlineScaledMaxDistance"), vrmMat.floatProperties._OutlineScaledMaxDistance}, {TEXT("_OutlineLightingMix"), vrmMat.floatProperties._OutlineLightingMix}, {TEXT("_UvAnimScrollX"), vrmMat.floatProperties._UvAnimScrollX}, {TEXT("_UvAnimScrollY"), vrmMat.floatProperties._UvAnimScrollY}, {TEXT("_UvAnimRotation"), vrmMat.floatProperties._UvAnimRotation}, {TEXT("_MToonVersion"), vrmMat.floatProperties._MToonVersion}, {TEXT("_DebugMode"), vrmMat.floatProperties._DebugMode}, {TEXT("_BlendMode"), vrmMat.floatProperties._BlendMode}, {TEXT("_OutlineWidthMode"), vrmMat.floatProperties._OutlineWidthMode}, {TEXT("_OutlineColorMode"), vrmMat.floatProperties._OutlineColorMode}, {TEXT("_CullMode"), vrmMat.floatProperties._CullMode}, {TEXT("_OutlineCullMode"), vrmMat.floatProperties._OutlineCullMode}, {TEXT("_SrcBlend"), vrmMat.floatProperties._SrcBlend}, {TEXT("_DstBlend"), vrmMat.floatProperties._DstBlend}, {TEXT("_ZWrite"), vrmMat.floatProperties._ZWrite}, }; for (auto &t : tableParam) { LocalScalarParameterSet(dm, *(TEXT("mtoon") + t.key), t.value); } // default for not vrm material if (LocalIsOriginalVrmMaterial() == false) { SetLocalParamsForScaler(dm); } //if (vrmMat.floatProperties._CullMode == 0.f) { // dm->BasePropertyOverrides.bOverride_TwoSided = true; // dm->BasePropertyOverrides.TwoSided = 1; //} if (vrmMat.floatProperties._Cutoff != 0.f) { dm->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; dm->BasePropertyOverrides.OpacityMaskClipValue = vrmMat.floatProperties._Cutoff; } { switch ((int)(vrmMat.floatProperties._BlendMode)) { case 0: case 1: break; case 2: case 3: //dm->BasePropertyOverrides.bOverride_BlendMode = true; //dm->BasePropertyOverrides.BlendMode = EBlendMode::BLEND_Translucent; break; } } } { struct TT { FString key; int value; }; TT tableParam[] = { {TEXT("mtoon_tex_MainTex"), vrmMat.textureProperties._MainTex}, {TEXT("mtoon_tex_ShadeTexture"), vrmMat.textureProperties._ShadeTexture}, {TEXT("mtoon_tex_Shade"), vrmMat.textureProperties._ShadeTexture}, // vrm1 {TEXT("mtoon_tex_BumpMap"), vrmMat.textureProperties._BumpMap}, {TEXT("mtoon_tex_ReceiveShadowTexture"), vrmMat.textureProperties._ReceiveShadowTexture}, {TEXT("mtoon_tex_ShadingGradeTexture"), vrmMat.textureProperties._ShadingGradeTexture}, {TEXT("mtoon_tex_RimTexture"), vrmMat.textureProperties._RimTexture}, {TEXT("mtoon_tex_RimMultiply"), vrmMat.textureProperties._RimTexture}, // vrm1 {TEXT("mtoon_tex_SphereAdd"), vrmMat.textureProperties._SphereAdd}, {TEXT("mtoon_tex_MatCap"), vrmMat.textureProperties._SphereAdd}, // vrm1 {TEXT("mtoon_tex_EmissionMap"), vrmMat.textureProperties._EmissionMap}, {TEXT("mtoon_tex_Emissive"), vrmMat.textureProperties._EmissionMap}, // vrm1 {TEXT("mtoon_tex_OutlineWidthTexture"), vrmMat.textureProperties._OutlineWidthTexture}, {TEXT("mtoon_tex_OutlineWidthMultiply"), vrmMat.textureProperties._OutlineWidthTexture}, // vrm1 {TEXT("mtoon_tex_UvAnimMaskTexture") , vrmMat.textureProperties._UvAnimMaskTexture}, {TEXT("mtoon_tex_UvAnimationMask"), vrmMat.textureProperties._UvAnimMaskTexture}, // vrm1 }; if (VRMConverter::Options::Get().IsVRM10Model()) { FSoftObjectPath r(TEXT("/VRM4U/MaterialUtil/T_DummyBlack.T_DummyBlack")); UObject* u = r.TryLoad(); if (u) { auto r2 = Cast(u); if (r2) { LocalTextureSet(dm, "mtoon_tex_EmissionMap", r2); } } } // default texture { auto n = TextureTypeToIndex[aiTextureType_DIFFUSE]; if (n >= 0) { LocalTextureSet(dm, TEXT("mtoon_tex_MainTex"), vrmAssetList->Textures[n]); LocalTextureSet(dm, TEXT("gltf_tex_diffuse"), vrmAssetList->Textures[n]); LocalTextureSet(dm, TEXT("mtoon_tex_Shade"), vrmAssetList->Textures[n]); } } // mtoon texture int count = 0; for (auto &t : tableParam) { ++count; if (t.value < 0 || t.value >= vrmAssetList->Textures.Num()) { continue; } LocalTextureSet(dm, *t.key, vrmAssetList->Textures[t.value]); if (count == 1) { // main => shade tex LocalTextureSet(dm, *tableParam[1].key, vrmAssetList->Textures[t.value]); LocalTextureSet(dm, *tableParam[2].key, vrmAssetList->Textures[t.value]); } //FTextureParameterValue *v = new (dm->TextureParameterValues) FTextureParameterValue(); //v->ParameterInfo.Index = INDEX_NONE; //v->ParameterInfo.Name = *t.key; //v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; //v->ParameterValue = vrmAssetList->Textures[t.value]; } // gltf default texture { auto n = TextureTypeToIndex[aiTextureType_NORMALS]; if (0 <= n && n < vrmAssetList->Textures.Num()) { vrmAssetList->Textures[n]->SRGB = false; vrmAssetList->Textures[n]->CompressionSettings = TC_Normalmap; vrmAssetList->Textures[n]->UpdateResource(); LocalTextureSet(dm, TEXT("mtoon_tex_Normal"), vrmAssetList->Textures[n]); } } { auto n = TextureTypeToIndex[aiTextureType_EMISSIVE]; if (0 <= n && n < vrmAssetList->Textures.Num()) { LocalTextureSet(dm, TEXT("mtoon_tex_Emissive"), vrmAssetList->Textures[n]); } } } return true; } UMaterial* CreateDefaultMaterial(UVrmAssetListObject *vrmAssetList) { #if UE_VERSION_OLDER_THAN(4,20,0) UMaterial* UnrealMaterial = VRM4U_NewObject(vrmAssetList->Package, TEXT("M_BaseMaterial"), RF_Standalone | RF_Public); #else UMaterial* UnrealMaterial = nullptr; if (vrmAssetList->Package == GetTransientPackage()) { UnrealMaterial = VRM4U_NewObject(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient, nullptr); } else { UnrealMaterial = VRM4U_NewObject(vrmAssetList->Package, TEXT("M_BaseMaterial"), RF_Standalone | RF_Public, nullptr); } #endif if (UnrealMaterial != NULL) { //UnrealMaterialFinal = UnrealMaterial; // Notify the asset registry //FAssetRegistryModule::AssetCreated(UnrealMaterial); if(true) { bool bNeedsRecompile = true; UnrealMaterial->GetMaterial()->SetMaterialUsage(bNeedsRecompile, MATUSAGE_SkeletalMesh); UnrealMaterial->GetMaterial()->SetMaterialUsage(bNeedsRecompile, MATUSAGE_MorphTargets); } // Set the dirty flag so this package will get saved later //Package->SetDirtyFlag(true); // textures and properties { #if WITH_EDITORONLY_DATA UMaterialExpressionTextureSampleParameter2D* UnrealTextureExpression = NewObject(UnrealMaterial); #if UE_VERSION_OLDER_THAN(5,1,0) UnrealMaterial->Expressions.Add(UnrealTextureExpression); #else UnrealMaterial->GetEditorOnlyData()->ExpressionCollection.Expressions.Add(UnrealTextureExpression); #endif UnrealTextureExpression->SamplerType = SAMPLERTYPE_Color; UnrealTextureExpression->ParameterName = TEXT("gltf_tex_diffuse"); #if UE_VERSION_OLDER_THAN(5,1,0) UnrealMaterial->BaseColor.Expression = UnrealTextureExpression; #else UnrealMaterial->GetEditorOnlyData()->BaseColor.Expression = UnrealTextureExpression; #endif #endif } UnrealMaterial->BlendMode = BLEND_Masked; UnrealMaterial->SetShadingModel(MSM_DefaultLit); } return UnrealMaterial; } bool isSameMaterial(const UMaterialInterface *mi1, const UMaterialInterface *mi2) { const UMaterialInstanceConstant *m1 = Cast(mi1); const UMaterialInstanceConstant *m2 = Cast(mi2); if (m1 == nullptr || m2 == nullptr) { return false; } // tex { if (m1->TextureParameterValues.Num() != m2->TextureParameterValues.Num()) { return false; } for (int i = 0; i < m1->TextureParameterValues.Num(); ++i) { if (m1->TextureParameterValues[i].ParameterValue != m2->TextureParameterValues[i].ParameterValue) { return false; } if (m1->TextureParameterValues[i].ParameterInfo.Name != m2->TextureParameterValues[i].ParameterInfo.Name) { return false; } } } // scalar { if (m1->ScalarParameterValues.Num() != m2->ScalarParameterValues.Num()) { return false; } for (int i = 0; i < m1->ScalarParameterValues.Num(); ++i) { if (m1->ScalarParameterValues[i].ParameterValue != m2->ScalarParameterValues[i].ParameterValue) { return false; } if (m1->ScalarParameterValues[i].ParameterInfo.Name != m2->ScalarParameterValues[i].ParameterInfo.Name) { return false; } } } // vector { if (m1->VectorParameterValues.Num() != m2->VectorParameterValues.Num()) { return false; } for (int i = 0; i < m1->VectorParameterValues.Num(); ++i) { if (m1->VectorParameterValues[i].ParameterValue != m2->VectorParameterValues[i].ParameterValue) { return false; } if (m1->VectorParameterValues[i].ParameterInfo.Name != m2->VectorParameterValues[i].ParameterInfo.Name) { return false; } } } if (m1->BasePropertyOverrides != m2->BasePropertyOverrides) { return false; } #define VRM4U_TMP_COMPARE(a) if (m1->a != m2->a) return false; VRM4U_TMP_COMPARE(OpacityMaskClipValue); VRM4U_TMP_COMPARE(BlendMode); VRM4U_TMP_COMPARE(TwoSided); VRM4U_TMP_COMPARE(DitheredLODTransition); VRM4U_TMP_COMPARE(bCastDynamicShadowAsMasked); #if UE_VERSION_OLDER_THAN(4,23,0) VRM4U_TMP_COMPARE(GetShadingModel()); #else VRM4U_TMP_COMPARE(GetShadingModels()); #endif #undef VRM4U_TMP_COMPARE return true; } }// namespace bool VRMConverter::ConvertTextureAndMaterial(UVrmAssetListObject *vrmAssetList) { if (vrmAssetList == nullptr || aiData == nullptr) { return false; } if (VRMConverter::Options::Get().IsNoMesh()) { return true; } const bool bGenerateMips = VRMConverter::Options::Get().IsMipmapGenerateMode(); // skip for preload // skip vrmAssetList->Textures.Reset(0); vrmAssetList->Materials.Reset(0); vrmAssetList->OutlineMaterials.Reset(0); TArray NormalBoolTable; NormalBoolTable.SetNum(aiData->mNumTextures); TArray MaskBoolTable; MaskBoolTable.SetNum(aiData->mNumTextures); TArray matArray; TArray matFlagTranslucentArray; TArray matFlagTwoSidedArray; TArray matFlagOpaqueArray; TArray textureCompressTypeArray; { const VRM::VRMMetadata *meta = static_cast(aiData->mVRMMeta); if (meta) { for (int i = 0; i < meta->materialNum; ++i) { int t = 0; t = meta->material[i].textureProperties._BumpMap; if (NormalBoolTable.IsValidIndex(t)){ NormalBoolTable[t] = true; } t = meta->material[i].textureProperties._OutlineWidthTexture; if (MaskBoolTable.IsValidIndex(t)){ MaskBoolTable[t] = true; } } } } { if (aiData->HasTextures() && vrmAssetList->Textures.Num() != aiData->mNumTextures) { TArray texArray; texArray.Reserve(aiData->mNumTextures); // Note: PNG format. Other formats are supported for (uint32_t i = 0; i < aiData->mNumTextures; ++i) { auto& t = *aiData->mTextures[i]; int Width = t.mWidth; int Height = t.mHeight; FString baseName = VRMConverter::NormalizeFileName(t.mFilename.C_Str()); if (baseName.Len() == 0) { baseName = TEXT("texture") + FString::FromInt(i); } bool bNormalGreenFlip = NormalBoolTable[i]; if (bNormalGreenFlip) { baseName += TEXT("_N"); } FString name = FString(TEXT("T_")) + baseName; auto* pkg = vrmAssetList->Package; if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) { pkg = VRM4U_CreatePackage(vrmAssetList->Package, *name); } UTexture2D* NewTexture2D = VRMLoaderUtil::CreateTextureFromImage(name, pkg, t.pcData, t.mWidth, bGenerateMips, NormalBoolTable[i], bNormalGreenFlip&&(VRMConverter::IsImportMode()==false)); #if WITH_EDITOR NewTexture2D->DeferCompression = false; #endif bool bIsBC7 = false; // Set options if (NormalBoolTable[i]) { NewTexture2D->SRGB = 0; #if WITH_EDITOR NewTexture2D->CompressionNoAlpha = true; if (VRMConverter::IsImportMode()) { NewTexture2D->bFlipGreenChannel = true; } #endif } else { if (VRMConverter::Options::Get().IsBC7Mode()) { NewTexture2D->CompressionSettings = TC_BC7; bIsBC7 = true; } } if (MaskBoolTable[i]) { // comment for material warning... //NewTexture2D->SRGB = 0; } if (bIsBC7) { textureCompressTypeArray.Add(EVRMImportTextureCompressType::VRMITC_BC7); } else { textureCompressTypeArray.Add(EVRMImportTextureCompressType::VRMITC_DXT1); } NewTexture2D->UpdateResource(); #if WITH_EDITOR NewTexture2D->PostEditChange(); #endif if (NormalBoolTable[i]) { // UE5.5でクラッシュするので update後に再度更新 NewTexture2D->CompressionSettings = TC_Normalmap; NewTexture2D->UpdateResource(); #if WITH_EDITOR NewTexture2D->PostEditChange(); #endif } texArray.Push(NewTexture2D); } vrmAssetList->Textures = texArray; // small thumbnail { createSmallThumbnail(vrmAssetList, aiData); } } } // texture TArray pmxTexNameList; if (Options::Get().IsPMXModel()) { TArray texArray; int MatNum = aiData->mNumMaterials; vrmAssetList->Materials.SetNum(MatNum); for (int32_t iMat = 0; iMat < MatNum; ++iMat) { auto& aiMat = *aiData->mMaterials[iMat]; // aiString ais; if (aiMat.Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), ais) != AI_SUCCESS) { continue; } FString TextureFullPath = FPaths::GetPath(vrmAssetList->FileFullPathName) + TEXT("/") + UTF8_TO_TCHAR(ais.C_Str()); FString baseName = FPaths::GetBaseFilename(UTF8_TO_TCHAR(ais.C_Str())); baseName = TEXT("T_") + FPaths::GetBaseFilename(baseName); baseName = NormalizeFileName(baseName); if (pmxTexNameList.Find(baseName) >= 0) { continue; } pmxTexNameList.Push(baseName); TArray RawFileData; if (FFileHelper::LoadFileToArray(RawFileData, *TextureFullPath)){ auto* pkg = vrmAssetList->Package; if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) { pkg = VRM4U_CreatePackage(vrmAssetList->Package, *baseName); } UTexture2D* NewTexture2D = VRMLoaderUtil::CreateTextureFromImage(baseName, pkg, RawFileData.GetData(), RawFileData.Num(), bGenerateMips); texArray.Push(NewTexture2D); } } vrmAssetList->Textures = texArray; } if (aiData->HasMaterials()) { //TArray MatNameList; TMap MatNameList; int MatNum = FMath::Max(1, (int)aiData->mNumMaterials); // lastmaterial = gltf default material if (Options::Get().IsVRMModel()) { MatNum = FMath::Max(1, (int)aiData->mNumMaterials - 1); } vrmAssetList->Materials.SetNum(MatNum); for (int32_t iMat = 0; iMat < MatNum; ++iMat) { auto &aiMat = *aiData->mMaterials[iMat]; UMaterialInterface *baseM = nullptr; UVrmImportMaterialSet *mset = nullptr; bool bMToon = false; { FString ShaderName = aiMat.mShaderName.C_Str(); auto MaterialType = Options::Get().GetMaterialType(); vrmAssetList->ImportMode = MaterialType; // native mtoon model ? LocalSetOriginalVrmMaterial(false); if (ShaderName.Find(TEXT("MToon")) >= 0) { // native LocalSetOriginalVrmMaterial(true); } if (VRMConverter::Options::Get().IsVRM10Model()) { // native LocalSetOriginalVrmMaterial(true); } if (Options::Get().GetMaterialType() == EVRMImportMaterialType::VRMIMT_Auto) { // default unlit MaterialType = EVRMImportMaterialType::VRMIMT_Unlit; if (ShaderName.Find(TEXT("MToon")) >= 0) { MaterialType = EVRMImportMaterialType::VRMIMT_MToonUnlit; } if (Options::Get().IsPMXModel()) { MaterialType = EVRMImportMaterialType::VRMIMT_MToonUnlit; } if (Options::Get().IsVRM10Model()) { MaterialType = EVRMImportMaterialType::VRMIMT_MToonUnlit; } } // select materialset switch (MaterialType) { case EVRMImportMaterialType::VRMIMT_MToon: mset = vrmAssetList->MtoonLitSet; bMToon = true; break; case EVRMImportMaterialType::VRMIMT_MToonUnlit: mset = vrmAssetList->MtoonUnlitSet; bMToon = true; break; case EVRMImportMaterialType::VRMIMT_SSS: mset = vrmAssetList->SSSSet; bMToon = true; // MTOON TRUE! break; case EVRMImportMaterialType::VRMIMT_SSSProfile: mset = vrmAssetList->SSSProfileSet; bMToon = true; // MTOON TRUE! break; case EVRMImportMaterialType::VRMIMT_Unlit: mset = vrmAssetList->UnlitSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_glTF: mset = vrmAssetList->GLTFSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_UEFNUnlit: mset = vrmAssetList->UEFNUnlitSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_UEFNLit: mset = vrmAssetList->UEFNLitSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_UEFNSSSProfile: mset = vrmAssetList->UEFNSSSProfileSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_Custom: mset = vrmAssetList->CustomSet; bMToon = false; break; case EVRMImportMaterialType::VRMIMT_Auto: default: break; } if (mset == nullptr) { continue; } bool bTwoSided = false; bool bTranslucent = false; bool bOpaque = false; { aiString alphaMode; aiReturn result = aiMat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode); FString alpha = alphaMode.C_Str(); if (alpha == TEXT("BLEND")) { // check also _ZWrite bTranslucent = true; } if (alpha == TEXT("MASK")) { } if (alpha == TEXT("OPAQUE")) { bOpaque = true; if (vrmAssetList->MaterialHasAlphaCutoff.IsValidIndex(iMat)) { if (vrmAssetList->MaterialHasAlphaCutoff[iMat]) { // force mask mode bOpaque = false; } } } bool b = false; aiReturn ret = aiMat.Get(AI_MATKEY_TWOSIDED, b); if (ret == AI_SUCCESS) { if (b) bTwoSided = true; } } if (ShaderName.Find(TEXT("MToon")) >= 0) { const VRM::VRMMetadata *meta = static_cast(aiData->mVRMMeta); if (meta) { if ((int)iMat < meta->materialNum) { auto &vrmMat = meta->material[iMat]; if (vrmMat.floatProperties._CullMode == 0.f || vrmMat.floatProperties._CullMode== 1.f) { bTwoSided = true; } if (vrmMat.floatProperties._ZWrite == 1.f) { bTranslucent = false; } } } }else{ if (ShaderName.Find(TEXT("UnlitTexture")) >= 0) { } if (ShaderName.Find(TEXT("UnlitTransparent")) >= 0) { bTranslucent = true; } } if (Options::Get().IsForceOpaque()) { bTranslucent = false; } if (Options::Get().IsForceTwoSided()) { bTwoSided = true; } // material set if (bMToon) { // opaque/translucent, twoside UMaterialInterface *table_param[2][2] = { { mset->Opaque, mset->OpaqueTwoSided, }, { mset->Translucent, mset->TranslucentTwoSided, }, }; int c[2] = { bTranslucent ? 1 : 0, bTwoSided ? 1 : 0, }; baseM = table_param[c[0]][c[1]]; if (matArray.Num() == matFlagTranslucentArray.Num()) { matFlagTranslucentArray.Add(c[0] != 0); matFlagTwoSidedArray.Add(c[1] != 0); matFlagOpaqueArray.Add(bOpaque); } }else{ // not mtoon baseM = mset->Opaque; if (bTranslucent){ baseM = mset->Translucent; } } } if (baseM == nullptr) { continue; } TArray TextureTypeToIndex; { TextureTypeToIndex.SetNum(AI_TEXTURE_TYPE_MAX); for (auto& a : TextureTypeToIndex) { a = -1; } TArray texName; texName.SetNum(AI_TEXTURE_TYPE_MAX); for (uint32_t t = 0; t < AI_TEXTURE_TYPE_MAX; ++t) { uint32_t n = aiMat.GetTextureCount((aiTextureType)t); for (uint32_t y = 0; y < FMath::Min((uint32_t)1, n); ++y) { aiMat.GetTexture((aiTextureType)t, y, &texName[t]); } } for (uint32_t i = 0; i < aiData->mNumTextures; ++i) { for (int32_t t = 0; t < texName.Num(); ++t) { if (aiData->mTextures[i]->mFilename == texName[t]) { TextureTypeToIndex[t] = i; break; } } } } for (uint32_t t = 0; t < AI_TEXTURE_TYPE_MAX; ++t) { aiString path; aiReturn r = aiMat.GetTexture(aiTextureType(t), 0, &path); if (r == AI_SUCCESS) { std::string s = path.C_Str(); s = s.substr(s.find_last_of('*') + 1); TextureTypeToIndex[t] = atoi(s.c_str()); if (Options::Get().IsPMXModel()) { for (int i = 0; i < pmxTexNameList.Num(); ++i) { FString baseName = FPaths::GetBaseFilename(UTF8_TO_TCHAR(path.C_Str())); baseName = TEXT("T_") + FPaths::GetBaseFilename(baseName); baseName = NormalizeFileName(baseName); if (pmxTexNameList[i] == baseName) { TextureTypeToIndex[t] = i; break; } } } } } //UMaterialInstanceDynamic* dm = UMaterialInstanceDynamic::Create(baseM, vrmAssetList, m.GetName().C_Str()); //UMaterialInstanceDynamic* dm = UMaterialInstance::Create(baseM, vrmAssetList, m.GetName().C_Str()); //MaterialInstance->TextureParameterValues //set paramater with Set***ParamaterValue //DynMaterial->SetScalarParameterValue("MyParameter", myFloatValue); //MyComponent1->SetMaterial(0, DynMaterial); //MyComponent2->SetMaterial(0, DynMaterial); // ALL!! no texture material. //if (indexDiffuse >= 0 && indexDiffuse < vrmAssetList->Textures.Num()) { //if (indexDiffuse >= 0) { { UMaterialInstanceConstant* dm = nullptr; { const FString origname = (FString(TEXT("MI_")) + NormalizeFileName(aiMat.GetName().C_Str())); FString name = origname; if (MatNameList.Find(origname)) { name += FString::Printf(TEXT("_%03d"), MatNameList[name]); } MatNameList.FindOrAdd(origname)++; if (vrmAssetList->Package == GetTransientPackage()) { dm = VRM4U_NewObject(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient); } else { TArray ret; GetObjectsWithOuter(vrmAssetList->Package, ret); for (auto *a : ret) { auto s = a->GetName().ToLower(); if(s.IsEmpty()) continue; if (s == name.ToLower()) { //a->ClearFlags(EObjectFlags::RF_Standalone); //a->SetFlags(EObjectFlags::RF_Public | RF_Transient); //a->ConditionalBeginDestroy(); static int ccc = 0; ++ccc; a->Rename(*(FString(TEXT("need_reload_tex_VRM"))+FString::FromInt(ccc)), GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_ForceNoResetLoaders); break; } } dm = VRM4U_NewObject(vrmAssetList->Package, *name, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone); vrmAssetList->MaterialNameOrigToAsset.Add(NormalizeFileName(aiMat.GetName().C_Str()), name); } } LocalMaterialSetParent(dm, baseM); if (dm) { { FVectorParameterValue *v = new (dm->VectorParameterValues) FVectorParameterValue(); v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = TEXT("gltf_basecolor"); v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; aiColor4D col(1.f, 1.f, 1.f, 1.f); aiReturn result = aiMat.Get(AI_MATKEY_BASE_COLOR, col); if (result == 0) { } v->ParameterValue = FLinearColor(col.r, col.g, col.b, col.a); } { float f[2] = { 1,1 }; aiReturn result0 = aiMat.Get(AI_MATKEY_ROUGHNESS_FACTOR, f[0]); aiReturn result1 = aiMat.Get(AI_MATKEY_METALLIC_FACTOR, f[1]); if (result0 == AI_SUCCESS || result1 == AI_SUCCESS) { f[0] = (result0 == AI_SUCCESS) ? f[0] : 1; f[1] = (result1 == AI_SUCCESS) ? f[1] : 1; if (f[0] == 0 && f[1] == 0) { f[0] = f[1] = 1.f; } FVectorParameterValue *v = new (dm->VectorParameterValues) FVectorParameterValue(); v->ParameterInfo.Index = INDEX_NONE; v->ParameterInfo.Name = TEXT("gltf_RM"); v->ParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter;; v->ParameterValue = FLinearColor(f[0], f[1], 0, 0); } } int indexDiffuse = TextureTypeToIndex[aiTextureType::aiTextureType_DIFFUSE]; if (indexDiffuse >= 0 && indexDiffuse < vrmAssetList->Textures.Num()) { LocalTextureSet(dm, TEXT("gltf_tex_diffuse"), vrmAssetList->Textures[indexDiffuse]); { FString str = TEXT("mtoon_tex_ShadeTexture"); bool bFindShadeTex = false; for (auto &t : dm->TextureParameterValues) { if (str.Compare(t.ParameterInfo.Name.ToString(), ESearchCase::IgnoreCase) == 0) { if (t.ParameterValue) { bFindShadeTex = true; if (IsValid(vrmAssetList->Textures[indexDiffuse]) == false) { #if UE_VERSION_OLDER_THAN(5,0,0) UTexture2D *tmp = Cast(t.ParameterValue); #else UTexture2D* tmp = Cast(t.ParameterValue.Get()); #endif if (tmp) { LocalTextureSet(dm, TEXT("gltf_tex_diffuse"), tmp); } } } } } if (bFindShadeTex == false) { LocalTextureSet(dm, *str, vrmAssetList->Textures[indexDiffuse]); } } } else { if (Options::Get().IsDefaultGridTextureMode() == false) { // set white texture for default UTexture2D* tex = LoadObject(nullptr, TEXT("/VRM4U/MaterialUtil/T_DummyWhite.T_DummyWhite")); if (tex) { LocalTextureSet(dm, TEXT("gltf_tex_diffuse"), tex); LocalTextureSet(dm, TEXT("mtoon_tex_ShadeTexture"), tex); LocalTextureSet(dm, TEXT("mtoon_tex_Shade"), tex); } } } if (bMToon == false){ { aiString alphaMode; aiReturn result = aiMat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode); FString alpha = alphaMode.C_Str(); if (alpha == TEXT("BLEND")) { if (Options::Get().IsForceOpaque() == false) { dm->BasePropertyOverrides.bOverride_BlendMode = true;; dm->BasePropertyOverrides.BlendMode = EBlendMode::BLEND_Translucent; } } } } // mtoon if (bMToon || VRMConverter::Options::Get().IsVRM10Model()) { createAndAddMaterial(dm, iMat, vrmAssetList, this, TextureTypeToIndex); if (matFlagOpaqueArray.IsValidIndex(iMat)) { if (matFlagOpaqueArray[iMat]) { LocalScalarParameterSet(dm, TEXT("bOpaque"), 1.f); } } if (vrmAssetList->MaterialHasMToon.IsValidIndex(iMat)) { if (vrmAssetList->MaterialHasMToon[iMat] == false) { for (auto& a : dm->VectorParameterValues) { if (a.ParameterInfo.Name == TEXT("mtoon_Color")) { auto c = a.ParameterValue; LocalVectorParameterSet(dm, TEXT("mtoon_ShadeColor"), c); break; } } } } } else { // gltf texture TArray materialParamName; materialParamName.SetNum(AI_TEXTURE_TYPE_MAX); materialParamName[aiTextureType::aiTextureType_DIFFUSE] = TEXT("gltf_tex_diffuse"); materialParamName[aiTextureType::aiTextureType_NORMALS] = TEXT("gltf_tex_normal"); materialParamName[aiTextureType::aiTextureType_EMISSIVE] = TEXT("gltf_tex_Emission"); materialParamName[aiTextureType::aiTextureType_BASE_COLOR] = TEXT("gltf_tex_diffuse"); materialParamName[aiTextureType::aiTextureType_EMISSION_COLOR] = TEXT("gltf_tex_Emission"); materialParamName[aiTextureType::aiTextureType_METALNESS] = TEXT("gltf_tex_metalness"); materialParamName[aiTextureType::aiTextureType_DIFFUSE_ROUGHNESS] = TEXT("gltf_tex_roughness"); for (uint32_t t = 0; t < AI_TEXTURE_TYPE_MAX; ++t) { if (materialParamName[t] == "") continue; int index = TextureTypeToIndex[t]; if (index < 0) continue; if (vrmAssetList->Textures.IsValidIndex(index) == false) continue; if (IsValid(vrmAssetList->Textures[index]) == false) continue; LocalTextureSet(dm, *(materialParamName[t]), vrmAssetList->Textures[index]); } } LocalMaterialFinishParam(dm); //dm->InitStaticPermutation(); matArray.Add(dm); if (matArray.Num() != matFlagTranslucentArray.Num()) { matFlagTranslucentArray.SetNumZeroed(matArray.Num()); matFlagTwoSidedArray.SetNumZeroed(matArray.Num()); matFlagOpaqueArray.SetNumZeroed(matArray.Num()); } TArray t; if (vrmAssetList->MaterialNameOrigToAsset.Num()) { vrmAssetList->MaterialNameOrigToAsset.GenerateValueArray(t); vrmAssetList->MaterialNameAssetToMatNo.Add(t[t.Num() - 1], matArray.Num() - 1); } } } } if (VRMConverter::Options::Get().IsBC7Mode()) { vrmAssetList->Texture_CompressType = EVRMImportTextureCompressType::VRMITC_BC7; } vrmAssetList->Texture_CompressTypeList = textureCompressTypeArray; if (Options::Get().IsMergeMaterial() == false) { vrmAssetList->Materials = matArray; vrmAssetList->MaterialFlag_Translucent = matFlagTranslucentArray; vrmAssetList->MaterialFlag_TwoSided = matFlagTwoSidedArray; vrmAssetList->MaterialFlag_Opaque = matFlagOpaqueArray; } else { TArray tmp; TArray tmpTranslucent; TArray tmpTwoSided; TArray tmpOpaque; vrmAssetList->MaterialMergeTable.Reset(); for (int i = 0; i < matArray.Num(); ++i) { vrmAssetList->MaterialMergeTable.Add(i, 0); bool bFind = false; for (int j = 0; j < tmp.Num(); ++j) { if (isSameMaterial(matArray[i], tmp[j]) == false) { continue; } bFind = true; vrmAssetList->MaterialMergeTable[i] = j; matArray[i]->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_ForceNoResetLoaders); break; } if (bFind == false) { int t = tmp.Add(matArray[i]); vrmAssetList->MaterialMergeTable[i] = t; tmpTranslucent.Add(matFlagTranslucentArray[i]); tmpTwoSided.Add(matFlagTwoSidedArray[i]); tmpOpaque.Add(matFlagOpaqueArray[i]); } } vrmAssetList->Materials = tmp; vrmAssetList->MaterialFlag_Translucent = tmpTranslucent; vrmAssetList->MaterialFlag_TwoSided = tmpTwoSided; vrmAssetList->MaterialFlag_Opaque = tmpOpaque; for (auto& a : vrmAssetList->MaterialNameAssetToMatNo) { a.Value = vrmAssetList->MaterialMergeTable[a.Value]; } } // ouline Material if (VRMConverter::Options::Get().IsGenerateOutlineMaterial()) { if (vrmAssetList->OptMToonOutlineMaterial){ for (const auto aa : vrmAssetList->Materials) { const UMaterialInstanceConstant *a = Cast(aa); FString s = a->GetName() + TEXT("_outline"); //UMaterialInstanceConstant *m = Cast(StaticDuplicateObject(a->GetOuter(), a, *s, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, UMaterialInstanceConstant::StaticClass())); UMaterialInstanceConstant *m = VRM4U_NewObject(vrmAssetList->Package, *s, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone); if (m) { LocalMaterialSetParent(m, vrmAssetList->OptMToonOutlineMaterial); m->VectorParameterValues = a->VectorParameterValues; m->ScalarParameterValues = a->ScalarParameterValues; m->TextureParameterValues = a->TextureParameterValues; LocalMaterialFinishParam(m); //m->InitStaticPermutation(); vrmAssetList->OutlineMaterials.Add(m); } } } } { UPackage* package = GetTransientPackage(); if (vrmAssetList) { package = vrmAssetList->Package; } auto sp = VRM4U_NewObject(package, *(FString(TEXT("SP_")) + vrmAssetList->BaseFileName), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone); #if UE_VERSION_OLDER_THAN(5,0,0) sp->Settings.ScatterRadius = 50.f; #else // default #endif #if WITH_EDITORONLY_DATA vrmAssetList->SSSProfile = sp; #endif for (auto& a : vrmAssetList->Materials) { a->SubsurfaceProfile = sp; auto* c = Cast(a); if (c) { c->bOverrideSubsurfaceProfile = true; } } } } return true; } VrmConvertTexture::VrmConvertTexture() { } VrmConvertTexture::~VrmConvertTexture() { }