오늘은 1편에 이어서 모바일 게임 성능 최적화 팁 2편을 공부해봤다.
유니티 유튜브에는 유익한 영상들이 정말 많은것같다.
에셋들의 최적화
유니티에는 수많은 에셋들이 있다.
이 영상에서는 에셋들을 하나하나 최적화하는 방법과 팁 그리고 정보들을 소개한다.
텍스쳐 최적화
텍스쳐 포맷
텍스쳐의 파일 포맷에는 여러가지가 있다.
TGA, JPG, PNG, PSD 등등..
많은 개발자들이 어떤 포맷을 사용해야 최적화 하는데 좋은지 물어본다고 하는데,
무엇을 써도 유니티에서 재 포맷 - 압축의 과정을 거치기 때문에 빌드 결과물에는 영향을 끼치지 않는다고 한다.
다만 에디터에서는 상관이 있으니 좋은 컴퓨터를 쓰라는 농담을 하셨다..(허허)
그래도 굳이 하나를 고르자면?
아티스트들의 작업 편의성을 위해 PSD를 사용할것을 권장했다.
포토샵으로 작업을 하고 PNG같은 파일로 변환해서 사용하다가 편집 할 일이 생긴다면 불편하지 않을까..
텍스쳐 사이즈
텍스쳐 사이즈 원본은 크게 쓰고, 플랫폼별로 강제 할 것을 추천했다.
텍스쳐 인스펙터창을 살펴보면 프랫폼별로 설정하는 곳이 있다.
모바일 게임의 텍스쳐 사이즈
아무리 타겟폰을 최신폰으로 잡더라도 많아봐야 2048, 웬만하면 1024를 권장했다.
4096은 절대 X.
Power of Two(POT)
2의 n승으로 해야 한다.
2D Sprite는 파일을 다 따로 만들어서 나중에 텍스쳐 아틀라스로 묶으라는 조언을 해주셨다.
Read / Write 옵션
텍스쳐 옵션에 보면 Read/Write 옵션이 있다. (다른 버전에서는 Read/Write Enable 인것같다.)
간단히 설명하자면
텍스쳐는 저장장치(HDD,SDD) -> 메모리 -> VRAM(그래픽 카드 메모리) 로 올라간다.
(모바일에서는 원칩 체제로 같은 메모리이며, 논리적으로는 나눠져 있다.)
텍스쳐는 GPU에서만 사용하기 때문에 VRAM 으로 메모리를 올리고 나서 CPU 에서는 날려버리는 형식이다.
만약 Read / Write 를 키면 API에서 텍스쳐에 접근하여 조작을 할 수 있다.
하지만 이 텍스쳐에 접근 하기 위해서 GPU 메모리에 바로 접근을 하면 안되고 CPU(우리가 컴퓨터에 박는 램)에 메모리를 남겨 두어야 한다.
이러면 두 공간에 메모리를 남겨두는 것이고, 정확하게 2배는 아니지만(압축 /다른것들이 들어가기 때문에) 메모리가 중복으로 들어가게 된다.
웬만한 게임에서는 텍스쳐를 동적으로 조작 할 일이 없기 때문에 끄는것이 좋다고 한다.
Min Map
텍스쳐 옵션에는 Min Map이 있다.
가까이에 있는 오브젝트의 텍스쳐는 고해상도의 텍스쳐를 사용하고, 멀리 보이는 오브젝트의 텍스쳐는 저해상도의 텍스쳐를 사용하는 기능이다.
텍스쳐의 사이즈가 512라면 256 .. 128 .. 64..이런식으로 텍스쳐의 절반의 크기를 재귀적으로 복사 생성한다.
이것을 다 합치면 메모리 용량이 33% 정도 뻥튀기 된다.
3D에서는 Min Map을 사용하면 메모리의 대역폭을 줄일 수 있고, 런타임 성능, 메모리 효율측면으로 이점이 많으니 무조건 사용하는것을 추천하고,
2D 게임에서는 일반적으로 이미지가 많고 그 이미지 용량을 줄이는게 낫기 때문에 끄는걸 추천한다고 한다.
메모리 대역폭이란? (Memory Bandwidth)
[네이버 지식백과]
일반적으로 대역폭은 데이터 운반 능력을 의미하는 것으로서 매초마다, 혹은 매 ㎐ 단위마다 주기적으로 표시된다.
램(RAM)의 경우 대역폭은 속도와 데이터 선로 크기를 측정하는 역할을 한다.
[옥스포드 영한사전]
BandWidth
대역폭, 띠너비(컴퓨터 네트워크나 인터넷이 특정 시간 내에 보낼 수 있는 정보량, 흔히 초당 비트로 측정됨)
- 부가설명
GPU로 예를 든다면 GPU가 필요한 데이터가 발생할 때마다 GPU 메모리에서 들고온다.
이 때 메모리에 접근하는 Memory Access라는것이 발생하는데, 이 메모리 접근은 인터넷에서 파일을 다운로드 하는 것과 같다.
우리가 인터넷에서 파일을 다운로드하기 위해서는 시간이 필요하며, Memory Access에서의 이 다운로드 속도는 대역폭에 따라 다르다.
하나의 파일을 6GB/s로 다운로드 할 수 있다면, 두개의 파일의 경우 각각 3Gb/s로 다운 가능하다.
텍스쳐 압축
위에서 한번 언급했지만 PNG,JPG,PSD 등 어떤 파일 포맷을 사용해도 상관이 없지만
그냥 단순히 작업을하는 하드웨어 저장용량을 아끼려면 적은 용량의 포맷을 사용하면 된다고 한다.
어떤 포맷으로 압축을 해도 GPU메모리로 올리면 압축이 풀려서 올라가며,
GPU전용 압축 포맷이 따로 있다.
PVRTC, ETC2, ASTC
앞의 두 압축방식은 손실 압축 방식이기 때문에 화질이 좋지 않다.
그래서 ASTC 압축 방식을 추천하셨는데, 요즘폰은 괜찮으나 저사양폰에는 하드웨어적 제약이 조금 있다.
어떤 핸드폰을 타겟으로 하느냐에 따라 압축도 변경되어야 한다.
동남아는 상대적으로 우리나라보다 사양이 좋지 않은 스마트폰을 사용하는 것을 예로 들었다.
아이폰, 안드로이드 둘 다 ASTC를 지원한다.
메쉬 최적화
메쉬 압축
텍스쳐 압축과는 다르게 데이터의 정밀도를 떨어트린다.
압축을 빡세게 하면 할수록 정말 좋지만 시각적인 퀄리티에 크게 영향이 있을 수 있으므로
화질 차이를 보면서 압축을 할것을 권장했다.
Read/Write
텍스쳐에 있는 옵션과 똑같다.
API로 메쉬에 접근하기위해 사용한다.
GPU에만 올리면 되는 메모리를 CPU 메모리에도 올려서 두곳에 메모리를 생성한다.
우리가 일반적으로 많이하는 이동이나 회전을 말하는것이 아니고,
예를들면 방망이를 깎거나, 항아리를 빗거나 메쉬를 동적으로 조작하는 상황을 말한다.
동적으로 메쉬를 조작 할 일이 없다면 무조건 끄자.
스키닝 연산이 일어나지 않는 메쉬 설정 (움직이지 않는 메쉬)
- 리깅을 꺼준다.
스키닝(Skinning)
스킨 정보와 본, 메시를 사용하여 스킨을 만드는 것.
- 블렌드 쉐입(Blend Shape)도 안쓴다면 꺼주자.
Blend Shape란?
블렌드쉐입은 무표정의 베이스쉐입(base shape)의 형상을 변형시켜
다른 표정의 타겟 쉐입(targetshpae)을 생성하고, 두 형상간의 선형보간을 통해 새로운 얼굴 표정을 합성하는 기술이다.
보통 표정을 만들때 많이 쓴다고 한다.
노말맵을 사용 안하는 메쉬
노말맵을 사용 안하는 단순한 메쉬라던지
노말맵을 사용 안하고 Unlit으로 게임이 구성되어 있다면 메쉬의 tangent 옵션을 꺼준다.
(반대로 lit은 light의 과거, 과거분사이다.)
폴리곤 수 체크
무조건 폴리곤을 적게 쓰는것이 좋은것이 아니고
프로파일링을 통해 GPU병목인지 확인하고 그중에서도 폴리곤 병목인지 체크하고 줄여야 한다.
Asset Post Processor
Unity - Scripting API: AssetPostprocessor (unity3d.com)
이러한 설정들을 하나하나 다 따져가며 만지면서 개발하기에는 현실적으로 큰 무리가 있다.
그래서 자신의 프로젝트를 어떤식으로 설정하여 최적화를 진행할것인지 판단하여 스크립트로 미리 설정들을 정해둘수 있다.
그래픽 최적화
모바일에서는 기존에 쓰던 Built - in을 사용하는것보다 URP를 사용하는것이 성능적으로 좋다고 한다.
하지만 라이브로 서비스 하는 게임을 URP로 올리는것은 큰 모험심을 가져야한다.. 허허
Batch Draw Call
URP를 쓰면 빌트인을 쓸때보다 Draw Call이 효율적이다.
동적 라이트를 사용했을때, 기존의 파이프라인은 그만큼 드로우콜이 일어나는 측면이 있는데 URP에서는 많이 절약이 가능하다고 한다.
Batching
드로우콜을 절약 할 수 있는 기법.
Dynamic Batching
- 동적으로 움직이는 오브젝트를 드로우콜 한방에 처리하여 절약한다.
- 현실적으로는 쉽지 않으며 적용되는 경우가 거의 없다고 한다.
Static Batching
- 적극 활용 추천.
- 움직이지 않는 오브젝트들을 한 메쉬로 합쳐 메모리에 생성하여 드로우콜을 한번에 처리한다.
- 메쉬를 메모리에 생성해두기 때문에 그만큼 메모리가 늘어난다.
- 프로파일링을 통해 런타임 성능을 고려하여 쓸지 말지 결정해야함.
다른 배칭의 방식은 따로 게시글을 정리하여 올릴 예정이다.
프레임 디버거
프레임 디버거 - Unity 매뉴얼 (unity3d.com)
실행중인 게임을 특정 프레임에서 중지하고 해당 프레임을 렌더링하는데 사용되는 개별 드로우콜을 볼 수 있다고 한다.
Light
앞서 얘기 했듯이 URP는 동적 Light를 조금 더 사용 가능하다고 한다.
하지만 동적 Light는 성능에 크게 영향을 미치는것을 항상 염두해두어야 한다.
Light Map, Light Probe를 적극 권장.
그림자는 끄자
-유니티는 그림자를위해 Shadow map이라는 기법을 사용한다.
이 기법을 사용하면 GPU성능과 드로우콜을 추가로 먹는다.
- 그림자를 사용 안하는 상황이라면 Cast Shadow를 꺼준다.
- 탑다운 시점, RTS처럼 캐릭터가 작게 보이는 시점은 그림자 대신에 Blob Shadow(그림자 같은 판떼기 깔기)를 사용하면 좋다.
이처럼 그림자를 우회하여 표현하는 방식을 고려해보자.
라이트 맵
- 빛의 결과물을 텍스쳐로 만들어서 Light의 효과를 내는것.
- 실시간 연산 라이트가 아님.
- Directional light를 MIXED나 BAKED로 설정하고, 그림자를 구울 정적인 물체들을 Static 처리 한 후에 사진처럼 라이트닝 설정에 들어가서 구울 수 있다.
라이트매핑: 시작하기 - Unity 매뉴얼 (unity3d.com)
라이트 프로브
정적인 그림자를 처리하는데 라이트맵을 사용했다면, 동적인 곳에는 라이트 프로브를 적용 할 수 있다.
어떤 공간의 Light 결과물을 미리 저장해두고, 그 공간에 오브젝트가 지나가면 오브젝트는 그 라이트 결과물에 영향을 받는다.
디테일과 정밀도가 떨어지며 Specular 표현이 안되는 등의 제약이 있지만, 빛의 변화는 표현이 가능하다.
*specular 정반사
라이트 프로브 그룹 - Unity 매뉴얼 (unity3d.com)
LOD (Level Of Design)
거리에 따라 오브젝트의 정밀도를 조절해주는 최적화 기능이다.
포폴에도 적용하고 싶었는데 거리에 따라서 메쉬의 정밀도가 떨어지게끔 3D작업을 해야해서 적용하지 못했다.
LOD 그룹 - Unity 매뉴얼 (unity3d.com)
오클루전 컬링
어떤 물체가 물체에 의해 가려지는걸 컬링해준다.
즉 , 가려지면 렌더링을 하지 않는다.
맵이 실내라면 오클루전을 활용하기 좋다.
하지만 반대로 야외 맵이라면 비효율적일 수 있다고 한다.
-> 가려진것은 없는데 오클루전 데이터, 공간을 구성하느라 추가적 메모리가 발생하기 때문
무조건 사용하는것은 좋지 않고 게임 특성에 따라 사용하자.
오클루전 컬링 - Unity 매뉴얼 (unity3d.com)
모바일 환경에서의 해상도
요즘은 핸드폰도 정말 다양하고, 해상도가 워낙 천차만별이다
QHD,UHD.. 많은 해상도의 폰들이 존재하는데,
모바일 환경에서는 모바일 디바이스의 네이티브 해상도 그대로 쓰면 안된다고 한다.
해상도를 적절히 조절을 해서 게임을 만들어야 한다.
하지만 문제는 이 과정에서 UI가 같이 뭉개져서 티가 나는데,
이 문제를 해결하기 위해 3D는 낮은 해상도를 사용하고 2D(UI)는 네이티브 해상도를 적용하는 것이다.
URP에서는 이 기능을 기본적으로 제공하고 있다.
URP 에셋의 Rendering 설정에 들어가보면 Render Scale 설정이 있다.
유니티 코리아의 오지현 에반젤리트가 제공하는 샘플 프로젝트도 있으니 궁금하신분은 확인 해보시면 좋을 것 같다.
카메라
카메라는 렌더링이 안되더라도 늘 컬링연산을 한다.(어떤것을 그리고, 어떤것을 걷어낼지)
그렇기 때문에 여러대 있으면 이 연산이 중복으로 일어나기 때문에 카메라를 여러대 쓴다면 주의하자.
※ 시네머신은 가상카메라서 상관 없다. (실제로는 메인 카메라(Brain) 1대만 존재하는것)
오버드로우(Over Draw)
오버드로우란?
애플리케이션이 한 프레임에 과도한 수의 픽셀을 여러번 입히는 프로세스를 '오버드로우'라고 한다.
오버드로우는 당연히 피해야 한다.
쉐이더
쉐이더는 당연히 가볍게 하는것이 좋다.
노드가 간단하다고 성능이 가벼운것은 아니라는 점을 명심해야한다.
포스트 프로세싱
포트스 프로세싱은 효과마다 성능이 천차만별이다.
컬러 조정과 같은 픽셀의 컬러를 바꾸는 작업은 그렇게 무겁지는 않지만,
Depth of Field와 같은 깊이를 읽어오고, 한번 더 그려야하는 샘플링을 여러번 해야하는 효과는 성능이 무겁다.
포스트 프로세싱을 게임에 적용하지 않는것은 말이 안되고, 막 사용해서도 안된다.
상황에 맞게 잘 사용하는것이 중요하다.
포스트 프로세싱 - Unity 매뉴얼 (unity3d.com)
그래픽 리소스 처리
매터리얼의 스크립트 접근
Unity - Scripting API: Renderer.material (unity3d.com)
우리가 게임에서 색상을 바꿀때 스크립트로 Renderer.material에 접근해서 컬러를 조작한다.
이렇게 접근을 하게 되면 매터리얼의 복사본이 만들어지게 된다.
몬스터가 같은 매터리얼을 쓰는데 복사본이 아닌 원본을 사용한다면 모든 몬스터의 색상이 바뀔것이다.
문제는 같은 매터리얼을 사용해야 배칭 효율성이 좋아지는데,
A오브젝트 - B오브젝트가 같은 매터리얼을 쓰다가 Renderer.material을 통해 접근해서 복사본을 생성하면 배칭이 깨진다고 한다.
이것을 많이하면 많이할수록 비효율적인 측면이 올라간다.
하나씩만 색을 바꿔야한다면 Renderer.material이 맞지만 동일한 매터리얼을 가진 오브젝트들을 전부 다 바꿔줘야 한다면
Renderer.sharedmaterial이라고 따로 있는 API를 쓰면 된다고한다.
Unity - Scripting API: Renderer.sharedMaterial (unity3d.com)
Reflection Probe는 사용을 지양하자
리플렉션 프로브는 비용이 정말 비싸다고 한다.
아까 소개했던 라이트 프로브는 저장 데이터가 숫자지만
리플렉션 프로브는 앞,뒤,좌,우,위,아래 총 6면의 텍스쳐가 다 저장된다고 한다.
저장된 결과물이 라이트 프로브와 확연히 다르며 성능차이가 엄청난다.
*라이트 프로브는 많이 써도 된다고 한다.
반사 프로브 - Unity 매뉴얼 (unity3d.com)
User Interface
캔버스
드로우콜 배칭은 캔버스 단위로 일어난다.
캔버스 안의 UI중에 하나가 바뀌면 나머지 UI들의 데이터도 업데이트가 된다.
(버텍스,폴리곤등을 구성하기 위해)
그래서 동적인 UI, 정적인 UI를 섞어놓으면 비 효율적이며
반대로 동적/정적으로 나누어 캔버스를 구성하는것이 효율적이다.
보이지 않으면 끄자
UI가 화면에 안보이고 저~기 아래에 내려가있다가 특정 상황에만 올라온다고 하더라도 연산은 계속 들어간다고 한다.
안보이면 그냥 꺼버리자..
Graphic RayCaster
캔버스를 만들면 그래픽 레이캐스터라는 컴포넌트가 붙어서 나온다.
우리가 화면에 터치를 하면 UI와 충돌처리를 해야하는데, 그 기능을 담당한다.
만약 UI에 버튼과 같이 상호작용하는 UI가 있다면 켜놔야 하지만,없다면 끄는것이 좋다.
만약 캔버스안에 상호작용하는 UI가 하나 있고 나머지는 그냥 이미지만 띄워놓는 UI라면 캔버스에 있는 그래픽 레이캐스터 컴포넌트를 지워버리고 버튼에만 달아주는게 효율적이다.
(달아놓은 UI만 연산이 들어감)
캔버스에 달아놓으면 캔버스 안에 있는 오브젝트들 전부 레이캐스트 연산이 들어가기 때문이다.
캔버스안에 UI가 한두개면 상관없지만 복잡하면 해주는것이 성능적으로 상당히 좋다고 한다.
또 UI마다 붙어있는 레이캐스트 타겟도 리액션이 없는 UI라면 꺼주는게 좋다.
LayOut Group
우리는 UI를 만들기위해 레이아웃 그룹을 많이 사용한다.
하지만 이 레이아웃 그룹은 동적으로 연산을 하기 때문에 안쓰는게 좋다.
하지만 이게 없으면 UI작업이 상당히 불편하기 때문에 작업할때 쓰고 꺼버리는게 좋다.
많은 양의 그리드뷰는 오브젝트 풀을 사용
영상에서는 페북의 친구목록을 예로 들었다.
만약에 친구가 1,000명이라면 친구 목록창에 UI 1,000개를 생성해서 보여준다면 정말 비효율적일 것이다.
게임을 예로들면 스크롤을 쭉쭉내리는 인벤토리라던지 상점 목록같은 어떤 리스트를 띄우는것을 생각하면 될것같다.
저런 그리드뷰에 표시해야할것이 많다면 오브젝트 풀을 이용(재활용) 해서 쓰도록하자.
사진에 보이는 UI를 제작한다면 이미지 5개 정도 써서 돌리면 될것같다.
디바이스 시뮬레이터
요즘 최신폰들은 노치, 펀치홀 등등 여러 요인에 의해 UI가 가려지는 요소들이 많다.
유니티에서는 디바이스 시뮬레이터를 제공하니 사용하면 좋을것같다.
Introduction | Device Simulator | 2.2.4-preview (unity3d.com)
Audio
오디오는 생각보다 메모리를 많이 먹는다.
PC와 콘솔은 음향 환경이 다양하여 스테레오로 사용해야 하지만
모바일 게임은 모노로 세팅해주는것이 좋다고 한다.
스테레오로 설정해서 메모리 뻥튀기 시키지 말자.
오디오 파일 포맷
오디오의 파일 포맷이 Mp3, Ogg 어떤걸 쓰더라도 유니티에서 다시 압축을 한다.
이 과정에서 Data손실이 발생할수도 있기 때문에 그냥 wav파일을 쓰는것이 좋을수도 있다.
데이터 손실이 그만큼 적기 때문이다.
사운드 압축
예전 버전에서는 아래와 같이 권장했다.
iOS - mp3
안드로이드 - Ogg
왜냐하면 두 파일 포맷이 각각의 네이티브 지원 포맷이였기 때문에
요즘 유니티에서는 그냥 Vorbis로 쓰면 된다고 한다.
유니티엔진은 FMod라는 사운드 엔진을 사용하는데 , FMod에서 디코딩을 할때 멀티코어를 사용, 멀티 쓰레드를 활용해서 압축처리를 하기때문에 네이티브 포맷은 신경쓰지 않아도 된다고 한다.
짧은 사운드는 ADPCM을 사용하면 좋고, 헤르츠를 높일 필요는 없다.
Load Type (중요)
오디오 설정의 로드타입을 보면 여러가지 타입이 있다.
하나하나 살펴보자.
Decompress On Load
- 압축을 다 풀어서 메모리에 올리는 로드방식이기 때문에 메모리가 비효율적으로 사용된다.
- BGM같이 큰 녀석을 이걸로 압축하면 메모리가 상당히 커진다.
- 총알소리, 발소리 등 작은 사운드만 이 설정을 사용하자
Compressed In Memory
- 압축상태로 메모리에 올린다.
- 중간 크기의 사운드를 사용할때 설정하면 좋다.
Streaming
- BGM처럼 용량이 큰녀석들을 하드웨어 데이터에서 동적으로 꺼내서 사용한다.
상황에 맞는 로드타입을 설정 하도록 하자.
재생하지 않을거라면 날리자
볼륨이 0이라도 메모리를 먹고 연산은 처리된다.
쓰지 않을거라면 날려서 메모리를 아끼자.
애니메이션
제네릭 vs 휴머노이드
휴머노이드는 제네릭에 비해 30~50% 정도 메모리가 더 소모된다.
사람 체형만이 존재 할수있는 여러가지 데이터들, Ik세팅 같은것들이 있기 때문이다.
휴머노이드를 써야만 쓸 수 있는 기능들이 있으니 잘 고려하여 사용하자.
물리처리
Prebake Collision meshes
프로젝트 세팅 - 플레이어 - Other Settings - Optimization에 있다.
충돌처리를 하기위한 내용들을 로딩타임에 미리 구워둔다.(바닥, 벽 콜리전같은)
설정하면 성능에 좋다고 한다.
Layer Collision Matrix
프로젝트 세팅 - Physics에 있다.
충돌 처리할 레이어를 미리 설정해둘수 있다.
예를들어 멀티게임에서 플레이어와 플레이어 끼리는 충돌처리가 필요없을수도 있다.
그때 겹치는 부분의 체크를 해제해주면 두 레이어간의 충돌처리는 일어나지 않는다.
이번 개인포트폴리오를 적용할때도 사용하여 연산을 줄였다.
콜라이더
많이들 아시겠지만 Mesh Collider 보다는 기본 도형 콜라이더를 쓰는것이 좋다.
콜라이더가 구체라면 중점과 반지름의 위치가 얼마나 떨어져있는지 등의 간단한 처리를 통해 충돌을 감지한다.
이처럼 다른 기본도형들도 최대한 연산을 간단하게 시킬수있는 방식들이 있다.
하지만 메쉬콜라이더를 사용하면 이런 방식이 아니기 때문에 성능에 좋지 않다.
Physics Debug
유니티에서 지원하는 기능이니 필요하신분은 사용하시면 될 것 같다.
물리 디버그 시각화 - Unity 매뉴얼 (unity3d.com)
마치며
영상이 꽤나 길고 내용이 많아서 공부하고 정리하는데 시간이 많이 소요됐다.
나도 정리하면서 공부가 많이 됐지만 이 글을 보고 도움이 되시는 분들이 많길 바라며 마친다.
시간이 있다면 직접 영상을 보고 공부해보시는것도 좋을것같다.
'게임 엔진 > Unity' 카테고리의 다른 글
유니티 박스(장애물) 높이별 점프 구현해보기 (0) | 2022.08.15 |
---|---|
[유니티 공식 유튜브] IL2CPP란 무엇일까? (0) | 2022.08.09 |
[유니티 공식 유튜브] 모바일 게임 성능 최적화 - 1편 (0) | 2022.08.01 |
유니티 알쓸유잡 - 병목현상 (0) | 2022.07.24 |
2D가 만들고싶어지는 <Lost Crypt> - 2D Sample Project (0) | 2022.07.19 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!