모듈 기능 구현 모듈 클래스에는 플러그인 기능을 구현하는 C++ 코드가 포함되어 있다. 모듈 클래스에서 필요한 함수를 구현하고 기능을 구현한다. Unreal Engine은 많은 기능을 제공하며, 개발자는 이러한 기능을 사용하여 모듈을 구현할 수 있다.

 

 

 


 

 

 
Plugins
  Example
    Example.Build.cs
ExampleConsoleCommand.h .cpp 
ExampleDebuggerCategory.h .cpp
ExampleModule.h .cpp
StaticMesh_Detail.h .cpp
Source
    Utilities
    CHelper.h
CLog.h .cpp
    Global.h
CStaticMesh.h .cpp
.Build.cs
    .uproject
 

 

 

 

 

 

StaticMesh_Detail를 활용한 LOD 구현

 

 


 

 

LOD (Level Of Detail)

 

LOD

  • LOD는 Tessilation을 활용한다.
  • 멀리 있으면 메쉬(와 Vertex)를 감소시키고 가까우면 메쉬(와 Vertex)를 증가 시킨다.

 

Tessilation 예시

 

Tessilation은 DirectX11에 등장하였다.

 

Microsoft 공식문서

https://learn.microsoft.com/ko-kr/windows/win32/direct3d11/direct3d-11-advanced-stages-tessellation

 

테셀레이션 단계 - Win32 apps

Direct3D 11 런타임은 낮은 세부 세분화 표면을 GPU의 상세 기본 형식으로 변환하는 테셀레이션을 구현하는 세 가지 새로운 단계를 지원합니다.

learn.microsoft.com

 


 

 

 

RenderData를 활용한 LOD 사용

 

RenderData

 

LOD0

LOD1

LOD2

LOD3

 

 

교육용

Struct Vertex
{
Vector3 Position;
Vector3 Normal;
Vector3 Uv;
}

 

실무용

  RenderData[0] RenderData[1] RenderData[2] RenderData[3] ...
Position          
Normal          
UV          
...          
    Control Point(=RenderData[ ])로 Position, Normal, Uv 등을 묶어서 사용.

 

RenderData[ ]에서 LOD 정보를 꺼내오고 그 안에 Buffer들에 접근한다.

 

예시

 


 

 

StaticMesh_Detail

 

StaticMesh_Detail.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "IDetailCustomization.h"
//UObject로부터 상속받은것이 아니기 때문에 직접 직렬화를 해줘야 한다.
struct FStaticMesh_DetailData
{
TArray<FVector> Positions;
TArray<FVector> Normals;
TArray<FColor> Colors;
TArray<FVector2D> Uvs;
TArray<int32> Indices;
FVector Extent; //부피
float Radius; //반지름이지만 지름으로 사용.
friend FArchive& operator<<(FArchive& InArchive, FStaticMesh_DetailData& InValue)
{
return InArchive
<< InValue.Positions
<< InValue.Normals
<< InValue.Uvs
<< InValue.Colors
<< InValue.Indices
<< InValue.Extent
<< InValue.Radius;
}
};
class EXAMPLE_API FStaticMesh_Detail : public IDetailCustomization //IDetailCustomization로부터 상속받는다.
{
public:
static TSharedRef<IDetailCustomization> MakeInstance();
public:
void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
private:
FReply OnClicked_Paint();
FReply OnClicked_SaveMesh();
private:
TArray<TWeakObjectPtr<UObject>> Objects;
};

구조체 추가

  • struct FStaticMesh_DetailData

 

 

 

StaticMesh_Detail.cpp

더보기
#include "StaticMesh_Detail.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"
#include "Engine/StaticMeshSocket.h"
#include "Misc/MessageDialog.h"
#include "Serialization/BufferArchive.h"
#include "U2212_05/CStaticMesh.h"
TSharedRef<IDetailCustomization> FStaticMesh_Detail::MakeInstance()
{
return MakeShareable(new FStaticMesh_Detail());
}
void FStaticMesh_Detail::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
IDetailCategoryBuilder& actor = DetailBuilder.EditCategory("Actor");
//actor.SetCategoryVisibility(false); //actor 카테고리를 숨긴다.
TArray<TSharedRef<IPropertyHandle>> handles;
actor.GetDefaultProperties(handles);
for(TSharedRef<IPropertyHandle> handle : handles)
{
// GLog->Log(handle->GetProperty()->GetName());
// GLog->Log(handle->GetProperty()->GetPathName());
// GLog->Log(handle->GetProperty()->GetFullName());
if (handle->GetProperty()->GetName().Compare("bCanBeDamaged") == 0)
DetailBuilder.HideProperty(handle); //해당 조건이면 handle를 숨겨준다.(=actor에 있는 bCanBeDamaged를 숨겨준다.
}
IDetailCategoryBuilder& mesh = DetailBuilder.EditCategory("Mesh");
mesh.AddCustomRow(FText::FromString("Mesh"))
.NameContent()
[
SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다.
.Text(FText::FromString("Color"))
]
.ValueContent()
[
SNew(SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Fill)
.OnClicked(this, &FStaticMesh_Detail::OnClicked_Paint)
//.Content()
[
SNew(STextBlock)
.Text(FText::FromString("Paint"))
]
];
mesh.AddCustomRow(FText::FromString("SaveMesh"))
.NameContent()
[
SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다.
.Text(FText::FromString("Mesh"))
]
.ValueContent()
[
SNew(SButton)
.VAlign(VAlign_Center)
.HAlign(HAlign_Fill)
.OnClicked(this, &FStaticMesh_Detail::OnClicked_SaveMesh)
//.Content()
[
SNew(STextBlock)
.Text(FText::FromString("SaveMesh"))
]
];
DetailBuilder.GetObjectsBeingCustomized(Objects);//TWeakObjectPtr의 Objcts를 사용하니 가비지 콜렉터에 의해 관리될 것이다.
}
FReply FStaticMesh_Detail::OnClicked_Paint()
{
for(TWeakObjectPtr<UObject> obj : Objects)
{
ACStaticMesh* mesh = Cast<ACStaticMesh>(obj); //CStaticMesh 캐스팅한다.
if (!!mesh) //mesh가 있다면
mesh->Paint(); //mesh에 Paint()를 사용하여 색을 적용.
}
return FReply::Handled();
}
FReply FStaticMesh_Detail::OnClicked_SaveMesh()
{
if(Objects[0]->GetWorld()->IsPlayInEditor()) //Objecs[0]이 Editor상에서 플레이 되었다면
{
FMessageDialog dialog;
dialog.Debugf(FText::FromString("In GameMode, it is not working."));
return FReply::Unhandled();//처리하지 못했기 때문에 다음으로 넘겨줘야 한다. 그래서 Unhandled()로 return
}
TArray<ACStaticMesh*> meshActors;
for (TWeakObjectPtr<UObject> obj : Objects)
{
ACStaticMesh* mesh = Cast<ACStaticMesh>(obj);
if (mesh == nullptr) continue; //캐스팅 실패 시에 저장할 자료형이 아니므로 continue로 패스
meshActors.Add(mesh);//캐스팅 성공 시에 mesh 추가
}
for(int32 i=0; i< meshActors.Num(); i++)
{
UActorComponent* actorComponent = meshActors[i]->GetComponentByClass(UStaticMeshComponent::StaticClass());//Component의 최상위인 UActorComponent로 리턴
UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(actorComponent);//UStaticMeshComponent로 사용하기 위해 actorComponent 캐스팅
if (MeshComponent == nullptr) return FReply::Unhandled();//meshComponent가 없는 경우, Unhandled()가서 처리하라.
UStaticMesh* mesh = MeshComponent->GetStaticMesh();
if (mesh == nullptr) return FReply::Unhandled(); //mesh가 없는 경우, , Unhandled()가서 처리하라.
FStaticMeshRenderData* renderData = mesh->RenderData.Get(); //renderData는 정점 데이터
if (renderData->LODResources.Num() < 1) return FReply::Unhandled();
FStaticMeshLODResources& lod = renderData->LODResources[0];
FPositionVertexBuffer& vb = lod.VertexBuffers.PositionVertexBuffer;//PositionVertexBuffer 정보
FStaticMeshVertexBuffer& meshVB = lod.VertexBuffers.StaticMeshVertexBuffer;//StatMeshVertexBuffer 정보(Uv, Normal 정보를 포함함)
FColorVertexBuffer& colorVB = lod.VertexBuffers.ColorVertexBuffer;//ColorVertexBuffer 정보
FRawStaticIndexBuffer& ib = lod.IndexBuffer;//IndexBuffer 정보 //인덱스는 그리기 순서. 양수여야 하지만 누락시키는 경우를 위해 음수도 넣어줄 수 있다.
uint32 vertexCount = vb.GetNumVertices();
int32 triangleCount = ib.GetNumIndices() / 3;//인덱스 3개가 모여야 삼각형이 되어 하나의 단위가 된다. 엔진에서는 주로 triangleCount를 사용한다.
GLog->Logf(L"%d Mesh, Vertex Count : %d", i + 1, vertexCount);
GLog->Logf(L"%d Mesh, Triangle Count : %d", i + 1, triangleCount);
FStaticMesh_DetailData data; //헤더의 구조체 FStaticMesh_DetailData 사용
TArray<FColor> colors;
colorVB.GetVertexColors(colors);
if(colors.Num() < 1)
{
for (uint32 index = 0; index < vertexCount; index++)
colors.Add(FColor(255, 255, 255, 255));
}
FVector minBounds = FVector(+MAX_FLT, +MAX_FLT, +MAX_FLT);//MAX_FLT는 float의 최대값 매크로
FVector maxBounds = FVector(-MAX_FLT, -MAX_FLT, -MAX_FLT);
for (uint32 index = 0; index < vertexCount; index++)
{
FVector position = vb.VertexPosition(index);
//최대 최소를 구한다.
if (position.X < minBounds.X) minBounds.X = position.X;
if (position.Y < minBounds.Y) minBounds.Y = position.Y;
if (position.Z < minBounds.Z) minBounds.Z = position.Z;
if (position.X > maxBounds.X) maxBounds.X = position.X;
if (position.Y > maxBounds.Y) maxBounds.Y = position.Y;
if (position.Z > maxBounds.Z) maxBounds.Z = position.Z;
//정점 정보를 저장한다.
data.Positions.Add(position);//위치 저장.
data.Normals.Add(meshVB.VertexTangentZ(index)); //z값은 수직이기 때문에 사용.
data.Uvs.Add(meshVB.GetVertexUV(index, 0));//Uv 레이어 번호는 0
data.Colors.Add(colors[index]);
}
float x = FMath::Abs(minBounds.X - maxBounds.X);
float y = FMath::Abs(minBounds.Y - maxBounds.Y);
float z = FMath::Abs(minBounds.Z - maxBounds.Z);
data.Extent = FVector(x, y, z); //부피
data.Radius = FMath::Max(x, y);
data.Radius = FMath::Max(data.Radius, z); //가장 큰 값을 지름으로 설정하였다.
GLog->Logf(L"x : %0.1f, y : %0.1f, z : %0.1f", x, y, z);
GLog->Logf(L"Radius : %f", data.Radius);
TArray<uint32> indices;
ib.GetCopy(indices);
//data.Indices.Insert((int32*)indices.GetData(), ) //pointer를 사용하여 Insert
}
return FReply::Handled();
}

추가

  • TArray<AStaticMesh*> meshActors;
  •  

 

 

 

FStaticMesh_DetailData의 정보 사용 예시

 


 

 

실행화면

 

 


 

 

 

 

언리얼 iostream 참고사항

 

 


 

 

언리얼의  iostream 특징 -     <<로 통일하여 사용

 

 

더보기
struct A
{
int a;
float b;
friend istream& operator >>(const istream& stream, A& ob)
{ return ob.a >> ob.b; }
friend ostream& operator >>(const ostream& stream, A& ob)
{ return ob.a << ob.b; }
};
A ob;
cin >> ob; //일반적인 C++
cout << ob; //일반적인 C++
cin << ob; //언리얼. << >>를 통일해서 <<만 사용한다.
cout << ob; //언리얼

 

 

 


 

'⭐ Unreal Engine > UE Plugin - Basic' 카테고리의 다른 글

[UE] Plugin (Button Command)  (0) 2023.04.14
[UE] Plugin (Save StaticMesh .csv)  (0) 2023.04.13
[UE] Plugin (StaticMesh Detail)  (0) 2023.04.07
[UE] StaticMesh  (0) 2023.04.06
[UE] Console Command  (0) 2023.04.05