[UE] Texture로 Material 생성하기

언리얼 엔진에서는 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();
- UFUNCTION(BlueprintCallable)
실행화면


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") };
- UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Supported Texture Names")
실행화면

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)
실행화면

'⭐ Unreal Engine > UE Plugin - Custom Editor Tool' 카테고리의 다른 글
[UE] Actor 복제 및 SRT 변경하는 Tool 만들기 (0) | 2023.12.11 |
---|---|
[UE] Content Browser Extender Menu에 아이콘 등록하기 (0) | 2023.12.07 |
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기 (0) | 2023.12.01 |
[UE] Slate UI 1: Tab 띄우기 (0) | 2023.12.01 |
[UE] ContentBrowser Menu: 사용하지 않는 에셋/빈 폴더 제거 (0) | 2023.11.25 |
댓글
이 글 공유하기
다른 글
-
[UE] Actor 복제 및 SRT 변경하는 Tool 만들기
[UE] Actor 복제 및 SRT 변경하는 Tool 만들기
2023.12.11언리얼 엔진 에디터에서 Actor를 배치하거나 위치를 조작하는 경우는 흔하다. 예를 들어, 맵에 몬스터 100마리를 배치한다고 가정했을때 몬스터 한마리 한마리 배치하기 시작하면 시간이 너무 많이 소요된다. 이 때, 몬스터 100마리를 각각 X, Y좌표로 1000 간격으로 배치하고 바라보는 방향으로 랜덤으로 돌리면 빠르게 맵에 배치할 수 있고 테스트하기도 좋다. 이런 기능을 담당하는 툴을 만들었다. 목차 Actor SRT Tool 만들기 UEditorSubsystem 기능을 사용할 것이다 UEditorActorSubsystem TArray GetSelectedLevelActors() TArray GetAllLevelActors() QuickActorActionWidget 생성 새 C++ 클래스 - Edit… -
[UE] Content Browser Extender Menu에 아이콘 등록하기
[UE] Content Browser Extender Menu에 아이콘 등록하기
2023.12.07Content Browser Extender Menu에 아이콘 등록하기 아이콘 등록하기 구현방법 1. Custom FSlateStyle Set에서 할 일 Custom FSlateStyle Set으로 사용할 SWManageStyle 클래스를 만들고 아래 작업들을 수행한다. Style set 이름 설정하기 static FName StyleSetName; Custom Style set 생성하기: static TSharedRef CreateSlateStyleSet(); 파일 경로를 가져와서 Icon 이미지를 로드 TSharedRef CustomStyleSet = MakeShareable(new FSlateStyleSet(StyleSetName)); const FString IconDirectory = IPlug… -
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
2023.12.01Content Menu Extender에 제거 마법사를 만들어 선택한 폴더에 들어있는 에셋들을 한 눈에 볼 수 있도록 만들것이다. 이번시간에는 Slate UI 내에 에셋리스트를 띄울 것이다. 다음 시간에는 띄운 에셋 리스트를 삭제할 수 있는 기능을 추가할 것이다. 목차 Widget을 생성하여 탭에 텍스트 띄우기 AdvanceDeletionWidget 생성 AdvanceDeletionWidget.h 더보기 #pragma once #include "Widgets/SCompoundWidget.h" /** Slate Widget을 위해 빈 클래스가 필요 * */ class SAdvanceDeletionTab : public SCompoundWidget { SLATE_BEGIN_ARGS(SAdvanceDeleti… -
[UE] Slate UI 1: Tab 띄우기
[UE] Slate UI 1: Tab 띄우기
2023.12.01Slate UI를 사용하여 탭을 생성하고 폴더 내에 있는 에셋 목록을 한번에 보여주게 만들 것이다. 에셋 목록을 조작하여 필요시 선택한 에셋들을 지우는 기능을 추가할 것이다. 일단, 탭을 생성하고 띄워주는 것부터 하겠다. 목차 Smart Pointer Smart Pointer 스마트 포인터 객체를 소유 삭제 예방 Unique Property TSharedPtr O O Reference Counting 메서드 사용 NULL을 가리킬 수 있음 TSharedRef O O NULL을 가리킬 수 없음 무조건유효한 객체를 가르켜야 함 TWeakPtr X X Reference cycle을 부순다 TSharedRef타입의 함수는 return 값이 무조건 NULL이 아닌 유효한 값이다. 이 점을 알고 있으면 코드 파악이…
댓글을 사용할 수 없습니다.