라는것을 알게 되는 좋은 경험.....

 

 


 

요즘 (25~26년 기준) 개발판은 AI로 핫하다 못해 불타는 중이다.

그중에서도, MCP (Model Context Protocol) 라는것이 등장해서, 복잡한 프로그램을 사용하는 AI 사용자들이 신세계를 맛보고 있는데..

 

MCP는 뭐.. 간단히 설명하면 통신규약이고, AI 도구와 타 프로그램을 연결해주는 어쩌고...

뭐, 이걸 검색해서 본 사람들이면 나보다 더 잘 알거라 생각하고 기본적인 얘기는 생략한다.

 

그래서 무슨 얘기가 하고 싶은가?

 

Unity MCP는 비교적 빠르게 나온 반면, Unreal MCP는 좀 소식이 지지부진하다가 5.8에 들어서야 드디어 그 소식이 들리기 시작했다.

그것도 >>실험버전<< 으로.....

 

참 빠르다 그래

 

그리고, MCP 관련된 설명이 꽤 빈약한게, 정상적으로 MCP 를 수행시키려면 3개의 플러그인 활성화가 필요하다

첫번째는 저기에 있는"MCP Client Toolset", 두번째는 MCP 코어인 "Unreal MCP"

그리고 마지막으로... 저기에 없는 (검색에 잘 안잡히는) "Editor Toolset" 플러그인

 

특히 3번은... 기껏 MCP 플러그인 켜놓고 껐다 키고 실행해보면 기능 제한이 걸려서 "뭐지?" 하고 살펴봐야 알 수 있는 무언가라... 굉장히 골치가 아프다.

 

더 문제는, 아직 실험단계라고 그런지 토큰을 진짜 물 마시듯이 써버린다.

 

실제로 측정된 값을 얘기하면...

내가 사용하는 ChatGPT Plus 기준, 30%가 약 11만~17만 가량인데,

블루프린트의 특정 노드 정보 조회만 해도 2~3만 토큰을 써버린다.

 

진짜 별거 아닌, "이 블루프린트의 주석 좀 찾아줘" 라는 명령 하나 넣었더니 5~10%가량의 토큰을 써버리는걸 보고 와 이거 지금은 못써먹겠다... 라는 생각이 바로 들더라.

 

그리고 한가지 더 문제,

Unreal MCP는 5.8 이후 기능이다.

하지만 게임 엔진을 쓰는 대부분의 개발 프로젝트가 그렇듯이, 매번 최신을 따라가는 프로젝트는 없다! 라고 단언이 가능할 수준으로 실무에서는 버전 업데이트를 굉장히 보수적으로 수행한다.

 

그럼... 결국 Unreal MCP를 실무에서 쓸 일은 아직 한참 멀었다는 뜻.

 

그치만 제가 누굽니까. 언리얼 엔진 프로그래머... 일단은 말이죠.

안되면 되게하라 마인드로 작업에 착수했습니다... 만, 작업전에 검색 해보니 이미 구버전으로 포팅하려는거 자체는 많더라구요?

그래도 뭐... 최근애 새로운 작업 안해보기도 했고, 요즘 면접때 AI 활용 관련을 많이 묻는거같기도 해서 공부할 겸 작업을 진행했습니다.

 

 

1. 일단 필요한걸 수집한다

우선, Unreal MCP가 실제로 쓰는 플러그인 및 기능들을 모아봤습니다.

생각보다 많더라구요?

EditorToolSet은 UnrealMCP를 요구,

UnrealMCP는 ToolSetRegistry를 요구,

ToolSetRegistry는 >>숨겨진 플러그인<<인 FileSandbox와, Engine Sourcecode 에 박혀있는 Json Utility 신규 기능들을 요구...

그리고 JsonUtilitiesEditor는, 내부에서 "JsonSchema" 의 5.8 업데이트 버전을 요구합니다.

산넘어 산이죠.

 

AI에게 맡긴 도식화...

 

별별 플러그인 다 봤지만 이렇게까지 종속성이 개판 오분전인건 처음보는거같아요.

아니 뭐 더 심한게 있기야 하겠지... 있겠지만...!

 

여튼, 필요한 리스트를 확보했으니 우선 틀을 만들어둡니다.

 

일단 목표는 "한개의 플러그인 그룹으로 기동하기" 니까요.

 

2. 하나씩 옮긴다... 어라?

그리고 이제 하나씩 옮기자니... 플러그인 단위로 옮겨야할거같은데

보시면 알겠지만 "모듈" 이 몇개 있어요.

 

그중에서 코어에 해당하는 "ToolsetRegistry"를 옮기자니... 이 친구의 종속성이 진짜 엄청납니다.

 

엔진 업데이트도 반영해야하지, 숨겨진 플러그인도 넣어야하지..

일단 냅다 넣고 보니까 5.8에 추가된 다른 기능들도 산더미지...

 

 

골치가 아프더라구요.

 

수정한 항목이 하도 많아서 대충 요약하면

 

문제 1. FSharedString::ToView() 부재

발생한 이슈 : String 전달시 StringView 를 Map에 넣은 상태로, .ToView() 함수로 전달하나, 해당 함수가 5.7.4에선 없음!

대응 방법 : *SharedString 또는 FStringView 생성으로 대응

 

문제 2 : FJsonObjectStructInterfaceRegistry::HasStructConverterRegistered() 신규기능 대응 불가

발생한 이슈 : 5.8의 로직에 포함되어있으나, 5.7에는 조회 API 없음. 또한 코어 기능은 Private 함수여서 엔진수정없이 접근불가

 

대응 방법 : 함수 조회결과, 실제 production registration도 사실상 없는것으로 보여, 해당 guard는 일단 주석처리

 

문제 3 : FPropertyAccessChangeNotify::ElementIndicesMap (인덱스 처리 추가 대응)

발생한 이슈 : 5.8에서 container element index notification 전달 기능이 추가되었으나, 5.7에는 관련 파이프라인 자체가 없음

대응 : 현재는 엔진버전 분기로 제외하고 known limitation으로 보류

 

문제 4 : UE_LOGF, UE_CLOGF 등 신규 Logging API 변화

발생한 이슈 : 5.8에서 추가된 logging API 변화로 인한 로그 처리 빌드 실패

대응 : 5.7 logging backend에 맞는 Convert 수행하여 기존 UE_LOG나 UE_CLOG 등으로 대체

 

문제 5 : 파일 입출력시 EPlatformFileWrite::AttemptDeleteAndCreate Enum 추가 및 분기 추가

발생 이슈 : 5.8에서 파일 입출력 관련 분기 추가 (재시도 기능 추가)

대응 : enum 자체를 추가하지 않고, MCP 내에서 delete + retry 방식으로 동작 재현

 

문제 6 : ISourceControlProvider interface 관련 빌드 실패

발생 이슈 : 5.8에서 일부 함수 삭제&재배치, 단 변화된 함수들에 특별한 기능이 존재하진 않음

대응 : 5.8의 UsesSoftRevertOnDelete(), 5.7 pure virtual인 IsAtLatestRevision(), GetNumLocalChanges()는 stub 추가

 

문제 7 : FUntilCommand 처리

발생 이슈 : 5.8에 timeout callback 없는 convenience constructor가 추가되어, 해당 로직 대응 필요

대응 : 호출 로직에서 [](){ return true; } timeout callback을 명시적으로 넣어 동일 동작 재현

 

문제 8 : UKismetSystemLibrary::RaiseScriptError() 함수 관련 처리

발생 이슈 : 5.8 에서 유틸리티성 API 기능 추가, 5.7 이하에서 대응함수 없음

대응 : 이미 존재하는 기존 UserRaisedError() 함수 이용, 플러그 함수 추가로 대응

 

굵직한건 이정도였고, 소소한 종속성 미연결로 인한 링킹 오류 같은걸 잡고 나니까...

 

일단 ToolsetRegistry 플러그인을 끼운채 "빌드는 됩니다" !

 

아직 MCP 구동은 테스트를 더 해봐야겠지만... 일단 한걸음 내딛긴 했어요.

 

 

 

https://locketgoma.tistory.com/109

 

MetalFX For Unreal - 개발일지 5

MetalFX For Unreal - 개발일지 5 : 사실 MetalFX 업스케일러는 2개에요 부제가 모든걸 설명하는 이번 회고입니다. 사실 최근 작업의 하이라이트는 이번 회고에 포함된 0.3까지가 아니라 그 이후~오늘까지

locketgoma.tistory.com

 

지난 회고에 "다음 편도 곧 쓸듯" 이라고 적어놓고 거의 한달만에 다시 왔습니다 ㅋㅋㅋㅋ

 

그동안 좀 정신없는일들이 많았었네요. 쩝.

 

여튼, 기술적으로도 [개발일지 5] 기준인 0.31에서 이번 [개발일지 6] 기준인 0.4 까지 꽤 많은 변화가 있었습니다.

 

꽤 큰건

 

1. 샘플 프로젝트 외에 Lyra 등 "언리얼에서 제공해주는 정규 프로젝트" 에서도 테스트가 가능

2. 샤프닝 기능 추가

3. 실제로 성능 개선이 확인됨

 

이렇게였던거같네요.

 


그래서 무슨 일이 있었나요?

1. 하드코딩은 우선 줄이고 봅시다.

사실, 이전까지의 로직으로는 업스케일러 사용 여부 판단을 "하드코딩 된 값"으로 쓰고 있었습니다.

(간략하게) 알고 있던 정보로는, MetalFX 자체는 Metal3 이상, MetalFX Temporal Upscaler는 A17 이후 (Apple GPU 9) 에 사용 가능한것으로 알고 있어서, 이런 정보들을 하드코딩 해놨었어요.

 

그으으으치만... 이건 (그럴 일은 없어야겠지만) 지원 디바이스 정보가 바뀌면 그때그때 바꿔줘야 하는 문제점이 있습니다.

특히, MetalCpp / OS 레벨에서 정보가 바뀐다? 그때부터 버전관리 지옥이 시작되는 셈이죠.

 

 

 

뭔가 크게 잘못된거 같은데?

 

 

 

 

해서, 업스케일러쪽에서 직접 지원해주는 supportsDevice 정보를 이용하기로 했습니다.

 

MTLFXTemporalScalerDescriptor::supportsDevice()
MTLFXSpatialScalerDescriptor::supportsDevice()

 

이렇게.

 

어차피 최종적으로는 Spatial Upscaler도 지원할거니까, 그걸 MetalFX 실행 기준으로, Temporal 은 별개의 검사로직으로 둔 것이죠.

 

또한, Temporal이 가능하다 -> Temporal만, Spatial만 가능하다 -> Spatial만 하는 방식으로, Primary Upscaler 충돌 문제를 방지하기 위한 보완 처리를 했습니다.

 

 

 

2. "Histroy" 는 1개가 아니다.

이건 뭔 소리인가 싶을거에요.

우선, Temporal Upscaler의 "Histroy" 개념에 대해 짧게 설명드리자면... 사실 이건 2개의 개념을 1개의 이름으로 묶어둔거에 가깝습니다.

 

Upscaler 에서 실제로 사용하는 History 는 "Temporal Upscaler에서 사용하는 기존까지의 업스케일러 결과물 기록" 이고, UE 환경에서 지원하는 History 는 "연속성 판단을 위한 토큰" 에 가까운 무언가인 셈이죠.

 

 

연속적인 이미지! (아님)

 

 

(어쩐지 DLSS History 클래스도 되게 간단하더니)

 

근데 이걸 2개가 아니라 1개로 생각하고 있으니, 이게 맞나? 하고 있던거지.

 

History 가 2개인걸 깨달은 이후, UE 환경에서의 History를 의미에 맞게 사용할 수 있게 개선을 했습니다. 역시나 DLSS 로직을 좀 참고했구요

 

오해를 막기위해 토큰 Histroy는 History(Token), 데이터 History는 History(Data) 로 적고 설명드리자면...

 

요약하자면, "이전 프레임과 연속되는지, 아닌지" 를 판별하는건데, 대체로 Histroy (Token) 를 유지하는 조건이

1. 카메라가 유지되는지 (카메라가 계속 같은 위치인지, 움직였는지 등등)

2. PrevHistroy가 존재하는지

 

인데, 둘중에 하나라도 만족하지 않으면 바로 Histroy (Token) 를 Reset으로 체크해서, History (Data)를 Reset 할지 말지를 결정하게 한것.

 

즉, 이제 진짜 "History" 를 활용하게 된 셈입니다.

 

3. Motion Vector 작업

역시나 DLSS 로직 도움을 받은 작업입니다. 고마워요 nvidia!

 

우선 Motion Vector 에 대해 간단히 설명드리자면, "직전 프레임 대비 픽셀이 어디로 옮겨젔는가" 를 보여주는 방향 벡터 텍스쳐... 로 이해하면 됩니다.

기존에는 제공되는 데이터를 그냥 들이박았는데, DLSS 로직을 좀 뒤져보니, 이걸 추가적인 쉐이더 처리를 통해 하더라구요?

 

하라면 한다!

 

 

 

그럼 해야지.

 

작업 스텝은 다음과 같이...

UE Scene Velocity 존재 UE Scene Velocity 없음
DecodeVelocityFromTexture() Depth + View.ClipToPrevClip
UE screen-space Velocity Camera Motion 재구성 처리
FrameBuffer Pixel-Space Motion Vector 변환 (for MetalFX)
PF_G16R16F Velocity Texture 생성
MetalFX Temporal Upscaler 입력으로 사용

 

진행되었고, 특히 "ScreenVelocity" 를 FrameBuffer Pixel 단위로 변환(디코딩) 하는 로직이 추가되었다는게 좀 큼.

 

풀어쓰자면, "기존의 UE Velocity Texture를 단순히 MetalFX 에 전달하는것에서 넘어, 별도 RDG Compute Pass에서 UE Velocity를 디코딩, Scene Velocity가 없는 Pixel의 경우 Depth 와 이전의 View Projection를 이용, Camera Motion을 복원한 후, 이 결과물들을 MetalFX에서 쓸 수 있게 pixel-Space Motion Vector로 변환해서 사용" 이라고... 할 수 있다.

(사실 어라 이게 이게 맞나 하는 느낌으로 써서 좀 헷깔리긴한데 그렇다고 합니다)

 

해서, 드디어 Motion Vector도 더미 데이터가 아닌 실질적인 데이터로 사용 가능!

 

4. 업스케일러에는 역시 샤프닝이지

꼭 업스케일러를 쓰진 않더라도, r.tonemapper.sharpen 이라는 명령으로 샤프닝을 임의로 쓸 수는 있다.

근데 이왕 MetalFX 쓰는거, 샤프닝도 MetalFX 에서 처리하면 좋지 않을까? 라는 개념으로 다른 모바일 업스케일러 (조금 구식인) 인 FSR1 로직을 참고했다.

만... 좀 어이없던 사실. 언리얼에서 기본 제공하던 모바일 FSR (FSR1)이 5.6.x 까진 있었는데, 5.7 으로 올라가면서 지원 목록에서 사라졌더라구요?

그래서 모바일 FSR 로직 찾는데 조금 걸리긴 했는데... 아무튼 그건 중요한거 아니고, 모바일 FSR에서 찾던건 바로 "RCAS" 로직

(Robust Contrast-Adaptive Sharpening 라고 부릅니다)

 

최종 출력 (정확히는 Primary Upcaler를 통해서 나온 최종 랜더링 결과물)에다 Sharpning을 추가해서, "선명한 테두리" 효과를 적용시켰습니다.

 

RCAS 로직이 보니까 좀 신기하던데, 진짜진짜 짧게 요약하면 흔히 쓰는 "현재 픽셀과 주변 픽셀의 대비를 강조" 하는 방식이더라구요.

 

여튼, 이렇게 RCAS 를 위한 "Secondary Upscaler" 슬롯을 사용, 배치하여 MetalFX 로 기동하는 Sharping 도 추가하였습니다. 짝짝.

 

5. 그리고 진짜 우연히 발견한 큰 이슈

...까지 작업하고, 테스트 중에 발견한 일이 하나 있었습니다.

 

기존 테스트는 그냥 사용하는 맥북에서 했는데, 맥북의 화면 출력을 외부 모니터에 연결해서 테스트하니까... 어라 뭔가 이상합니다?

출력되는 스케일이 확실하게 차이나는게 보이더라구요. 그래서 "이거 왜이러지?" 하고 좀 찾아보니까... 놀랍게도

 

GPU 단에서 만든 실제 Content Size가 Output Size와 다를 수 있다.

 

라는 사실이 나왔습니다.

 

 

진짜요?

 

 

즉...

MTLTexture Allocation Size != Input Contex Size != Output Size

 

라는 굉장한 사실.

 

이거에 대해서는, "Dynamic Input" 설정을 통해 작업하기로 했고, 사이즈 오차에 따른 대응을 마쳤습니다.

 

 


 

 

여기까지 하고나니까... 생각보다 분량이 크더라구요.

그리고, 이제는 "단순히 테스트 프로젝트말고 공식 샘플에다가도 넣어보자!" 해서 Lyra에 투입해보니, 성능 향상 약 10% 정도가 확인됐습니다.

 

https://github.com/LocketGoma/MetalFX/blob/main/Docs/PerformanceValidation.md

 

 

체감상, DLSS 3 정도의 성능인거같은데, 이정도만 해도 어디야 싶은 느낌.

 

 

일단 다음 작업은...... 취업하고 할거 같은데.......

노력해보겠습니다.

 

이직좀 시켜주세요............ 열심히 일할게.......

 

이전 회사에서 일할때 자주 듣지만, 자주 이해가 안되는 표현이 있었다.

바로 "Hitch" 라는 표현인데, "지연" 이라는 뜻으로만 어렴풋이 이해하고 있었다.

 

근데 알고보니, Hitch 라는 표현이 생각보다 많은 의미를 담고 있는거같더라...

 

이번에 강연가니까 아예 "The Anatomy of a Hitch" 라는 항목이 있을 정도였으니...
(대충 한국식으로 번역하면 Hitch 뜯어먹기 정도 되려나)


Hitch란?

간단하게 요약하면, "특정 Frame 또는 특정 Pipeline Stage 가 예상 시간보다 오래 걸려서 Frame Timing이 깨지는 현상"

더 간단하게 요약하면, "어 뭔가 지연이 생긴거같은데요" 같은 느낌인것.

 

예를들어, 60Hz 환경이라고 치면 1 Frame 당 Budget은 대략 16.67ms 정도가 된다.

 

정상적인 경우라면 많이 차이가 나봤자 1~2ms 정도로 큰 차이가 없을건데, IO 지연 등의 문제로 특정 프레임에서 갑자기 50ms / 100ms 이러고 있으면 그게 Hitch 인 것이다.

 

1. The Anatomy of the a Hitch - "Hitch 뜯어보기"

 

Hitch 를 도식화 하면 다음과 같은 느낌이다.

1. 다양한 이유에서 파생된 지연이 발생하고

2. 이것이 기본 Frame Budget를 초과해버린다

3. 이를 Hitch 라 부르고

4. Hitch 로 인해 프레임 지연, Jitter 등의 파생 문제들이 발생한다.

 

 

 

좀 더 자세히 설명하자.

(너무너무 다양한) Hitch의 원인말고, Hitch의 여파에 대해 언급하면

 

1. Dropped Frames (=Frame Miss)

- 프레임을 정해진 Budget 안에 만들지 못했다.

 

대체로 이런 경우, 새로운 프레임이 아직 생성이 되지 않아서 "이전 프레임을 더 오래 표시" -> "화면이 멈춘것처럼 보임".

혹은, 흔히 "버벅임", "끊김" 같은 느낌이 들게 된다.

 

2. Recovery (->과한 Deltatime 여파)

- Hitch로 인한 Deltatime 과부하, 그로 인한 Simulation Time Problem

 

게임 업데이트 기준은 Deltatime 이다. 대체로 Deltatime은 Frame Rate를 따라가는 편이지만(항상 같은건 아님), Hitch 등이 발생한 경우에는 Deltatime이 "길어지는 현상" 이 일어난다.

 

그럼 무슨 일이 일어나는가, 대체로 이동 계산을 "속도 x Deltatime" 을 1틱마다 움직이게 하는데, 1틱의 Deltatime이 5배로 커졌다 하면 이동도 갑자기 5배 멀리 움직이는 이른바 "순간이동 현상"이 발생하는것.

 

그래서 요즘 게이머들이 흔히 "트레이서 현상" 이라고 부르는게 바로 이거인것... 데자뷰 느껴본적 있어? x N

 

 

3. Pipeline Refill

Hitch로 인해 특정 스레드가 지연이 걸리면, 그 후속 스레드들도 죄다 지연이 걸린다.

 

그도 그럴게... 아무리 언리얼 파이프라인상 각 스레드가 병렬로 돌아가서 여러 단계의 프레임이 동시에 연산된다지만, 각 N번째 프레임은 GT -> RT -> RHI -> GPU 로 이어지는 순서가 정해져 있다. 

근데 여기서 하나 지연이 크게 걸리면? 그 후속 작업들도 전부 지연이 걸리겠지? 연대책임인 것이다

 

(조금 정확하지 않을 수 있음)

 

대충 이런 느낌.

 

한번 지연이 씨게 걸리면 그 프레임만 걸리는게 아니라 나머지도 쭉쭉 지연되는것...

그리고, 지연이 걸리면 "복구"를 해야하니까 복구를 위한 작업도 파바박 이루어지고... 그러면서 프레임 재생이 개판 오분전이 되는것이다.

 

이런 상황을 대응하기 위해 발생된 "타이밍 변동성"가 바로 Jitter 이다

 

4. Jitter?

이제 Jitter를 설명할 차례. Jitter를 번역하면 많은 뜻이 나오는데, 여기서는 간단하게 "지연현상" 의 의미로 설명하겠다.

 

Jitter는 원인이 아니라 지연의 결과고, 우리가 흔히 눈에 보게 되는 지연 현상도 "Jitter" 로 보이는것이다.

 

대략 이런 흐름인 셈.

 

밀려버린 Frame Time으로 인한 지연을 대응하기 위해 발생하는 현상... 인 것, 그래서 Hitch가 한 프레임에서만 발생했다고 Jitter도 한 프레임만 발생한다는걸 보장할 수 없는게 이런 이유인 것이다. (실제 Hitch 가 단 1프레임이라도 최악을 상정한다면 거의 1초 이상 Jitter 현상이 발생할수도 있는것이다.)

 

 

얼마전에 면접을 봤었는데, 랜더링 파이프라인에 관한 깊은 얘기가 많이 나왔었다.

 

물론 대차게 망해버렸고, "이제는 이런것도 묻는구나" 라는 생각이 들더라...

 

해서, 이왕 이렇게 알게 된거, 제대로 정리하자! 싶어서 정리 문서로 남길까 한다.

 

 


 

우선 여기서 설명하는 랜더링 파이프라인은 "보편적인 랜더링 파이프라인" 을 의미하는것이 아닌, 언리얼 시스템 플로우에 가까운 무언가 긴 하다.

 

즉 "언리얼에서는 이 프레임을 어떤 순서로 그리게 되는가" 라는 뜻.

 

일단, 이미지부터 한장 보자.

 

(살짝 정확하지 않을 수 있음에 주의)

 

이번 면접때 크게 당한 부분이자, 마침 다음날 언리얼 페스트에서 들은 가장 중요한 내용같아서 따로 빼둔다.

 

요약하면, "각 프레임을 그리기 위한 일들은 같은 시점에 일어나지않다" / "각 프레임을 그리기 위한 일은 병행 처리한다"

이렇게 2개인것 같다.

 

실제로 N번째 프레임을 화면에 그리고 있을때, (진행 속도에 따라) Game Thread 는 N+2 프레임을 준비하고 있고 Render Thread는 N+1~N+2, RHI Thread는 N+1을 준비하는 느낌.

 

대체로 디스플레이를 N으로 두면 (Display+GPU를 하나로 보는 편인데)

RHI는 N+1

Render Thread는 N+1~N+2

Game Thread는 N+2 정도가 나오는 편이다.

 

그리고, 각 프레임 진행의 간격을 맞추기 위해 중간중간 Wait가 들어가는데, 이게 들어가지 않으면 "내 화면에서 보이는건 N번째인데 실제로 입력이 들어가는건 N+3~N+4" 이 되어버릴수도 있는 처참한 상황이 되어버린다.

극단적으로, 이게 60 FPS 환경이라면 N+3 지연 -> 약 64ms 이상 지연이라는 소리가 되어버린다. (1초당 프레임 수가 줄어들면 줄어들수록 지연은 더 길어지고...)

 

그리고 각 프레임의 진행 간격을 최소화 시키면 그게 Low Latency 가 되는거고...

 

1. 랜더링 파이프라인의 구조

언리얼 랜더링 파이프라인은 (나누는 방식에 따라)

Game Thread

Render Thread

RHI Thread

GPU Task Thread

Display

로 나눌 수 있다.

 

Game Thread는 말 그대로 Game 관련 이벤트 등이 발생하는 스레드, World 구성 및 Tick, Input 이 발생하는 "우리가 일반적으로 알고 있는 게임 스레드".

Render Thread는 Game World를 렌더링 가능한 Scene으로 해석하고, 이번 프레임의 Rendering Operations를 구성하는 Renderer Frontend. 즉, "이제 뭘 그릴까요?" 하는 부분인 셈.

Render Thread에서는 Scene representation, Visibility&Culling, View, Mesh drawing, Material/shader selection, RDG Pass와 Resource dependency 구성, RHI command 생성 등의 작업을 수행한다.

RHI Thread는 Render Thread에서 생성된 RHI Command를 처리하고, 플랫폼별 Graphics API(DX12/Vulkan/Metal)에 대응하는 backend 작업을 수행한다.

Epic 가이드에서도 Render Thread를 frontend, RHI Thread를 backend execution/translation 단계로 설명한다. 

CPU 단의 랜더링에서는 Render Thread - Front / RHI Thread - Back 인 셈.

 

Render Thread 와 RHI Thread가 분리되어있는 핵심 이유는 "Render frontend 작업 + Graphics backend 작업" 을 병행 처리하기 위함 인 것이다.

 

2. 스레드 간 데이터 전송

 

이렇게 다양한 스레드가 있는데, 각 스레드별로 데이터 전송은 어떻게 하는가.

내가 이걸 면접때 "락 걸고 처리..." 라고 얘기했는데, 대체로 틀린 표현이다. (아예 틀린건 아님. 락을 걸고 주고받을수도 있긴 하니까)

 

락을 걸고 처리를 할 수 있으나 안 하는 이유 - 락은 공짜냐?

락을 걸고 작업하는순간 (상황에 따라) 작업의 비용이 너무너무너무너무 비싸져서, 언리얼에선 대체로 각각의 데이터를 별개로 처리하는 방식을 택한다.

 

Game Thread : UPrimitiveComponent
       │
       │ Render representation 생성
      ▼
Render Thread : FPrimitiveSceneProxy

이런 식인것. Shared mutable state를 최소화하고, 각 Thread가 자기 representation을 소유한 채 command/message를 통해 상태 변화를 전달하여 처리.

 

이러는 이유는 "각 스레드가 병행 처리 될 수 있음" 이 가장 큰 이슈이다. 만일 데이터를 공유한다 치자. Game Thread N 에서 존재하던 오브젝트 A가 N+1 에 완전히 사라진다고 할때, 아직 Render Thread N에서 A 에 접근하려는 순간 바로 잘못된 포인터 접근으로 크래시가 나버릴것이다.

 

모든 시스템을 thread-safe shared object로 만드는 대신, 각 실행 영역에 ownership을 부여하고 필요한 데이터를 command/task/message 형태로 넘긴다.


이게 핵심인것.

 

그래서 이런 데이터들을 (예시로) RDG Pass에서는 다음과 같이 처리한다.

 

SomeGTFunction()
{
    Data = ...;

    ENQUEUE_RENDER_COMMAND(UpdateSomething)(
        [CopiedData = Data](FRHICommandListImmediate& RHICmdList)
        {
            // 나중에 Render Thread에서 수행
        });
}

 

ENQUEUE_RENDER_COMMAND 라는걸 호출, 필요한 명령 / 데이터 전송을 호출하는 셈이다.

 

 

MetalFX For Unreal - 개발일지 5 : 사실 MetalFX 업스케일러는 2개에요


 

두개지요

부제가 모든걸 설명하는 이번 회고입니다.

 

사실 최근 작업의 하이라이트는 이번 회고에 포함된 0.3까지가 아니라 그 이후~오늘까지 진행한 작업들이 굵직굵직해서 거기를 얘기해야할거같은데

 

일단 그 전에 정리할건 하고 가야죠

 

 

 

그래서 무슨 일이 있었나요?

1. 업스케일러가 두개라구요?

 

사실 업스케일러는 크게 2개가 있습니다.

우리가 흔히 아는 "Temporal Upscaling", 시간적 업스케일링이라 부르는 최신 기술.

 

그리고, 그 전에 쓰이던 "Spatial Upscaling", 공간적 업스케일링이라 부르는 기존 업스케일링 기술

 

기존 업스케일링 기법은 현재 픽셀 데이터를 기반으로 화질 상승을 이루어주는 기법으로, 종종 보이는 "이미지를 2배로 키워주는 기능" 과 같은 일반 이미지 업스케일러를 게임 랜더링환경에 맞게 발전시킨 버전으로 볼 수 있다.

다만 이는 큰 문제가 있는데, 기존 업스케일링 기법인 공간적 업스케일링은 "현재 프레임"만 활용하다보니, 가볍긴 하나 성능도 떨어지고 빠르게 변하는 환경에서는 품질이 떨어질 수 있다는 문제가 있다.

 

해서 나온게 시간적 업스케일링인 "Temporal Upscaling". 물론, 여기서 얘기할건 Temporal이 아니라 Spatial 이긴 하다.

주로 공개된 게임용 Spatial Upscaler는 Nvidia 계열로 NIS (Nvidia Image Scaling), AMD 계열로 FSR 1.0 2개를 꼽는 편.

 

물론 둘다 최신 Temporal / AI 기반 Neural Upscaling으로 가면 얘기가 많이 다르긴 합니다.

 

 

 

여튼 그래서, 최신 업스케일링 기법들을 대충 요약하면... 요런 느낌이긴 한데요.

 

 

여기에 빠져있는, 지금 작업중인 "MetalFX"의 경우에는 내부적으로 Spatial, Temporal을 둘다 지원합니다.

다만, 지원 타겟이 달라요.

 

Spatial은 "MetalFX가 탑재만 가능하면 대부분 실행 가능" 이나, Temporal은 Spatial보다는 좀 더 높은 사양을 요구합니다.
테스트 결과로는 iPhone 15 (A17칩) 전후로 유용하게 사용가능한것으로 판단했는데, 실제로는 좀 다른거 같더라구요.

 

여튼, MetalFX Temporal Upscaling을 쓰지 못하는 경우에도 Spatial이라도 사용가능하도록, (언리얼의 경우라면, 기존 모바일 업스케일러인 내장 FSR1을 대체할 수 있도록) 추가 작업을 진행했습니다.

 

 

2. 그래서 뭘 했어요?

사실, 아직 Spatial Upscaler를 완성한건 아니에요. 우선 Temporal Upscaler를 완성시키는게 목표라 공간적 업스케일러는 조금 후순위긴 했어요.

 

솔직히, 성능 좋은걸 먼저해놔야 "오 때깔좋네" 하지... 보기좋은게 먹기도 좋다고... 그쵸?

 

다만, 작업하다 보니 문득 든 생각.

 

"어 이거 지금 분리 안해두면 Spatial Upscaler 작업할때 두번 작업할거같은데"

 

'어 이거 큰일날거같은데'

그래서 0.22 태그 달아서 기존 작업 상태 확정지은 김에 바로 분리작업 진행했습니다.

 

기존에는 MetalFX 업스케일러에 직접 명령을 내리는 UpscalerCore라는 클래스가 TemporalUpscaler 전용이었거든요.

 

신규 구조로는 UpscalerCore를 상속받아서 Temporal / Spatial Upscaler 2개의 클래스가 존재하도록 변경했습니다.

 

공통적으로 쓰는 유틸 함수 같은건 Core 부모에 넣고, 각 업스케일러 타입별로 자체 기능들을 담는 느낌인거죠.

 

 

 

그래서 사실상 해당 작업을 위주로 진행했습니다. 작업 자체는 별거 안한거 같은데, 생각보다 시간이 오래 걸렸습니다. 도중에 면접도 좀 볼 일 있었고 떨어졌지만 이런저런 일들이 있었네요.

 

3. 겸사겸사 확인한 일

그리고, Mac에서도 Metal Debug HUD를 열 수 있더라구요? 빌드 세팅을 변경해서 해당 설정을 띄운채로 테스트결과를 확보해서 첫번째 벤치마크 결과를 띄웠습니다.

 

대충 요런 느낌

https://github.com/LocketGoma/MetalFX/blob/main/Docs/PerformanceValidation.md

 

1차 벤치마크는 요기.

 

 

이게 최신 데이터 (24일까지 작업한 내용)을 토대로는 조금 안 맞는 부분이 있어서, 다음 벤치마크는 좀 더 디테일한 데이터를 가져올거 같긴 해요.

 

그리고... 이거 작업하면서 발견한 재밌는 이슈가 있는데, 이건 다음 문서 (지금까지 쓴 회고글 중에서 제일 분량 크고 데이터도 많을 예정) 에서 적도록 하겠습니다.

 

아마 바로 이어갈거 같아요.

 

 

안녕하세요. 곰입니다.

 

결론부터 얘기하면 아직도 백수입니다

안타까운 청년...

생각보다 면접이 잘 안잡히네요... 업계가 많이 춥긴 한가봅니다.

 

아직 이직처도 안 정해진 상태에서 실업급여도 1달정도밖에 안 남아서 좀 초조한 상태가 됐는데

 

그렇다고 제가 할 수 있는게 있나요 하던 공부나 계속 해야지...

 

 

 

그나마 다행인건 개인 프로젝트인 MetalFX 플러그인 작업이 순항중이고, 다이어트도 지난 회고대비 -10kg 빠져서 잘 되고 있다는점.....?

 

하반기는 좀 좋은일만 있으면 좋겠다 싶었는데 아직은 좋은소식이 없네요 습.

 

MetalFX 개발일지나 써야지

'신변잡기 > 일상' 카테고리의 다른 글

근황  (0) 2026.04.07
12월부터 26년 계획  (0) 2025.12.01
트위터 이런날은 첨보네  (0) 2025.03.11
To do.  (0) 2024.07.22
묵혀뒀던 프로젝트들 좀 살려야겠어요  (0) 2024.06.11

MetalFX For Unreal 개발일지 4 : 이제 슬슬 상용 프로젝트에도 쓸 수 있지 않을까?

 


https://locketgoma.tistory.com/106

 

MetalFX For Unreal - 개발일지 3

MetalFX For Unreal 개발일지 3 : 그 플러그인은 어쩌다 얼레벌레를 벗어나게 되었는가 뭐, 보신분이 있으실진 모르겠는데, 개발일지 2를 올린 뒤 드디어 "0.1 릴리즈" 를 올렸습니다. https://github.com/Locke

locketgoma.tistory.com

 

개발일지 3을 올린지 대충 1달이 지났습니다.

 

그동안 어찌어찌 Resolution 버그도 고쳤고, 기타 안정화 작업을 꽤 많이 수행했습니다.

 

그래서 테스트 환경에서는 이제 안터지더라구요! 

 

그리고 이제, 이 글 이후로 대망의 ver 0.2가 올라갈 예정입니다.

 

드디어 정량화된 수치가 나왔거든요.

 

그래서 무슨 일이 있었나요?

1. 일그러지는거 뭐였는데요?

 

3편에서는 텍스쳐 터지고, resolution 일그러짐 문제가 있었다고 얘기했었죠?

 

그중에, res 문제가 좀 많이 골치아팠는데... 이건 좀 희안하게 고쳤습니다.

 

사실대로 얘기하자면... Codex 도움을 좀 받았었습니다.

 

다만, 원인 추측 자체는 직접 하긴 했는데... 역시나 결론은 "인풋사이즈가 두개다" 이었습니다.

 

정확히는, MetalFX descriptor가 쓰는 입력 texture 크기실제로 화면에 유효한 content rect 크기가 다를 수 있다.

라는 거였죠.

즉, 할당된 "텍스쳐 크기"가 실제 유효 영역보다 더 클 수 있다는것을 놓친것이 가장 큰 문제였습니다.

이거 진짜에요?

 

 

그래서 보완을 어떻게 했느냐... 보완 자체는 별거 없었습니다. 그냥...

실제 렌더 사이즈가 RDG Allocation 크기보다 작으면 실제 렌더 사이즈를 사용한다.

 

즉 descriptor 입력 크기를 texture allocation으로 잡더라도, allocation이 content보다 크면 content 크기로 내려서 MetalFX가 잘못된 입력 스케일을 보지 않도록 보완, 일그러짐을 잡았습니다.

 

이 작업이 완료된 이후, 드디어 MetalFX On / Off 를 반복하더라도 "화면 출력 크기가 변하는 버그" 가 사라졌습니다.

 

2. 방치해둔 Motion Vector / Jitter 임시 처리

Temporal Upscaler에 필요한 데이터 중, Motion Vector (or Velocity Texture) 라는것이 있습니다.

간단히 설명하면, "이전 프레임과 현재 프레임간의 이동" 을 기록한 데이터인데, 지금 작업중인 업스케일러에 Temporal 이라는 표현에서도 알 수 있다시피. "시간적 변화" 의 정보가 필요합니다.

근데 이걸 처음에는 MetalFX를 위한 Motion Vector가 제대로 준비 안 된채로 사용하다보니 업스케일러의 최종 결과물이 불안정해서 "퍼포먼스 우선"(= 스크린 퍼센트를 떨굴수록...) 상황에서 화면이 달달달 떨리는 문제가 있었습니다.

(진짜 이런 느낌이었음)

문제를 확인해보니... Motion Vector 데이터가 미완성인 상태로 적용하려고 하다보니 떨림이 발생하는것이었고, 이 사항은 우선 Motion Vector Scale을 0으로 밀어버리는(= 영향을 주지 않게) 방법 으로 임시대응하긴 했습니다.

 

 

추가로, Jitter라는 데이터도 있는데, 이 값 역시 MetalFX Temporal Upscaler가 이전 프레임과 현재 프레임을 정렬하는 데 필요한 값. 이 데이터는 TemporalUpscaler Input에 포함되는데, 이걸 MetalFX용으로 보정할 수 있는 로직을 추가, 적당히 필요한 조건대로 컨버팅 한 뒤 Jitter를 쓸 수 있는 상황 (=Upscaler Encode가 가능할때)에 전달해서 작업 처리를 했습니다.

 

여튼 이렇게, 큰 벽을 하나 더 넘었습니다.

 

 

 

3. 진짜 최종 모바일 테스트.ipa

그리고 이제, 이게 "진짜 제대로 돌아가는지" 봐야하는 시간.

다만, MetalFX는 (나름) 최신 기술이다보니 Apple 기기중에서도 꽤 최근 기기들에서 잘 돌아갑니다.

그리고 제가 가지고 있는 개발용 아이폰은....... iPhone XR.......... 꽤 옛날 폰이죠.

 

의미있는 테스트를 위해서는 iPhone 15 이상이 필요한데, 이걸 지금 상황에서 구할수는 없으니, 주변에서 구할수 있는 "그나마 최신 iOS 기기"를 하나 구했습니다.

 

그리고 돌려보려고 하니... 어라. 모바일 패스에서 문제가?

라고 하기엔 이미 작업했던거라 반영 안된거만 고치고 빠르게 진행했습니다.

 

그 결과...

 

 

 

드디어... 드디어 Metal Debug HUD에 MetalFX가 잡혔습니다!!!!

 

히히 신난다

지금까지의 노력이 헛되지 않았다는 증거가 드디어 나왔습니다...

 

수치는 50%로 테스트한거라 정상적으로 나온게 맞구요.

 

 

4. 그리고 기타등등 안정화 작업, 폴리싱 등등

이제 테스트 할 항목이 많아지다보니, 테스트를 위한 Debug Display Log를 찍기 시작했습니다.

 

이제 실시간 설명이 필요한 시간

특히 업스케일러는 "스케일을 줄인다" 라는 작업이 수반되는 편이다 보니, 실제 input rect가 어떤지를 꼭 알아야 하더라구요.

 

해당 데이터는 꽤 중요한 사항으로 생각되어서, 출력에 넣었습니다.

 


이제 "iOS에서도 확인은 가능하다" 라는 단계에 도달해서, 슬슬 상용 프로젝트에서도 쓸 수 있지 않을까? 라는 생각이 들기 시작했습니다.

 

근데... 일단 지금 회사 나와있는 마당에 재 취업을 해야 다시 써먹든지 하죠...

 

제가 이 플러그인을 가지고 취업을 할 수 있을까요?

 

일단... 아직 남은게 많으니까 작업은 계속됩니다.

'게임 개발 > Unreal' 카테고리의 다른 글

Unreal Render Pipeline... (1)  (0) 2026.08.21
MetalFX For Unreal - 개발일지 5  (0) 2026.07.25
MetalFX For Unreal - 개발일지 3  (0) 2026.05.20
MetalFX For Unreal - 개발일지 2  (0) 2026.05.13
MetalFX For Unreal - 개발일지 1  (0) 2026.05.07

MetalFX For Unreal 개발일지 3 : 그 플러그인은 어쩌다 얼레벌레를 벗어나게 되었는가

 


 

뭐, 보신분이 있으실진 모르겠는데, 개발일지 2를 올린 뒤 드디어 "0.1 릴리즈" 를 올렸습니다.

 

https://github.com/LocketGoma/MetalFX/releases/tag/0.11

 

Release MetalFX Alpha 0.11 (Edited) · LocketGoma/MetalFX

MetalFX For Unreal Plugin Alpha Release v0.1 Verified Features MetalFX integration Confirmed MetalFX Temporal Upscaler startup Packaging is supported, and testing in packaged builds is possible W...

github.com

 

이 글 기준으로는 0.11이네요.

 

이전 개발일지 (2편) 이후로 뭔가 많이 많이 수정하고, 안정화도 좀 해서

"오 이제 좀 돌릴만한데?" 싶어서 바로 릴리즈를 올린건데... 뭐. 안터지는거같으니 된거 아닐까요?

 

 

 

 

그래서 뭘 했나요?

2편에서는 "Motion Texture" 펑크 문제가 제일 큰 문제였습니다.

 

Command Buffer 문제는 사실 1편쯤에도 어느정도 해결된거였고, 1편쯤에서는 "일단 붙여보자", 2편쯤에는 "어 이거 될거같은데" 같은 느낌이었죠.

 

근데 하다보니... 여전히 몇가지 문제가 더 나왔습니다.

 

1. 텍스쳐가 자꾸 터지는데요?

네. 텍스쳐가 자꾸 터졌습니다.

 

말 그대로, 텍스쳐 객체가 "터져요"

 

와 텍스쳐가 모였더니 터져요

MetalFX도 결국 랜더링 파이프라인을 건드는 무언가다 보니, 랜더링을 위한 데이터들을 가지고 있어야합니다.

그 중, 가장 중요하고 필수적인것이 바로 "텍스쳐" 들인데... 얘들도 결국 데이터란 말이죠?

언리얼, C++ 이니까 당연히 C++ 스타일로 "객체"로 저장되어 "포인터"로 접근하게 될건데, 지금 내가 접근한 텍스쳐가 언제까지 유효할지 알 수가 없는겁니다.

 

그래서 언제는 1프레임 진행하다 터지고, 언제는 3초정도 있다가 터지고... 난리도 아니더라구요.

 

 

 

그으으으래서 우에엥 DLSS에몽~ 하면서 DLSS 로직을 찾아봤습니다.

 

RHICmdList.EnqueueLambda(
	[LocalNGXRHIExtensions, DLSSArguments, DLSSState](FRHICommandListImmediate& Cmd) mutable
	{
		const uint32 FeatureCreationNode = CVarNGXDLSSFeatureCreationNode.GetValueOnRenderThread();
		const uint32 FeatureVisibilityMask = CVarNGXDLSSFeatureVisibilityMask.GetValueOnRenderThread();

		DLSSArguments.GPUNode = FeatureCreationNode == -1 ? Cmd.GetGPUMask().ToIndex() : FMath::Clamp(FeatureCreationNode, 0u, GNumExplicitGPUsForRendering - 1);
		DLSSArguments.GPUVisibility = FeatureVisibilityMask == -1 ? Cmd.GetGPUMask().GetNative() : (Cmd.GetGPUMask().All().GetNative() & FeatureVisibilityMask) ;
		LocalNGXRHIExtensions->ExecuteDLSS(Cmd, DLSSArguments, DLSSState);
	});

 

CommandList에 "EnqueueLambda" 라는게 있더라구요?

 

그으러니까... 작업 스텝을 요약하면, "현재 활성화 된 커맨드버퍼 리스트에 작업을 넣어두고, 차례가 오면 예약된 람다함수를 수행한다" 로 흘러가는거였습니다.

 

이러면 중간에 끼워넣을 필요도, 지금 커맨드버퍼가 마지막인지 검사할 필요도 없어지는거죠. 물론! 텍스쳐의 수명 문제는 밑에서 언급했다시피 여전히 발목을 잡았지만

 

텍스쳐 문제도 결국엔 이쪽에서 해결되는게 몇개 있었습니다. 가장 큰 건 "실행 시점과 텍스쳐 전달 시점의 일치" 가 확보되었다는게 중요한 지점인거 같아요.

 

좀 더 정확히 설명하면 ::
EnqueueLambda를 쓰면 RDG pass 구성 시점이 아니라 실제 RHI command 실행 단계에서 로직을 태울 수 있었습니다. 덕분에 current Metal command buffer를 잡는 문제는 많이 정리됐습니다. 다만 이게 모든 리소스 수명 문제를 자동으로 해결해주는 건 아니고, RDG Texture에서 RHI/Metal Texture를 꺼내는 작업은 AddPass 본문에서 처리하고, EnqueueLambda 내부에서는 이미 준비된 native texture와 command buffer만 쓰도록 역할을 분리해야 했습니다.

 

덕분에, "MetalFX Encode"가 정상적으로 실행되고, 리소스 Release까지 도달했습니다!

 

드디어... 긴 터널의 끝이 보이는구나...

 

물론! 이걸로 끝이 난건 아니었습니다.

 

2. 사용한 포인터는 제자리에

이게 결국 포인터로 관리되잖아요?

그러면 포인터가 유효할때만 써야하는데, 위에서 언급한것처럼 랜덤하게 와장창 터지는 문제가 있었습니다.

특히, 1번에서 EnqueueLambda로 꽤 많은 문제가 해결된 이후 발견된 문제인데, MetalFX 로직을 벗어나면서 텍스쳐 릴리즈될때 터지는 문제가 발견되었습니다.

 

갑자기 텍스쳐가 또 왜 터지냐? 이건 지금 부제를 보면 알겠지만... 잘못된 포인터 접근으로 터지는거였습니다.

 

간단히 요약하면,

1. 텍스쳐를 받아옴

2. 텍스쳐를 변환함

3. 텍스쳐를 Encode로 옮김

4. 텍스쳐를 사용함

5. 텍스쳐를 해제함

 

여기서 문제가 되는 지점이 바로 3번이었습니다. UE Texture를 Metal Texture로 바꾸고, 이걸 관리하고자 관리용 구조체를 만들었는데, C++ 로직상으로는, 동일한 클래스의 다른 객체로 데이터를 "옮김" 또한 "생성" 이란 말이죠? (정확히는 대입 연산자 / 복사 생성자 얘기지만 아무튼) 

 

그러다보니, "새로운 데이터를 생성하는 시점"에 잘못된 재활용을 막기위해 추가된 "강제 릴리즈 로직"이 여기서 사고를 치는겁니다...

 

아직 안썼는데!

 

저 아직 해제될때가 아닌거같은데요...

 

해서 어떻게 해결했느냐... 뭐, 별거 없습니다. 이건 AI 도움을 좀 받았는데, "현재 커맨드버퍼가 사라질때 정리를 하자" 라는 아이디어로 로직을 구성해서, 해당 텍스쳐가 진짜 사용되었을때 (혹은 현재 텍스쳐가 사용되지 않고 완전히 다음 프레임이 재생되어야할때) 소멸하도록 처리를 했습니다.

대충 deferred release 한 셈이죠. 네.

 

짜잔 문제 해결!

 

덤으로, 이건 복사/이동 생성 관련 정책이다보니, 생성자/릴리즈 플래그등을 정리해서 문제 재발을 방지했습니다.

 

 

 

3. 그리고 안정화를 위한 기타등등

그리고 추가로 몇가지를 좀 더 했습니다.

 

첫번째로, 임의의 포멧으로 자동생성되던 Output Texture의 포멧을 "Input Scene Color Texture" 포멧으로 일치. (실제로는 다른걸 써야할수도 있지만 "일단은" 그렇게 적용했습니다.)

 

두번째로, 이게... 엔진에 물려있는 코드다보니, 일부 로직이 "엔진 초기화 시점" 에 기동이 되는겁니다.

내가 원하는건 "게임이 켜질때" 인데, 게임 실행이 아니라 "패키징" 중에도 Module Init이 되버리는 바람에... Init 시점에 넣어둔 로직이 와장창 터지는 사고가.. 있었습니다.

물론 지금은 Commandlet / Cook 경로에서는 안 돌고, "실행중"(=Editor/Game 상태) 에만 돌도록 보완했구요.

 

뭐 그 외에 사소한 실수 엔진코드로 복사되어야하는 코드 자체는 업데이트해놓고 Git Repo에는 업데이트를 안했다던가 들이 몇개 있었는데, 적절하게 고치고 드디어 릴리즈를! 했습니다!

 


 

진짜... 진짜 많이 힘들었지만, 그래도 거의 3개월간 고생한 끝에 릴리즈를 띄우니까 기분이 좋더라구요.

 

이제는 남은 문제들을 해결하러 갈 시간인거 같습니다.

 

 

 

다음 이야기를 기대해주세요! (미정)

 

 

 

P.S : 남아있는 문제들

1. 스케일이 미묘하게 틀린 문제

2. Spatial Upscaler

3. 옵션 확충, "품질 선택기능"

4. 안정화... 기타등등

 

아직 갈길이 멉니다.

'게임 개발 > Unreal' 카테고리의 다른 글

MetalFX For Unreal - 개발일지 5  (0) 2026.07.25
MetalFX For Unreal - 개발일지 4  (0) 2026.06.29
MetalFX For Unreal - 개발일지 2  (0) 2026.05.13
MetalFX For Unreal - 개발일지 1  (0) 2026.05.07
언리얼 포인터 관련 정리  (0) 2026.04.04

+ Recent posts