기본 콘텐츠로 건너뛰기

[Unreal Engine] Texture

    목차

각 상황에 대해 테스트가 필요함


Test version : UE 5.3.2

Texture2D → TArray<FColor>

(런타임에서 잘 실행되지 않는 것 같음...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Header
UFUNCTION(BlueprintCallable)
static bool Texture2d_To_ColorArray(UTexture2D* SrcTexture2D, TArray<FColor>& ResultColorArray,
                                        int32& ResultTextureWidth, int32& ResultTextureHeight);
 
 
// Cpp
bool "클래스 이름"::Texture2d_To_ColorArray(UTexture2D* SrcTexture2D, TArray<FColor>& ResultColorArray,
                                                int32& ResultTextureWidth, int32& ResultTextureHeight)
{
    if (SrcTexture2D == nullptr)
    {
        //(TEXT("Convert texture error : Texture2D is not defined"));
        ResultTextureWidth = 0;
        ResultTextureHeight = 0;
        ResultColorArray.Empty();
        return false;
    }
 
    //Backup texture option
    const TextureCompressionSettings BackUp_CompressionSettings = SrcTexture2D->CompressionSettings;
    const bool BackUp_SRGB = SrcTexture2D->SRGB;
    //const ETextureMipLoadOptions BackUp_MipLoadOptions = SrcTexture2D->MipLoadOptions;
    //const TEnumAsByte BackUp_TextureGroup = SrcTexture2D->LODGroup;
 
#if WITH_EDITOR || WITH_EDITORONLY_DATA
    const TextureMipGenSettings BackUp_MipGenSettings = SrcTexture2D->MipGenSettings;
    SrcTexture2D->MipGenSettings = TMGS_NoMipmaps;
#endif
 
    //Set texture for read pixel
    SrcTexture2D->CompressionSettings = TC_VectorDisplacementmap;
    SrcTexture2D->SRGB = false;
    //SrcTexture2D->MipLoadOptions = ETextureMipLoadOptions::OnlyFirstMip;
    //SrcTexture2D->LODGroup = TEXTUREGROUP_UI;
    SrcTexture2D->UpdateResource();
 
    bool IsRunSuccess;
 
    const FColor* FormattedImageData = static_cast<const FColor*>(SrcTexture2D->GetPlatformData()->Mips[0]
                                                                  .BulkData.Lock(LOCK_READ_ONLY));
 
    if (FormattedImageData != nullptr)
    {
        ResultTextureWidth = SrcTexture2D->GetPlatformData()->SizeX;
        ResultTextureHeight = SrcTexture2D->GetPlatformData()->SizeY;
 
        //(TEXT("Texture width : %d height : %d"), ResultTextureWidth, ResultTextureHeight)
 
        const int ResultTextureLength = ResultTextureWidth * ResultTextureHeight;
        ResultColorArray.AddUninitialized(ResultTextureLength);
        FMemory::Memcpy(ResultColorArray.GetData(), FormattedImageData, sizeof(FColor) * ResultTextureLength);
 
        // for (int32 Y = 0; Y < ResultTextureHeight; Y++)
        // {
        //     for (int32 X = 0; X < ResultTextureWidth; X++)
        //     {
        //         FColor PixelColor = FormattedImageData[Y * ResultTextureWidth + X];
        //         ResultColorArray.Add(PixelColor);
        //     }
        // }
 
        SrcTexture2D->GetPlatformData()->Mips[0].BulkData.Unlock();
        IsRunSuccess = true;
    }
    else
    {
        //(TEXT("Convert texture error : Failed to convert \"BulkData\" to \"FColor\""));
        ResultTextureWidth = 0;
        ResultTextureHeight = 0;
        ResultColorArray.Empty();
        IsRunSuccess = false;
    }
 
    //Rollback texture option    
#if  WITH_EDITOR || WITH_EDITORONLY_DATA
    SrcTexture2D->MipGenSettings = BackUp_MipGenSettings;
#endif
 
    SrcTexture2D->CompressionSettings = BackUp_CompressionSettings;
    SrcTexture2D->SRGB = BackUp_SRGB;
    //SrcTexture2D->MipLoadOptions = BackUp_MipLoadOptions;
    //SrcTexture2D->LODGroup = BackUp_TextureGroup;
    SrcTexture2D->UpdateResource();
 
    return IsRunSuccess;
}
 
cs


주의!

일부 이미지에 대해서 작동하지 않는 경우가 존재하니 테스트 필요.
예를 들면, uasset으로 변환한 Texture2D는 동작하지 않음. 오히려  FImageUtils::ImportFileAsTexture2D 함수를 이용하여 로드한 실제 (Png) 이미지는 작동을 함.


추가 사항

FImageUtils::CreateTexture2D

FImageUtils::CreateTexture2D 함수는 에디터 전용으로 예상됨.
참고자료 링크에 의하면 Texture2D 내부에 MipGenSettings 변수를 건드리는 경우 #WITH_EDITOR 또는 #WITH_EDITORONLY_DATA 매크로로 감싸라고 되어 있다.
FImageUtils::CreateTexture2D 함수 파라미터에 들어가는 FCreateTexture2DParameters 구조체에서 MipGenSettings을 받고 있는 점도 그렇고, 실제로 Shipping으로 빌드를 하여 실행을 하면 "ConstructTexture2D not supported on console" 메시지를 띄우며 Crush가 뜬다.

참고 자료






TArray<FColor> ↔ TArray<uint8>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
//Header
UFUNCTION(BlueprintCallable)
static TArray<FColor> ByteArray_To_Color(UPARAM(ref) TArray<uint8>& SrcByteArray);
 
UFUNCTION(BlueprintCallable)
static TArray<uint8> Color_To_ByteArray(UPARAM(ref) TArray<FColor>& SrcColorArray);
 
//Cpp
TArray<FColor> "클래스 이름"::ByteArray_To_Color(TArray<uint8>& SrcByteArray)
{
    const int32 Length = SrcByteArray.Num();
 
    TArray<FColor> ResultColorArray;
    ResultColorArray.AddUninitialized(Length / 4);
    FMemory::Memcpy(ResultColorArray.GetData(), SrcByteArray.GetData(), Length);
    return ResultColorArray;
}
 
TArray<uint8> "클래스 이름"::Color_To_ByteArray(TArray<FColor>& SrcColorArray)
{
    const int32 Length = SrcColorArray.Num() * 4;
 
    TArray<uint8> ResultByteArray;
    ResultByteArray.AddUninitialized(Length);
    FMemory::Memcpy(ResultByteArray.GetData(), SrcColorArray.GetData(), Length);
    return ResultByteArray;
}
 
cs


주의

※ 언리얼 (blueprint) 에서는 byte array를 TArray<uint8>로 표현한다.

FColor 구조체는 알파를 포함하여 각 색상 요소를 uint8로 표현한다. 따라서 자료 크기는 4byte가 된다 (uint8 * 4, uint32). 리틀 엔디안이면 BGRA, 빅 엔디안이면 ARGB로 표현.
물론 이미지 포맷마다 나타내는 크기는 다름. (예 : FLinearColor의 경우 각 색상 요소를 float로 표현)

텍스쳐 생성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
//Header
UFUNCTION(BlueprintCallable)
static UTexture2D* CreateTexture2D_BpAble(const int32 Width, const int32 Height,
                                              UPARAM(ref) TArray<FColor>& SrcColorArray);
 
 
//Cpp
UTexture2D* "클래스 이름"::CreateTexture2D_BpAble(const int32 Width, const int32 Height,
                                                      TArray<FColor>& SrcColorArray)
{
    const int32 SrcColorArrayNum = SrcColorArray.Num();
 
    UTexture2D* ResultTexture = UTexture2D::CreateTransient(Width, Height);
 
    FColor* P_Pixels = static_cast<FColor*>(ResultTexture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
 
    FMemory::Memcpy(P_Pixels, SrcColorArray.GetData(), sizeof(FColor) * SrcColorArrayNum);
 
    ResultTexture->GetPlatformData()->Mips[0].BulkData.Unlock();
    ResultTexture->UpdateResource();
 
    return ResultTexture;
}
 
cs


참고 자료




FColor, FFloat16Color, FLinearColor

  1. FColor

    • 정수 기반의 색상 클래스

    • 8비트 채널(R, G, B, A)을 가지며, 각 채널은 0~255 범위의 값을 가짐.

    • 주로 메모리 사용이 적고, 단순한 색상 표현이 필요한 경우 사용.

    • FColor RedColor = FColor(255, 0, 0, 255); // 빨간색 (R=255, G=0, B=0, A=255)
      
      
  2. FFloat16Color

    • 16비트 부동소수점 기반의 색상 클래스

    • 더 큰 색상 범위와 정확도를 제공하며, HDR(High Dynamic Range) 데이터나 고정밀 색상 처리를 위해 사용.

    • R, G, B, A 채널이 각각 FFloat16 타입을 가짐.

    • FFloat16Color HDRColor(0.5f, 0.5f, 0.5f, 1.0f); // 중간 회색
      
      
  3. FLinearColor

    • 32비트 부동소수점 기반의 색상 클래스

    • 채널 값이 0.0~1.0의 범위를 가지며, 선형 색상 공간에서 계산.

    • 고정밀도 색상 표현 및 렌더링 작업에 적합.

    • FColor와 변환 가능하며, SRGB 색상 공간과 선형 색상 공간 간의 변환에 사용.

    • FLinearColor BlueColor = FLinearColor(0.0f, 0.0f, 1.0f, 1.0f); // 파란색
      
      


sRGB와 Linear Color의 차이

1. sRGB (Standard RGB)

  • 감마 보정된 색상 공간

    • 인간의 시각 시스템이 빛의 밝기를 비선형적으로 인식하는 점을 반영하여 설계된 색상 공간.
    • 어두운 색은 더 세밀하게, 밝은 색은 덜 세밀하게 표현.
    • 일반적으로 UI 작업, 이미지 파일 포맷(JPEG, PNG) 등에서 사용.
  • 특징

    • 각 채널 값은 0~255 범위를 가짐.
    • 색상이 비선형적이며, 감마 값(보통 2.2)을 적용하여 처리.
  • 예제

    FColor sRGBColor(128, 64, 255, 255); // sRGB 공간에서 표현된 색상
    

2. Linear Color (선형 색상)

  • 선형적으로 처리된 색상 공간

    • 감마 보정을 적용하지 않은 상태로, 색상 값이 빛의 물리적 강도를 직접적으로 나타냄.
    • 렌더링 파이프라인에서 색상 연산(예: 블렌딩, 조명 계산)에 사용.
    • 더 높은 정확도로 작업 가능.
  • 특징

    • 각 채널 값은 보통 0.0~1.0 범위를 가짐.
    • 선형적이므로 색상 연산에 왜곡이 적음.
  • 예제

    FLinearColor LinearColor(0.5f, 0.25f, 1.0f, 1.0f); // 선형 색상 공간에서 표현된 값
    
    


USceneCaptureComponent2D, UTextureRenderTarget2D 


  USceneCaptureComponent2D의 CaptureSource와 UTextureRenderTarget2D의 ETextureRenderTargetFormat, EPixelFormat 간의 조합이 잘 맞아야한다. 여기서 이미지 저장 (예: PNG)까지 해야한다면, 저장되는 이미지의 포맷 조합 또안 맞춰야하고 데이터 추출이 잘되는지도 확인해야한다.
  매칭되는 ETextureRenderTargetFormat, EPixelFormat 조합의 예시는 하단의 참고코드 부분에 있는 "GetPixelFormatFromRenderTargetFormat" 함수를 참조하면 된다.

USceneCaptureComponent2D

BP 및 C++에서 TextureTarget부분에 UTextureRenderTarget2D를 지정할 수 있다. 


UTextureRenderTarget2D

FRenderTarget::ReadLinearColorPixels (UnrealClient.h 파일 내부) 함수를 호출하면 (D3D11RenderTarget.cpp 또는 D3D12RenderTarget.cpp 파일 내부의) ConvertRAWSurfaceDataToFLinearColor 함수가 호출되는 것으로 보인다. RenderTarget의 PixelFormat이 지원되지 않는 포맷이라면 ReadLinearColorPixels 함수는 제대로 된 값을 반영하지 않는다. UE5.3버전 기준으로 지원되는 EPixelFormat은 다음과 같다. (정확히는 처리 분기가 있는 녀석들)

  • PF_R16F, PF_R16F_FILTER
  • PF_R8G8B8A8
  • PF_B8G8R8A8
  • PF_A2B10G10R10
  • PF_FloatRGBA
  • PF_FloatRGB, PF_FloatR11G11B10
  • PF_A32B32G32R32F
  • PF_D24
  • PF_DepthStencil
  • PF_A16B16G16R16
  • PF_G16R16

나머지 녀석은 상위 버전에서 지원 될 수도 있다.

추천 이미지 옵션

  • RGB
    • USceneCaptureComponent2D
      • CaptureSource : "SCS_SceneColor" 글자가 포함된 것, SCS_FinalColorLDR (Post Processing에서 처리)
    • UTextureRenderTarget2D
      • Format : RTF_RGBA8
        • Pixel을 읽은 후, alpha 채널 값을 수정해야될 수도 있음. 
  • Depth : 
    • USceneCaptureComponent2D
      • CaptureSource : SCS_SceneDepth
    • UTextureRenderTarget2D
      • Format : RTF_RGBA32f
        • Depth 특성상 R채널에만 이미지 데이터가 저장됨
        • TArray<FLinearColor>로 Pixel을 읽어 R채널만 따로 추출하면 됨 (float)
  • Normal : 
    • USceneCaptureComponent2D
      • CaptureSource : SCS_Normal
    • UTextureRenderTarget2D
      • Format : RTF_RGBA16f
      • ClearColor 추가 추천


이미지 저장

PNG

  하단의 코드는 Rama님께서 제작한 Victory Plugin에 있는 코드이다. 8bit RGB 형태로 캡쳐를 한 후, 8bit PNG 파일로 저장한다. FColor 구조체가 Endian 설정에 따라서 저장하는 순서가 다르기 때문에 PNG 저장 전에 Pixel 순서를 재조정 처리도 한다.
  PNG의 경우 ImageWrapper::SetRaw 함수에서 ERGBFormat::RGBA는 지원하지만, ERGBFormat::RGBAF는 지원하지 않는다.
  PNG 이미지는 무손실 압축이기 때문에 ImageWrapper::GetCompressed의 파라미터인 Quality부분에 값을 넣어도 무시된다.

==GistCode Start==
==GistCode End==


FFloat16Color를 이용하여 (ReadFloat16Pixels) 하여 16bit PNG를 저장은 할 수 있다. (다만 검증은 필요함.) 




참고할만한 코드


==GistCode Start==
==GistCode End==

참고 링크










댓글