언리얼 엔진에서는 Material과 Material Instance를 사용해서 Texture들을 묶어서 관리한다. Texture의 종류로는 Metalic, Roughness, Normal, Ambient Occulution, BaseColor 등 다양하다. 이번에는 이런 다양한 Texture들을 선택한 후 버튼 클릭 하나로 Material를 생성하고 Material 내의 Texture들의 노드를 자동으로 이어주도록 할 것이다.  

 

목차

     

     


     

     

    Material 생성 버튼 WBP로 만들기

     


     

     

    UEditorUtilityWidgets를 상속받는 위젯클래스 QuickMaterialCreationWidget

     

    UEditorUtilityWidgets를 상속받는 위젯클래스를 만들어 사용할 것이다  

     

    UEditorUtilityWidgets은..

    • Unreal Motion Graphics(UMG) 기반이다.
    • Editor Tab을 생성하기에 사용자 친화적인 방법이다.
    • 변수에 따라 위젯을 자동으로 생성할 수 있다. ex. 아래의 Custom Details View를 변수와 연동하는 방식
    • 코드에서 작성한 함수를 Blueprint에 노출해서 사용할 수 있다.

     

    QuickMaterialCreationWidget 생성

     

    새 C++ 클래스 - UEditorUtilityWidgets - QuickMaterialCreationWidget 생성

     

     

    QuickMaterialCreationWidget.h

    더보기
    #pragma once
    #include "CoreMinimal.h"
    #include "EditorUtilityWidget.h"
    #include "QuickMaterialCreationWidget.generated.h"
    
    /** Material 관련 EditorUtilityWidget
     * 
     */
    UCLASS()
    class SWMANAGER_API UQuickMaterialCreationWidget : public UEditorUtilityWidget
    {
    	GENERATED_BODY()
    public:
    
    #pragma region QuickMaterialCreationCore
    
    	UFUNCTION(BlueprintCallable)
    	void CreateMaterialFromSelectedTextures();
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	FString MaterialName = TEXT("M_");
    
    #pragma endregion
    };

     

     

     

    QuickMaterialCreationWidget.cpp

    더보기
    #include "AssetActions/QuickMaterialCreationWidget.h"
    #include "DebugHeader.h"
    
    #pragma region QuickMaterialCreationCore
    
    void UQuickMaterialCreationWidget::CreateMaterialFromSelectedTextures()
    {
    	DebugHeader::Print(TEXT("Working"), FColor::Cyan); // 디버깅 메시지 출력
    }
    
    #pragma endregion

     

     

     

    QuickMaterialCreationWidget 클래스 기반의 WBP_QuickMaterialCreation 생성 

     

    DetailsView를 코드의 변수와 연결하기

    • Custom DetailsView 생성 (여기의 경우 QuickMaterialCreationDetailsView)
    • QuickMaterialCreationDetailsView - View - Categories to Show의 +를 눌러 index 생성
      •  index[0]에 코드에서 생성한 변수를 연결
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
        FString MaterialName = TEXT("M_");
    • Graph쪽에서 Event Pre Construct + Set Object로 DetailsView를 등록

     

    버튼 만들기

    • 버튼 생성. CreateMaterialButton
    • On Clicked 이벤트 시, 코드에서 생성한 함수를 실행시키도록 만든다.
      • UFUNCTION(BlueprintCallable)
        void CreateMaterialFromSelectedTextures();

     


     

     

    실행화면

     

     


     

     

     

     

    Material 생성하기 1 ~ 5

     


     

    Material 생성 과정 1 ~ 6

     

    1. Material 이름 체크

    • void CreateMaterialFromSelectedTextures();
      • 이름을 입력하지 않거나 이미 있는 이름을 입력했다면
      • if (MaterialName.IsEmpty() || MaterialName.Equals(TEXT("M_"))) 
        {
        DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("해당 이름을 사용할 수 없습니다."));
        return;
        }

     

    2. 선택한 데이터를 가져옴

    • void CreateMaterialFromSelectedTextures();
      • TArray<FAssetData> SelectedAssetsData = UEditorUtilityLibrary::GetSelectedAssetData();

     

    3. 선택한 데이터 처리

    • 선택된 데이터 처리. Texture면 Material로 변환하고 true 리턴, Texture가 아니면 false 리턴
    • bool ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath);

     

    4. 생성하려는 이름이 사용되고 있는지 체크

    • bool CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck); 

     

    5. Material 생성

    • TObjectPtr<UMaterial> CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial);

     

    6. Node 생성하고 연결하기

    • 선택한 Texture의 접미어를 확인하여 노드를 생성하고 연결

     

     


     

    QuickMaterialCreationWidget

     

    QuickMaterialCreationWidget.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "EditorUtilityWidget.h"
    #include "QuickMaterialCreationWidget.generated.h"
    
    /** EditorUtilityWidget를 상속받는 위젯클래스
     *  선택한 Texture를 사용해서 Material 생성
     *	bCustomMaterailName==true면 이름을 지을 수 있고, false면 Texture이름에 M_ 접두어를 붙인 이름으로 생성 
     */
    UCLASS()
    class SWMANAGER_API UQuickMaterialCreationWidget : public UEditorUtilityWidget
    {
    	GENERATED_BODY()
    public:
    
    #pragma region QuickMaterialCreationCore
    
    	UFUNCTION(BlueprintCallable)
    	void CreateMaterialFromSelectedTextures();
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	bool bCustomMaterialName = true;
    
    	// bCustomMaterialName이 true일때만 MaterialName을 변경할 수 있다.
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures", meta = (EditCondition = "bCustomMaterialName")) 
    	FString MaterialName = TEXT("M_");
    
    #pragma endregion
    
    private:
    #pragma region QuickMaterialCreation
    
    	bool ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath);
    	bool CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck); // 생성하려는 이름이 이미 있는지 확인
    	TObjectPtr<UMaterial> CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial); // Material 생성
    
    #pragma endregion
    
    };

     

     

     

    QuickMaterialCreationWidget.cpp

    더보기
    #include "AssetActions/QuickMaterialCreationWidget.h"
    #include "DebugHeader.h"
    #include "EditorUtilityLibrary.h"
    #include "EditorAssetLibrary.h"
    #include "AssetToolsModule.h"
    #include "Factories/MaterialFactoryNew.h"
    
    #pragma region QuickMaterialCreationCore
    
    void UQuickMaterialCreationWidget::CreateMaterialFromSelectedTextures()
    {
    	if (bCustomMaterialName)
    	{
    		// 이름을 입력하지 않거나 이미 있는 이름을 입력했다면
    		if (MaterialName.IsEmpty() || MaterialName.Equals(TEXT("M_"))) 
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("해당 이름을 사용할 수 없습니다."));
    			return;
    		}
    
    		TArray<FAssetData> SelectedAssetsData = UEditorUtilityLibrary::GetSelectedAssetData();
    		TArray<UTexture2D*> SelectedTexturesArray;
    		FString SelectedTextureFolderPath;
    
    		// 선택한 데이터를 처리
    		if (false == ProcessSelectedData(SelectedAssetsData, SelectedTexturesArray, SelectedTextureFolderPath)) return;
    
    		// 생성하려는 이름(=MaterialName)이 이미 있는지 확인하고 이미 있다면 return
    		if (CheckIsNameUsed(SelectedTextureFolderPath, MaterialName)) return;
    
    		TWeakObjectPtr<UMaterial> CreatedMaterial = CreateMaterialAsset(MaterialName, SelectedTextureFolderPath); // Material 생성 후 CreatedMaterial변수에 담음
    
    		if (false == CreatedMaterial.IsValid()) // Material 생성에 실패했다면
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("Material 생성에 실패했습니다."));
    			return;
    		}
    	}
    }
    
    // 선택된 데이터 처리. Texture면 Material로 변환하고 true 리턴, Texture가 아니면 false 리턴
    bool UQuickMaterialCreationWidget::ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath)
    {
    	if (SelectedDataToProccess.Num() == 0)
    	{
    		DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("선택된 Texture가 없습니다."));
    		return false;
    	}
    
    	bool bMaterialNameSet = false;
    
    	for (const FAssetData& SelectedData : SelectedDataToProccess)
    	{
    		TObjectPtr<UObject> SelectedAsset = SelectedData.GetAsset();
    
    		if (false == IsValid(SelectedAsset)) continue; // 선택된 에셋이 없다면 continue
    
    		TObjectPtr<UTexture2D> SelectedTexture = Cast<UTexture2D>(SelectedAsset); // 선택된 에셋을 Texture 캐스팅
    		
    		if (false == IsValid(SelectedTexture)) // 선택된 에셋이 Texture타입이 아니라면
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("한 가지 Texture만 선택하십시오.\n") +
    				SelectedAsset->GetName() + TEXT(" 은 Texture가 아닙니다."));
    
    			return false;
    		}
    
    		OutSelectedTexturesArray.Add(SelectedTexture); // 선택된 에셋이 Texture라면 OutSelectedTexturesArray 담는다
    
    		if (OutSelectedTexturePackagePath.IsEmpty())
    		{
    			OutSelectedTexturePackagePath = SelectedData.PackagePath.ToString(); // 선택된 에셋(=Texture)의 파일 경로를 담는다.
    		}
    
    		if (false == bCustomMaterialName && false == bMaterialNameSet)
    		{
    			MaterialName = SelectedAsset->GetName(); // 이름을 가져와서 담음
    			MaterialName.RemoveFromStart(TEXT("T_")); // 접두어 T_ 지움
    			MaterialName.InsertAt(0, TEXT("M_")); // 접두어 M_ 붙임
    
    			bMaterialNameSet = true;
    		}
    	}
    
    	return true;
    }
    
    bool UQuickMaterialCreationWidget::CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck) // 생성하려는 이름이 이미 있는지 확인
    {
    	TArray<FString> ExistingAssetsPaths = UEditorAssetLibrary::ListAssets(FolderPathToCheck, false); // 에셋들의 파일경로를 담음
    
    	for (const FString& ExistingAssetPath : ExistingAssetsPaths)
    	{
    		const FString ExistingAssetName = FPaths::GetBaseFilename(ExistingAssetPath); // 파일경로에 있는 에셋 이름을 담음 
    
    		// ExistingAssetName이 새로 생성하려는 이름(=MaterialNameToCheck)과 같다면 메시지를 띄우고 true 리턴 종료
    		if (ExistingAssetName.Equals(MaterialNameToCheck)) 
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, MaterialNameToCheck +
    				TEXT(" 는 이미 사용중인 이름입니다."));
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    TObjectPtr<UMaterial> UQuickMaterialCreationWidget::CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial) // Material 생성
    {
    	FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools")); // FAssetToolsModule를 사용하려면 #include "AssetToolsModule.h" 필요
    
    	TObjectPtr<UMaterialFactoryNew> MaterialFactory = NewObject<UMaterialFactoryNew>(); // UMaterialFactoryNew를 사용하려면 #include "Factories/MaterialFactoryNew.h" 필요
    
    	TObjectPtr<UObject> CreatedObject = AssetToolsModule.Get().CreateAsset(NameOfTheMaterial, PathToPutMaterial, UMaterial::StaticClass(), MaterialFactory); // NameOfTheMaterial이름으로 에셋 생성
    
    	return Cast<UMaterial>(CreatedObject); // 생성한 에셋을 UMaterial타입으로 캐스팅하여 UMaterial타입으로 return
    }
    
    #pragma endregion

    헤더 추가

    • #include "AssetToolsModule.h"
    • #include "Factories/MaterialFactoryNew.h"

     

     

    ※ 참고: IAssetTools.h에 정의된 virtual UObject* CreateAsset() 함수

    FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools")); 
    TObjectPtr<UObject> CreatedObject AssetToolsModule.Get().CreateAsset(NameOfTheMaterial, PathToPutMaterial, UMaterial::StaticClass(), MaterialFactory);

     

     

     

     

     

    Material 생성하기 6


     

     

    Material 생성 과정 6

     

    6. Node 생성하고 연결하기

    • 선택한 Texture의 접미어를 확인하여 노드를 생성하고 연결

     

     

    QuickMaterialCreationWidget

     

    QuickMaterialCreationWidget.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "EditorUtilityWidget.h"
    #include "QuickMaterialCreationWidget.generated.h"
    
    /** EditorUtilityWidget
     *  선택한 Texture를 사용해서 Material 생성
     *	bCustomMaterailName==true면 이름을 지을 수 있고, false면 Texture이름에 M_ 접두어를 붙인 이름으로 생성 
     */
    
    UENUM(BlueprintType)
    enum class E_ChannelPackingType : uint8
    {
    	ECPT_NoChannelPacking UMETA(DisplayName = "No Channel Packing"),
    	ECPT_ORM UMETA(DisplayName = "OcclusionRoughnessMetallic"),
    	ECPT_MAX UMETA(DisplayName = "DefaultMAX")
    };
    
    UCLASS()
    class SWMANAGER_API UQuickMaterialCreationWidget : public UEditorUtilityWidget
    {
    	GENERATED_BODY()
    public:
    
    #pragma region QuickMaterialCreationCore
    
    	UFUNCTION(BlueprintCallable) // WBP_QuickMaterialCreation에 노출됨
    	void CreateMaterialFromSelectedTextures(); // 선택된 Texture로 Material 생성
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	E_ChannelPackingType ChannelPackingType = E_ChannelPackingType::ECPT_NoChannelPacking;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	bool bCustomMaterialName = true;
    
    	// bCustomMaterialName이 true일때만 MaterialName을 변경할 수 있다.
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures", meta = (EditCondition = "bCustomMaterialName")) 
    	FString MaterialName = TEXT("M_");
    
    #pragma endregion
    
    #pragma region SupportedTextureNames
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> BaseColorArray = {
    		TEXT("_BaseColor"),
    		TEXT("_Albedo"),
    		TEXT("_Diffuse"),
    		TEXT("_diff")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> MetallicArray = {
    		TEXT("_Metallic"),
    		TEXT("_metal")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> RoughnessArray = {
    		TEXT("_Roughness"),
    		TEXT("_RoughnessMap"),
    		TEXT("_rough")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> NormalArray = {
    		TEXT("_Normal"),
    		TEXT("_NormalMap"),
    		TEXT("_nor")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> AmbientOcclusionArray = {
    		TEXT("_AmbientOcclusion"),
    		TEXT("_AmbientOcclusionMap"),
    		TEXT("_AO")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> ORMArray = {
    		TEXT("_arm"),
    		TEXT("_OcclusionRoughnessMetallic"),
    		TEXT("_ORM")
    	};
    
    #pragma endregion
    
    private:
    #pragma region QuickMaterialCreation
    
    	bool ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath);
    	bool CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck); // 생성하려는 이름이 이미 있는지 확인
    	TObjectPtr<UMaterial> CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial); // Material 생성
    	void Default_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter); // Material Node 생성
    	void ORM_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter); // Material Node 생성 (Channel Packed Texture인 경우)
    
    #pragma endregion
    
    #pragma region CreateMaterialNodesConnectPins
    
    	bool TryConnectBaseColor(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial); // BaseColor Node 연결이 되어 있으면 false, 안 되어 있으면 연결하고 true 리턴
    	bool TryConnectMetalic(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectRoughness(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectNormal(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectAO(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectORM(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial); // Channel Packed Texture인 경우, AO, Roughness, Metallic을 차례대로 연결해준 후 true 리턴
    
    #pragma endregion
    
    };

     

     

     

    QuickMaterialCreationWidget.cpp

    더보기
    #include "AssetActions/QuickMaterialCreationWidget.h"
    #include "DebugHeader.h"
    #include "EditorUtilityLibrary.h"
    #include "EditorAssetLibrary.h"
    #include "AssetToolsModule.h"
    #include "Factories/MaterialFactoryNew.h"
    
    #pragma region QuickMaterialCreationCore
    
    void UQuickMaterialCreationWidget::CreateMaterialFromSelectedTextures()
    {
    	if (bCustomMaterialName)
    	{
    		// 이름을 입력하지 않거나 "M_"만 입력했다면
    		if (MaterialName.IsEmpty() || MaterialName.Equals(TEXT("M_")))
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("해당 이름을 사용할 수 없습니다."));
    			return;
    		}
    	}
    
    	TArray<FAssetData> SelectedAssetsData = UEditorUtilityLibrary::GetSelectedAssetData();
    	TArray<TObjectPtr<UTexture2D>> SelectedTexturesArray;
    	FString SelectedTextureFolderPath;
    	uint32 PinsConnectedCounter = 0;
    
    	// 선택한 데이터를 처리
    	if (false == ProcessSelectedData(SelectedAssetsData, SelectedTexturesArray, SelectedTextureFolderPath)) return;
    
    	// 생성하려는 이름(=MaterialName)이 이미 있는지 확인하고 이미 있다면 return
    	if (CheckIsNameUsed(SelectedTextureFolderPath, MaterialName)) return;
    
    	TObjectPtr<UMaterial> CreatedMaterial = CreateMaterialAsset(MaterialName, SelectedTextureFolderPath); // Material 생성 후 CreatedMaterial변수에 담음
    
    	if (false == IsValid(CreatedMaterial)) // Material 생성에 실패했다면
    	{
    		DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("Material 생성에 실패했습니다."));
    		return;
    	}
    
    	// 선택한 Texture가 해당 Texture에 알맞는 노드 생성 후 루프를 돌며 알맞은 소켓을 찾아 핀 연결
    	for (TObjectPtr<UTexture2D> SelectedTexture : SelectedTexturesArray)
    	{
    		if (false == IsValid(SelectedTexture)) continue;
    		
    		switch (ChannelPackingType)
    		{
    		case E_ChannelPackingType::ECPT_NoChannelPacking:
    			// Texture타입에 알맞는 노드 생성 후 핀 연결
    			Default_CreateMaterialNodes(CreatedMaterial, SelectedTexture, PinsConnectedCounter);
    			break;
    			
    		case E_ChannelPackingType::ECPT_ORM:
    			// 
    			ORM_CreateMaterialNodes(CreatedMaterial, SelectedTexture, PinsConnectedCounter);
    			break;
    
    		case E_ChannelPackingType::ECPT_MAX:
    			break;
    
    		default:
    			break;
    		}
    	}
    
    	if (PinsConnectedCounter > 0)
    	{
    		DebugHeader::ShowNotifyInfo(TEXT("총 ")
    			+ FString::FromInt(PinsConnectedCounter) + (TEXT(" 개 핀 연결 성공")));
    	}
    
    	MaterialName = TEXT("M_");
    }
    
    // 선택된 데이터 처리. Texture면 Material로 변환하고 true 리턴, Texture가 아니면 false 리턴
    bool UQuickMaterialCreationWidget::ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath)
    {
    	if (SelectedDataToProccess.Num() == 0)
    	{
    		DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("선택된 Texture가 없습니다."));
    		return false;
    	}
    
    	bool bMaterialNameSet = false;
    
    	for (const FAssetData& SelectedData : SelectedDataToProccess)
    	{
    		TObjectPtr<UObject> SelectedAsset = SelectedData.GetAsset();
    
    		if (false == IsValid(SelectedAsset)) continue; // 선택된 에셋이 없다면 continue
    
    		TObjectPtr<UTexture2D> SelectedTexture = Cast<UTexture2D>(SelectedAsset); // 선택된 에셋을 Texture 캐스팅
    		
    		if (false == IsValid(SelectedTexture)) // 선택된 에셋이 Texture타입이 아니라면
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("한 가지 Texture만 선택하십시오.\n") +
    				SelectedAsset->GetName() + TEXT(" 은 Texture가 아닙니다."));
    
    			return false;
    		}
    
    		OutSelectedTexturesArray.Add(SelectedTexture); // 선택된 에셋이 Texture라면 OutSelectedTexturesArray 담는다
    
    		if (OutSelectedTexturePackagePath.IsEmpty())
    		{
    			OutSelectedTexturePackagePath = SelectedData.PackagePath.ToString(); // 선택된 에셋(=Texture)의 파일 경로를 담는다.
    		}
    
    		if (false == bCustomMaterialName && false == bMaterialNameSet)
    		{
    			MaterialName = SelectedAsset->GetName(); // 이름을 가져와서 담음
    			MaterialName.RemoveFromStart(TEXT("T_")); // 접두어 T_ 지움
    			MaterialName.InsertAt(0, TEXT("M_")); // 접두어 M_ 붙임
    
    			bMaterialNameSet = true;
    		}
    	}
    
    	return true;
    }
    
    bool UQuickMaterialCreationWidget::CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck) // 생성하려는 이름이 이미 있는지 확인
    {
    	TArray<FString> ExistingAssetsPaths = UEditorAssetLibrary::ListAssets(FolderPathToCheck, false); // 에셋들의 파일경로를 담음
    
    	for (const FString& ExistingAssetPath : ExistingAssetsPaths)
    	{
    		const FString ExistingAssetName = FPaths::GetBaseFilename(ExistingAssetPath); // 파일경로에 있는 에셋 이름을 담음 
    
    		// ExistingAssetName이 새로 생성하려는 이름(=MaterialNameToCheck)과 같다면 메시지를 띄우고 true 리턴 종료
    		if (ExistingAssetName.Equals(MaterialNameToCheck)) 
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, MaterialNameToCheck +
    				TEXT(" 는 이미 사용중인 이름입니다."));
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    TObjectPtr<UMaterial> UQuickMaterialCreationWidget::CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial) // Material 생성
    {
    	FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools")); // FAssetToolsModule를 사용하려면 #include "AssetToolsModule.h" 필요
    
    	TObjectPtr<UMaterialFactoryNew> MaterialFactory = NewObject<UMaterialFactoryNew>(); // UMaterialFactoryNew를 사용하려면 #include "Factories/MaterialFactoryNew.h" 필요
    
    	TObjectPtr<UObject> CreatedObject = AssetToolsModule.Get().CreateAsset(NameOfTheMaterial, PathToPutMaterial, UMaterial::StaticClass(), MaterialFactory); // NameOfTheMaterial이름으로 에셋 생성
    
    	return Cast<UMaterial>(CreatedObject); // 생성한 에셋을 UMaterial타입으로 캐스팅하여 UMaterial타입으로 return
    }
    
    void UQuickMaterialCreationWidget::Default_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter) // Material Node 생성
    {
    	TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode = NewObject<UMaterialExpressionTextureSample>(CreatedMaterial);
    
    	if (false == IsValid(TextureSampleNode)) return;
    
    	if (false == CreatedMaterial->BaseColor.IsConnected()) // BaseColor가 연결X
    	{
    		// SelectedTexture가 BaseColor인지 검사 후 TextureSampleNode 노드 연결
    		if (TryConnectBaseColor(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Metallic.IsConnected()) // Metallic이 연결X 라면
    	{
    		// SelectedTexture가 Metallic인지 검사 후 TextureSampleNode 노드 연결
    		if (TryConnectMetalic(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Roughness.IsConnected()) // Roughness이 연결X
    	{
    		if (TryConnectRoughness(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Normal.IsConnected()) // Normal이 연결X
    	{
    		if (TryConnectNormal(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->AmbientOcclusion.IsConnected()) // AmbientOcclusion이 연결X
    	{
    		if (TryConnectAO(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    
    	DebugHeader::Print(TEXT("Texture 연결 실패 : ") + SelectedTexture->GetName(), FColor::Red);
    }
    
    void UQuickMaterialCreationWidget::ORM_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter)
    {
    	TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode = NewObject<UMaterialExpressionTextureSample>(CreatedMaterial);
    
    	if (false == IsValid(TextureSampleNode)) return;
    
    	if (false == CreatedMaterial->BaseColor.IsConnected()) // BaseColor가 연결X
    	{
    		if (TryConnectBaseColor(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Normal.IsConnected()) // Normal이 연결X
    	{
    		if (TryConnectNormal(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Roughness.IsConnected()) // Roughness이 연결X
    	{
    		if (TryConnectORM(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter += 3;
    			return;
    		}
    	}
    }
    
    #pragma endregion
    
    #pragma region CreateMaterialNodesConnectPins
    
    bool UQuickMaterialCreationWidget::TryConnectBaseColor(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial) 
    {
    	for (const FString& BaseColorName : BaseColorArray)
    	{
    		if (SelectedTexture->GetName().Contains(BaseColorName))
    		{
    			// 노드를 BaseColor 소켓에 핀 연결
    			TextureSampleNode->Texture = SelectedTexture;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->BaseColor.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600; // 위치가 겹치지 않게 위치 이동
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectMetalic(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,
    	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& MetalicName : MetallicArray)
    	{
    		if (SelectedTexture->GetName().Contains(MetalicName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Metallic.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 240;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectRoughness(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& RoughnessName : RoughnessArray)
    	{
    		if (SelectedTexture->GetName().Contains(RoughnessName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Roughness.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 480;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectNormal(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& NormalName : NormalArray)
    	{
    		if (SelectedTexture->GetName().Contains(NormalName))
    		{
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Normal;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Normal.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 720;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectAO(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& AOName : AmbientOcclusionArray)
    	{
    		if (SelectedTexture->GetName().Contains(AOName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->AmbientOcclusion.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 960;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectORM(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& ORM_Name : ORMArray)
    	{
    		if (SelectedTexture->GetName().Contains(ORM_Name))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Masks;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Masks;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->AmbientOcclusion.Connect(1, TextureSampleNode); // AO 연결
    			CreatedMaterial->Roughness.Connect(2, TextureSampleNode); // Roughness 연결
    			CreatedMaterial->Metallic.Connect(3, TextureSampleNode); // Metallic 연결
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600; // x 위치 이동
    			TextureSampleNode->MaterialExpressionEditorY += 960; // y 위치 이동
    
    			return true;
    		}
    	}
    	return false;
    }
    
    #pragma endregion

     

     

     

     

    WBP_QuickMaterialCreationWidget

     

    DetailsView 등록

    • SupportedTextureNamesDetails 생성
    • View - Categories to Show - index 생성 후 Supported Texture Names 연결
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> BaseColorArray = {
        TEXT("_BaseColor"),
        TEXT("_Albedo"),
        TEXT("_Diffuse"),
        TEXT("_diff") };
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> MetallicArray = {
        TEXT("_Metallic"),
        TEXT("_metal") };
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> RoughnessArray = {
        TEXT("_Roughness"),
        TEXT("_RoughnessMap"),
        TEXT("_rough") };
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> NormalArray = {
        TEXT("_Normal"),
        TEXT("_NormalMap"),
        TEXT("_nor") };
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> AmbientOcclusionArray = {
        TEXT("_AmbientOcclusion"),
        TEXT("_AmbientOcclusionMap"),
        TEXT("_AO") };
      • UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
        TArray<FString> ORMArray = {
        TEXT("_arm"),
        TEXT("_OcclusionRoughnessMetallic"),
        TEXT("_ORM") };

     

    실행화면

     


     

     

     

     

    Material Instance 생성하기

     


     

    Material Instance 생성 과정

     

     

     


     

     

    QuickMaterialCreationWidget

     

    QuickMaterialCreationWidget.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "EditorUtilityWidget.h"
    #include "QuickMaterialCreationWidget.generated.h"
    
    /** Material 관련 EditorUtilityWidget
     *  선택한 Texture를 사용해서 Material 생성. Material Instance 생성
     *	bCustomMaterailName==true면 이름을 지을 수 있고, false면 Texture이름에 M_ 접두어를 붙인 이름으로 생성 
     */
    
    UENUM(BlueprintType)
    enum class E_ChannelPackingType : uint8
    {
    	ECPT_NoChannelPacking UMETA(DisplayName = "No Channel Packing"),
    	ECPT_ORM UMETA(DisplayName = "OcclusionRoughnessMetallic"),
    	ECPT_MAX UMETA(DisplayName = "DefaultMAX")
    };
    
    UCLASS()
    class SWMANAGER_API UQuickMaterialCreationWidget : public UEditorUtilityWidget
    {
    	GENERATED_BODY()
    public:
    
    #pragma region QuickMaterialCreationCore
    
    	UFUNCTION(BlueprintCallable) // WBP_QuickMaterialCreation에 노출됨
    	void CreateMaterialFromSelectedTextures(); // 선택된 Texture로 Material 생성
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	E_ChannelPackingType ChannelPackingType = E_ChannelPackingType::ECPT_NoChannelPacking;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	bool bCustomMaterialName = true;
    
    	// bCustomMaterialName이 true일때만 MaterialName을 변경할 수 있다.
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures", meta = (EditCondition = "bCustomMaterialName")) 
    	FString MaterialName = TEXT("M_");
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CreateMaterialFromSelectedTextures")
    	bool bCreateMaterialInstance = false; // 생성하려는 Material Instance 이름이 사용중인지 확인. 이름이 사용가능하면 true, 사용불가능하면 false로 설정 
    
    #pragma endregion
    
    #pragma region SupportedTextureNames
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> BaseColorArray = {
    		TEXT("_BaseColor"),
    		TEXT("_Albedo"),
    		TEXT("_Diffuse"),
    		TEXT("_diff")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> MetallicArray = {
    		TEXT("_Metallic"),
    		TEXT("_metal")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> RoughnessArray = {
    		TEXT("_Roughness"),
    		TEXT("_RoughnessMap"),
    		TEXT("_rough")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> NormalArray = {
    		TEXT("_Normal"),
    		TEXT("_NormalMap"),
    		TEXT("_nor")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> AmbientOcclusionArray = {
    		TEXT("_AmbientOcclusion"),
    		TEXT("_AmbientOcclusionMap"),
    		TEXT("_AO")
    	};
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
    	TArray<FString> ORMArray = {
    		TEXT("_arm"),
    		TEXT("_OcclusionRoughnessMetallic"),
    		TEXT("_ORM")
    	};
    
    #pragma endregion
    
    private:
    #pragma region QuickMaterialCreation
    
    	bool ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath);
    	bool CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck); // 생성하려는 이름이 이미 있는지 확인
    	TObjectPtr<UMaterial> CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial); // Material 생성
    	void Default_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter); // Material Node 생성
    	void ORM_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter); // Material Node 생성 (Channel Packed Texture인 경우)
    
    #pragma endregion
    
    #pragma region CreateMaterialNodesConnectPins
    
    	bool TryConnectBaseColor(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial); // BaseColor Node 연결이 되어 있으면 false, 안 되어 있으면 연결하고 true 리턴
    	bool TryConnectMetalic(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectRoughness(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectNormal(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectAO(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial);
    	bool TryConnectORM(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial); // Channel Packed Texture인 경우, AO, Roughness, Metallic을 차례대로 연결해준 후 true 리턴
    
    #pragma endregion
    
    	TObjectPtr<class UMaterialInstanceConstant> CreateMaterialInstanceAsset(TObjectPtr<UMaterial> CreatedMaterial, FString NameOfMaterialInstance, const FString& PathToPutMI); // Material Instance 생성
    };

     Material Instance 생성하는 함수 추가

    • TObjectPtr<class UMaterialInstanceConstant> CreateMaterialInstanceAsset(TObjectPtr<UMaterial> CreatedMaterial, FString NameOfMaterialInstance, const FString& PathToPutMI);

     

     

     

    QuickMaterialCreationWidget.cpp

    더보기
    #include "AssetActions/QuickMaterialCreationWidget.h"
    #include "DebugHeader.h"
    #include "EditorUtilityLibrary.h"
    #include "EditorAssetLibrary.h"
    #include "AssetToolsModule.h"
    #include "Factories/MaterialFactoryNew.h"
    #include "Materials/MaterialInstanceConstant.h" // UMaterialInstanceConstant 사용 시 필요
    #include "Factories/MaterialInstanceConstantFactoryNew.h" // UMaterialInstanceConstantFactoryNew 사용 시 필요
    
    #pragma region QuickMaterialCreationCore
    
    void UQuickMaterialCreationWidget::CreateMaterialFromSelectedTextures()
    {
    	if (bCustomMaterialName)
    	{
    		// 이름을 입력하지 않거나 "M_"만 입력했다면
    		if (MaterialName.IsEmpty() || MaterialName.Equals(TEXT("M_")))
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("해당 이름을 사용할 수 없습니다."));
    			return;
    		}
    	}
    
    	TArray<FAssetData> SelectedAssetsData = UEditorUtilityLibrary::GetSelectedAssetData();
    	TArray<TObjectPtr<UTexture2D>> SelectedTexturesArray;
    	FString SelectedTextureFolderPath;
    	uint32 PinsConnectedCounter = 0;
    
    	// 선택한 데이터를 처리
    	if (false == ProcessSelectedData(SelectedAssetsData, SelectedTexturesArray, SelectedTextureFolderPath)) { MaterialName = TEXT("M_"); return; }
    
    	// 생성하려는 이름(=MaterialName)이 이미 있는지 확인하고 이미 있다면 return
    	if (CheckIsNameUsed(SelectedTextureFolderPath, MaterialName)) { MaterialName = TEXT("M_"); return; }
    
    	TObjectPtr<UMaterial> CreatedMaterial = CreateMaterialAsset(MaterialName, SelectedTextureFolderPath); // Material 생성 후 CreatedMaterial변수에 담음
    
    	if (false == IsValid(CreatedMaterial)) // Material 생성에 실패했다면
    	{
    		DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("Material 생성에 실패했습니다."));
    		return;
    	}
    
    	// 선택한 Texture가 해당 Texture에 알맞는 노드 생성 후 루프를 돌며 알맞은 소켓을 찾아 핀 연결
    	for (TObjectPtr<UTexture2D> SelectedTexture : SelectedTexturesArray)
    	{
    		if (false == IsValid(SelectedTexture)) continue;
    		
    		switch (ChannelPackingType)
    		{
    		case E_ChannelPackingType::ECPT_NoChannelPacking:
    			// Texture타입에 알맞는 노드 생성 후 핀 연결
    			Default_CreateMaterialNodes(CreatedMaterial, SelectedTexture, PinsConnectedCounter);
    			break;
    			
    		case E_ChannelPackingType::ECPT_ORM:
    			// 
    			ORM_CreateMaterialNodes(CreatedMaterial, SelectedTexture, PinsConnectedCounter);
    			break;
    
    		case E_ChannelPackingType::ECPT_MAX:
    			break;
    
    		default:
    			break;
    		}
    	}
    
    	if (PinsConnectedCounter > 0)
    	{
    		DebugHeader::ShowNotifyInfo(TEXT("총 ")
    			+ FString::FromInt(PinsConnectedCounter) + (TEXT(" 개 핀 연결 성공")));
    	}
    
    	if (bCreateMaterialInstance) // 생성하려는 Material Instance의 이름이 사용가능하면
    	{
    		CreateMaterialInstanceAsset(CreatedMaterial, MaterialName, SelectedTextureFolderPath); // Material Instance 생성
    	}
    
    	MaterialName = TEXT("M_");
    }
    
    // 선택된 데이터 처리. Texture면 Material로 변환하고 true 리턴, Texture가 아니면 false 리턴
    bool UQuickMaterialCreationWidget::ProcessSelectedData(const TArray<FAssetData>& SelectedDataToProccess, TArray<UTexture2D*>& OutSelectedTexturesArray, FString& OutSelectedTexturePackagePath)
    {
    	if (SelectedDataToProccess.Num() == 0)
    	{
    		DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("선택된 Texture가 없습니다."));
    		return false;
    	}
    
    	bool bMaterialNameSet = false;
    
    	for (const FAssetData& SelectedData : SelectedDataToProccess)
    	{
    		TObjectPtr<UObject> SelectedAsset = SelectedData.GetAsset();
    
    		if (false == IsValid(SelectedAsset)) continue; // 선택된 에셋이 없다면 continue
    
    		TObjectPtr<UTexture2D> SelectedTexture = Cast<UTexture2D>(SelectedAsset); // 선택된 에셋을 Texture 캐스팅
    		
    		if (false == IsValid(SelectedTexture)) // 선택된 에셋이 Texture타입이 아니라면
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, TEXT("한 가지 Texture만 선택하십시오.\n") +
    				SelectedAsset->GetName() + TEXT(" 은 Texture가 아닙니다."));
    
    			return false;
    		}
    
    		OutSelectedTexturesArray.Add(SelectedTexture); // 선택된 에셋이 Texture라면 OutSelectedTexturesArray 담는다
    
    		if (OutSelectedTexturePackagePath.IsEmpty())
    		{
    			OutSelectedTexturePackagePath = SelectedData.PackagePath.ToString(); // 선택된 에셋(=Texture)의 파일 경로를 담는다.
    		}
    
    		if (false == bCustomMaterialName && false == bMaterialNameSet)
    		{
    			MaterialName = SelectedAsset->GetName(); // 이름을 가져와서 담음
    			MaterialName.RemoveFromStart(TEXT("T_")); // 접두어 T_ 지움
    			MaterialName.InsertAt(0, TEXT("M_")); // 접두어 M_ 붙임
    
    			bMaterialNameSet = true;
    		}
    	}
    
    	return true;
    }
    
    bool UQuickMaterialCreationWidget::CheckIsNameUsed(const FString& FolderPathToCheck, const FString& MaterialNameToCheck) // 생성하려는 이름이 이미 있는지 확인
    {
    	TArray<FString> ExistingAssetsPaths = UEditorAssetLibrary::ListAssets(FolderPathToCheck, false); // 에셋들의 파일경로를 담음
    
    	for (const FString& ExistingAssetPath : ExistingAssetsPaths)
    	{
    		const FString ExistingAssetName = FPaths::GetBaseFilename(ExistingAssetPath); // 파일경로에 있는 에셋 이름을 담음 
    
    		// ExistingAssetName이 새로 생성하려는 이름(=MaterialNameToCheck)과 같다면 메시지를 띄우고 true 리턴 종료
    		if (ExistingAssetName.Equals(MaterialNameToCheck)) 
    		{
    			DebugHeader::ShowMsgDialog(EAppMsgType::Ok, MaterialNameToCheck +
    				TEXT(" 는 이미 사용중인 이름입니다."));
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    TObjectPtr<UMaterial> UQuickMaterialCreationWidget::CreateMaterialAsset(const FString& NameOfTheMaterial, const FString& PathToPutMaterial) // Material 생성
    {
    	FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools")); // FAssetToolsModule를 사용하려면 #include "AssetToolsModule.h" 필요
    
    	TObjectPtr<UMaterialFactoryNew> MaterialFactory = NewObject<UMaterialFactoryNew>(); // UMaterialFactoryNew를 사용하려면 #include "Factories/MaterialFactoryNew.h" 필요
    
    	TObjectPtr<UObject> CreatedObject = AssetToolsModule.Get().CreateAsset(NameOfTheMaterial, PathToPutMaterial, UMaterial::StaticClass(), MaterialFactory); // NameOfTheMaterial이름으로 에셋 생성
    
    	return Cast<UMaterial>(CreatedObject); // 생성한 에셋을 UMaterial타입으로 캐스팅하여 UMaterial타입으로 return
    }
    
    void UQuickMaterialCreationWidget::Default_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter) // Material Node 생성
    {
    	TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode = NewObject<UMaterialExpressionTextureSample>(CreatedMaterial);
    
    	if (false == IsValid(TextureSampleNode)) return;
    
    	if (false == CreatedMaterial->BaseColor.IsConnected()) // BaseColor가 연결X
    	{
    		// SelectedTexture가 BaseColor인지 검사 후 TextureSampleNode 노드 연결
    		if (TryConnectBaseColor(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Metallic.IsConnected()) // Metallic이 연결X 라면
    	{
    		// SelectedTexture가 Metallic인지 검사 후 TextureSampleNode 노드 연결
    		if (TryConnectMetalic(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Roughness.IsConnected()) // Roughness이 연결X
    	{
    		if (TryConnectRoughness(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Normal.IsConnected()) // Normal이 연결X
    	{
    		if (TryConnectNormal(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->AmbientOcclusion.IsConnected()) // AmbientOcclusion이 연결X
    	{
    		if (TryConnectAO(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    
    	DebugHeader::Print(TEXT("Texture 연결 실패 : ") + SelectedTexture->GetName(), FColor::Red);
    }
    
    void UQuickMaterialCreationWidget::ORM_CreateMaterialNodes(TObjectPtr<UMaterial> CreatedMaterial, TObjectPtr<UTexture2D> SelectedTexture, uint32& PinsConnectedCounter)
    {
    	TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode = NewObject<UMaterialExpressionTextureSample>(CreatedMaterial);
    
    	if (false == IsValid(TextureSampleNode)) return;
    
    	if (false == CreatedMaterial->BaseColor.IsConnected()) // BaseColor가 연결X
    	{
    		if (TryConnectBaseColor(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Normal.IsConnected()) // Normal이 연결X
    	{
    		if (TryConnectNormal(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter++;
    			return;
    		}
    	}
    	if (false == CreatedMaterial->Roughness.IsConnected()) // Roughness이 연결X
    	{
    		if (TryConnectORM(TextureSampleNode, SelectedTexture, CreatedMaterial))
    		{
    			PinsConnectedCounter += 3;
    			return;
    		}
    	}
    }
    
    #pragma endregion
    
    #pragma region CreateMaterialNodesConnectPins
    
    bool UQuickMaterialCreationWidget::TryConnectBaseColor(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial) 
    {
    	for (const FString& BaseColorName : BaseColorArray)
    	{
    		if (SelectedTexture->GetName().Contains(BaseColorName))
    		{
    			// 노드를 BaseColor 소켓에 핀 연결
    			TextureSampleNode->Texture = SelectedTexture;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->BaseColor.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600; // 위치가 겹치지 않게 위치 이동
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectMetalic(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,
    	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& MetalicName : MetallicArray)
    	{
    		if (SelectedTexture->GetName().Contains(MetalicName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Metallic.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 240;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectRoughness(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& RoughnessName : RoughnessArray)
    	{
    		if (SelectedTexture->GetName().Contains(RoughnessName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Roughness.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 480;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectNormal(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& NormalName : NormalArray)
    	{
    		if (SelectedTexture->GetName().Contains(NormalName))
    		{
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Normal;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->Normal.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 720;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectAO(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode,	TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& AOName : AmbientOcclusionArray)
    	{
    		if (SelectedTexture->GetName().Contains(AOName))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_LinearColor;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->AmbientOcclusion.Expression = TextureSampleNode;
    			CreatedMaterial->PostEditChange();
    
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600;
    			TextureSampleNode->MaterialExpressionEditorY += 960;
    
    			return true;
    		}
    	}
    
    	return false;
    }
    
    bool UQuickMaterialCreationWidget::TryConnectORM(TObjectPtr<UMaterialExpressionTextureSample> TextureSampleNode, TObjectPtr<UTexture2D> SelectedTexture, TObjectPtr<UMaterial> CreatedMaterial)
    {
    	for (const FString& ORM_Name : ORMArray)
    	{
    		if (SelectedTexture->GetName().Contains(ORM_Name))
    		{
    			SelectedTexture->CompressionSettings = TextureCompressionSettings::TC_Masks;
    			SelectedTexture->SRGB = false;
    			SelectedTexture->PostEditChange();
    
    			TextureSampleNode->Texture = SelectedTexture;
    			TextureSampleNode->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Masks;
    
    			CreatedMaterial->Expressions.Add(TextureSampleNode);
    			CreatedMaterial->AmbientOcclusion.Connect(1, TextureSampleNode); // AO 연결
    			CreatedMaterial->Roughness.Connect(2, TextureSampleNode); // Roughness 연결
    			CreatedMaterial->Metallic.Connect(3, TextureSampleNode); // Metallic 연결
    			CreatedMaterial->PostEditChange();
    
    			TextureSampleNode->MaterialExpressionEditorX -= 600; // x 위치 이동
    			TextureSampleNode->MaterialExpressionEditorY += 960; // y 위치 이동
    
    			return true;
    		}
    	}
    	return false;
    }
    
    #pragma endregion
    
    TObjectPtr<UMaterialInstanceConstant> UQuickMaterialCreationWidget::CreateMaterialInstanceAsset(TObjectPtr<UMaterial> CreatedMaterial, FString NameOfMaterialInstance, const FString& PathToPutMI) // Material Instance 생성
    {
    	NameOfMaterialInstance.RemoveFromStart(TEXT("M_")); // 접두어 M_ 제거
    	NameOfMaterialInstance.InsertAt(0, TEXT("MI_")); // 접두어 MI_ 삽입
    
    	TObjectPtr<UMaterialInstanceConstantFactoryNew> MIFactoryNew = NewObject<UMaterialInstanceConstantFactoryNew>(); // UMaterialInstanceConstantFactoryNew 사용 시 #include "Factories/MaterialInstanceConstantFactoryNew.h" 필요
    
    	FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
    
    	TObjectPtr<UObject> CreatedObject = AssetToolsModule.Get().CreateAsset(NameOfMaterialInstance, PathToPutMI, UMaterialInstanceConstant::StaticClass(), MIFactoryNew); // NameOfMaterialInstance 이름으로 UObject 생성. UMaterialInstanceConstant 사용하려면 #include "Materials/MaterialInstanceConstant.h" 필요
    
    	// 방금 생성한 CreatedObject를 UMaterialInstanceConstant 타입으로 캐스팅하여 CreatedMI 변수에 담음(=UObject타입을 UMaterialInstanceConstant타입으로 변환)
    	if (TObjectPtr<UMaterialInstanceConstant> CreatedMI = Cast<UMaterialInstanceConstant>(CreatedObject)) // 캐스팅 성공 시
    	{
    		CreatedMI->SetParentEditorOnly(CreatedMaterial); // 생성한 CreatedMI의 부모 Material를 CreatedMaterial로 설정
    
    		CreatedMI->PostEditChange(); // CreatedMI(=Material Instance) 업데이트
    		CreatedMaterial->PostEditChange(); // CreatedMaterial(=Material) 업데이트
    
    		return CreatedMI;
    	}
    
    	return nullptr;
    }

    헤더 추가

    • #include "Materials/MaterialInstanceConstant.h"
      • UMaterialInstanceConstant 사용 시 필요
    • #include "Factories/MaterialInstanceConstantFactoryNew.h"
      • UMaterialInstanceConstantFactoryNew 사용 시 필요

     

    Material Instance 생성하는 함수 정의

    • TObjectPtr<UMaterialInstanceConstant> UQuickMaterialCreationWidget::CreateMaterialInstanceAsset(TObjectPtr<UMaterial> CreatedMaterial, FString NameOfMaterialInstance, const FString& PathToPutMI)

     

     

    실행화면