언리얼 엔진에서는 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)

 

 

실행화면