[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 -
[UE] Content Browser Extender Menu에 아이콘 등록하기
[UE] Content Browser Extender Menu에 아이콘 등록하기
2023.12.07 -
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
2023.12.01 -
[UE] Slate UI 1: Tab 띄우기
[UE] Slate UI 1: Tab 띄우기
2023.12.01