2022년 9월 12일 월요일

Unity Webgl 1.0 2.0

 Unity Webgl 1.0 2.0

테스트 환경 Unity 2020.3.35f
 
Webgl 2.0

포스트프로세싱(가능)
IOS 프레임 저하
linear 사용가능(gamma도 가능)
런타임중에 안티앨리어싱 변경가능

Webgl 1.0

포스트프로세싱(불가능)
IOS 프레임 이상없음
linear 사용 불가능(only gamma)
런타임중에 안티앨리어싱 변경불가능

---

Unity Version

2021 부터 astc 텍스처 지원
2020 이하 버전에서는 ios 모바일에서 사용가능한 압축 텍스처없어서
ios는 포기해야함

---

unity 2021 webgl 2.0 ios 프레임은 테스트 예정

---

ios에서만 webgl 2.0이상 프레임이 안나온다.
아직 ios에서 webgl 2.0이 실험적으로만 제공해주는거와 연관이 있어보이기는 하는데
target api 순서로 android 혹은 pc와 다르게 세팅할려고해도 
ios 사파리 세팅에서 webgl 2.0이 디폴트로 되어있어서 사용자한테 끄라고 할 수도 없고
언제나 unity는 ios가 문제다 ios가





2022년 5월 15일 일요일

URP)CameraStackManager

CameraStackManager

 
https://github.com/do-won-kim/CameraStack

unity urp로 넘어오면서 카메라가 제법 많이 변경이 있었는데 그중 눈에 띄는 점은 depth가 사라진 점과 depth only 가 사라진 것이다.

depth는 stack이라는 리스트로 depth only는 overlay camera로 구현이 가능한데 사실 기존에 UI canvas쪽을 screen space - overlay를 사용하던 프로젝트는 카메라를 여러대 쓰는게 아니라서 별로 신경쓸 점이 없다.

하지만 불행이도 우리 프로젝트는 screen space - camera를 사용중이였고 그에따라 ui camera가 따로 존재해서 overlay camera 기능을 사용해주어야 했다.

canvas를 전부 overlay로 바꾸는것은 이펙트 때문에 작업량이 많아져서 불가능 했기 때문에 기존 형태에서 자동으로 씬 이동이나 overlay 카메라가 켜지거나 꺼질때 혹은 base 카메라가 추가되거나 꺼지거나 할때 자동으로 stack 이라는 리스트를 관리해줄 필요성이 있어서 해당 매니저를 제작하게 되었다.


-camera stacking 스크립트-

사용법은 간단하다 CsCameraStacking 스크립트를 카메라에 부착해주고 depth값을 넣어주면된다.
밑에 bool 값은 overlay로 시작하는 카메라가 아니라 basecamera로 시작하는 카메라에 달린 스크립트에 체크 해주면 된다.

만들때 오래걸리지는 않았지만 이 카메라 변경점은 urp로 넘어올 때 많이 당황했던 부분 중 하나였다.


프로파일링 데이터 엑셀 추출 툴

 프로파일링 데이터 엑셀 추출 툴

https://github.com/do-won-kim/Tools/tree/main/Profiling


한창 최적화를 할 때 테스트 데이터를 저장해서 필요한 장소만 수정 할 수 있도록 엑셀 데이터를 뽑는 툴을 만든 적이 있었는데 이 후 다른 작업을 하게되면서 잊고 있었는데 갑자기 생각이 나서 odin 제거하고 기본 유니티에디터 스크립트로 수정한 버전

유니티 에디터에서 촬영하는 버전과 android와 연결해서 촬영하는 버전 두개가 있다.

자세한 사용방법은 깃허브의 리드미로 대체 합니다.

2022년 5월 9일 월요일

Simple)Material Copy Tool

 Material Copy Tool



간단한 툴 요청이 들어와서 만들게 된 텍스처를 제외한 머테리얼 값 카피 툴이다.

사용법은 별거 없다.
GetAllMaterial 버튼을 누르면 assets 폴더 하위의 모든 머테리얼 중 base와 같은 쉐이더를 가진 머테리얼을 Add한다.

ClearMaterial은 수정할 머테리얼 리스트 clear

ImportProperty 버튼은 기준 머테리얼에 있는 값들을 텍스처를 제외하고 리스트에 덮어씌운다음 에셋들을 모두 저장한다.

퇴근 두시간 전쯤에 요청이 들어와 Odin으로 간단하게 만들었다.


22-05-09 기준 Odin으로 작성된 버전만 커밋되어 있다. 추후 유니티 기본 에디터로 수정 예정
22-05-23 유니티 에디터 코드로만 작성된 버전 업로드

2022년 5월 2일 월요일

Custom Shader GUI Basic Frame

 Custom Shader GUI Basic Frame


유니티에서 쉐이더를 만들게 되면 프로퍼티에 따라서 GUI도 알아서 자동으로 나온다.
하지만 보통 사용하게 되는 그래픽팀에서는 작업을 위해서 여러 수정을 요청 할때가 많고 그럴때는 shadergui 라는 스크립트를 상속받아 프로그래머가 직접 커스텀을 해줘야한다.

                                -URP 기본쉐이더인 Lit shader의 custom gui-

지금까지는 baseshadergui 라는 스크립트를 상속받아 적당히 만들었지만 이번에 몇개의 큰쉐이더들의 gui를 크게 바꿔야 할 때가 오자 상당히 코드도 난잡해져있고 수정하기 복잡해져서 갈아엎고 새로 만들었다.

-ShaderGUI를 상속받은 CustomShaderGUI-

심플하게 필요한 기능과 필수로 구현해야할 부분만 따로 빼서 만들었다.
간단하게 설명하자면 MakeFoldoutHeaderGroup은 foldout 형식의 gui를 만들게 해준다.
header의 이름과 짤렸지만 안쪽에 들어갈 함수를 넣으면 아래와 같은 형식의 gui가 만들어진다.

DoPopup은 이름 그대로 팝업 형식을 만들때 쓴다 유용하기 때문에 baseshadergui에 있는 것을 그대로 가져왔다.

쉐이더에서 사용할 모든 프로퍼티들은 FindProperties에 넣으면되고 MaterialChanged에서는 주로 쉐이더 키워드들을 컨트롤 할때 사용하고 DrawSurceinputs에 원하는 순서로 프로퍼티들을 정렬하면된다.

실제 사용 gui 스크립트는 최상단의 링크에 있는 LitSpecularWithMaskGUI.cs를 참고

아무튼 gui 스크립트들이 대부분 좀 난잡했는데 깔끔해지니 기분이 좋다.







2022년 2월 16일 수요일

URP 7.6.3 -> 10.8.1 Specular가 미묘하게 달라져있다.

 URP 7.6.3 -> 10.8.1 Specular가 미묘하게 달라져있다.

Unity 버전을 2019에서 2020으로 올리게 되면서 URP 버전을 강제적으로 올리게 되었는데 몇몇 프랍들이 색이 좀 달라져서 수정하게 되었다.

#1
inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
#ifdef _SPECULAR_SETUP
    half reflectivity = ReflectivitySpecular(specular);
    half oneMinusReflectivity = 1.0 - reflectivity;
    //half3 brdfDiffuse = albedo * (half3(1.0h, 1.0h, 1.0h) - specular);
    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = specular;
#else
    half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
    half reflectivity = 1.0 - oneMinusReflectivity;
    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic);
#endif

    InitializeBRDFDataDirect(brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
}

#2
half3 EnvironmentBRDFSpecular(BRDFData brdfData, half fresnelTerm)
{
    float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0);
    //float c = surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm);
    float3 c = surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm);

    return c;
}

1번은 그렇다치고 2번은 half3의 specular을 lerp해서 float 값으로 반환해서 색이 이상하게 나오는 문제인데 의도한거 같지는 않고 버그가 아닐까 생각한다.

#3
float DistanceAttenuation(float distanceSqr, half2 distanceAttenuation)
{
    // We use a shared distance attenuation for additional directional and puctual lights
    // for directional lights attenuation will be 1
    float lightAtten = rcp(distanceSqr);

//#if SHADER_HINT_NICE_QUALITY
    // Use the smoothing factor also used in the Unity lightmapper.
    half factor = distanceSqr * distanceAttenuation.x;
    half smoothFactor = saturate(1.0h - factor * factor);
    smoothFactor = smoothFactor * smoothFactor;
//#else
    // We need to smoothly fade attenuation to light range. We start fading linearly at 80% of light range
    // Therefore:
    // fadeDistance = (0.8 * 0.8 * lightRangeSq)
    // smoothFactor = (lightRangeSqr - distanceSqr) / (lightRangeSqr - fadeDistance)
    // We can rewrite that to fit a MAD by doing
    // distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)
    // distanceSqr *        distanceAttenuation.y            +             distanceAttenuation.z
    half smoothFactor = saturate(distanceSqr * distanceAttenuation.x + distanceAttenuation.y);
//#endif
    return lightAtten * smoothFactor;
}

3번은 색이 이상하게 나오는 수준이 아니라 아예 깨져서 나왔는데 원래 2019에서 모바일과 에디터 환경에서 색이 다르게 나오는게 문제가 있어서 분기문을 지우고 SHADER_HINT_NICE_QUALITY 쪽 코드를 사용 했는데 2020에서는 오히려 파랑색쪽 코드를 사용하니깐 기존 색이 유지되고 이상하게 색이 깨지는 문제도 발생하지 않아서 수정했다.

일단 정상적으로 플레이 가능하도록 수정하는 작업이 끝나면 원인도 찾아봐야겠다.

2021년 12월 15일 수요일

Coroutine과 Task를 사용할 때 DeadLock

Coroutine과 Task를 사용할 때 DeadLock


버그 리포트를 처리 하던 중 처음 보는 유형의 버그를 처리해서 기록하려한다.

버그의 내용은 아래와 같다.

1.아이폰7에서 플레이를 했다.
2.게임에 들어간 직후 자동사냥을 한다.
3.게임이 멈춘 후 시간이 지난 후에 크래쉬가 난다.

시트의 내용을 처음 봤을 때 오래 걸릴 것이라고 바로 생각했다.

지금까지 경험으로 봤을 때 특정 기종 + 크래쉬 조합은 찾기가 제법 까다로웠기 때문이다.

직접 테스트 결과 100% 발생하는 것은 아니고 간혈적으로 일어 난다는 것을 깨닫았다.
혹시 다른 기종도 문제일까 ipad 12.9 5th로 여러 차례 테스트를 해봤지만 재현이 되지 않았다.
Android도 갤럭시 노트 10+로 테스트를 여러 차례 해보았지만 재현이 되지 않았다.

특정 기종 + 간혈적 + 크래쉬라는 3중고에 빠져서 잠시 패닉이 왔지만
기적적으로 몇일 전에 테스트 했을 때가 기억이 났다.

이 문제를 해결하기 전에 나는 메모리를 최대한 줄일 수 있는 것들을 테스트 하고 있었고 그 중에 이펙트 풀을 하 옵션에서는 로드를 안하는 것을 테스트를 했었는데, 단순히 미리 로드만 안하도록 막자 유니티에서 게임이 멈추는 것을 경험했었다.

다만 이 때는 멀쩡한 코드를 내가 잘못 수정했기 때문에 발생한 현상이라고 생각했고, 나에게 메모리를 줄일 수 있는 시간이 넉넉하지 않아서 로드 뿐만 아니라 호출하는 부분도 모두 막아서 테스트를 했었다.

아무튼 결과 적으로 이 과정에서 프리징을 겪었던게 큰 도움이 됐다. 그렇지 않았으면 아마 나는 이 문제에 더 많은 시간을 투자했어야 했을 테니깐

해당 스크립트로 찾아가 게임이 멈출만한 코드를 모두 찾은 후에 소거법으로 현상을 재현하는 것에 성공했다. 사실상 여기서 문제 해결은 90% 끝난 것이다.

다만 왜 게임이 프리징이 걸리는지 생각하는데 좀 더 많은 시간이 걸렸을 뿐

자세한 설명은 뒤에 더 하겠지만 요약하자면 'Unity에서만 작업을 하면서 비동기적인 작업을 할 때 Coroutine밖에 사용할 일이 거의 없다보니 Task가 어떻게 동작하는지 알지 못했기 때문이다' 

뒤를 돌아보니 인터넷에 자주 나오는 케이스이며 매우 쉬운 문제였다.
아래는 문제가 일어났던 코드를 간단히 요약한 코드이다.

    //이펙트 풀링
    Dictionary<int,GameObject> dic;

    //특정 상황에 사용하는 오브젝트
    GameObject go;

    //게임에 들어갈때 이펙트들을 비동기로 풀링한다.
    public void Init()
    {
        StartCoroutine(LoadObject());
    }
    IEnumerator LoadObject()
    {
        for (int i = 0; i < 100;++i)
        {
            var v = Addressables.LoadAssetAsync<GameObject>(i);
            yield return v;
            dic.Add(i,v.Result);        
        }
    }

 // 이펙트를 가져오는 코드 풀링이 이미 되어 있으면 오브젝트를 바로 가져오고 없으면 생성
 // 필자가 만든 코드는 아니지만 추측해보건데 IEnumerator를 사용 못하는 코드에서도 간단히 사용 할 수 있어서 이런식으로 작성한 것으로 보인다.

    async Task<GameObject> Load(int id)
    {
        if (!dic.ContainsKey(id))
        {
            var v = await Addressables.LoadAssetAsync<GameObject>(id).Task;
            dic.Add(id, v);
        }

        if (dic.ContainsKey(id))
        {
            return dic[id];
        }

        return null;
    }

  // 게임을 멈추는 코드
    IEnumerator Set(int id)
    {
        var task = Load(id);
        yield return task;

        //프리징
        go = task.Result;
    }



결론부터 이야기 하면 예를 들어 유니티에서 자주 사용하는 Coroutine에서 yield return Addressables.LoadAssetAsync를 하면 해당 구문은 load가 끝날 때까지 기다리고 완료가 되면 return되서 넘어간다.

하지만 Task의 경우(위 코드의 async Task<GameObject> Load) 해당 코드안에 Task는 작업이 완료가 되지 않아도 완료가 되지 않았다는 내용의 Task가 return된다.

그럼 결국 yield return task; 구문은 아무런 영향이 없이 task.Result를 실행하게 되고 비완료된 Task에서 Result를 실행할 경우 컨텍스트를 동기적으로 차단하게 되고 결국 그 후에 
Task Load가 완료 되더라도 이것이 컨텍스트가 이용가능 할 때까지 기다리기 때문에 교착상태 즉 deadlock이 발생해서 유니티의 메인쓰레드가 멈춰버린것이였다....

그럼 다시 처음으로 돌아가서 왜 지금까지 재현이 되지 않다가 아이폰7에서 재현이 되었나?
- 게임에 들어가서 Load함수를 호출 할 때 이미 로드가 된 상태면 바로 반환을 해주기 때문에 task.Result를 사용해도 아무런 이상이 없다. 즉 휴대폰에 성능이 좋은 폰이거나 게임에 들어가서 바로 혹은 빠르게 함수를 사용 하는 케이스가 아니면 보기 힘든 문제였다는 것이다.

간혈적으로 일어난 이유
- 해당 함수는 몬스터가 죽고 드랍아이템이 나올 때만 호출되는데 게임에 접속 후 빠르게 사냥을 했을 해도 드랍아이템이 나오는 것은 확률적이였기 때문

크래쉬가 난 이유
- 오래 동안 멈춰있어서 OS에서 앱을 죽였다.

그럼 해결을 어떻게 하면 되느냐 Set 함수가 async 였다면 await로 끝날 일이지만 Coroutine으로 되어 있었기 때문에 아래처럼 코드를 수정했다.

    IEnumerator Set(int id)
    {
        var task = Load(id);
        yield return new WaitUntil(() => task.IsCompleted);
        go = task.Result;
    }


간단하다 task가 완료 될 때까지 정상적으로 기다리게 수정해주면 해결이 된다.

게임이 바로 크래쉬가 나는건 여러번 경험해봤지만 오버플로를 제외하고는 멈추는것은 보지 못했는데 빨리 찾아서 다행인 버그였다.

2021년 8월 11일 수요일

Addressable 1.18.15 번들 갱신이 되는 특이한(?) 경우

 Addressable 1.18.15 번들 갱신이 되는 특이한(?) 경우

addressable 1.16.19 버전의 오류 때문에 1.18.15로 업데이트 후 매일 번들을 업데이트 하면서 테스트를 하면서 번들이 신기하게 갱신되는 경우가 있어서 포스팅을 하게 되었습니다.

-번들 기본 세팅-

준비물 : FBX파일과 해당 FBX의 mesh를 사용하는 prefab 하나를 각각 다른 번들에 위치 시켜 줍니다.


-번들 생성-

당연하게도 3개의 번들과 catalog가 나옵니다.

여기서 FBX파일의 번들위치를 옮겨보겠습니다.


-FBX에셋을 FBX번들에서 packed assets으로 이동-

화면 상에서는 FBX번들과 Packed Assets번들 두 번들에서의 수정만 있었으므로 2개의 번들이 새로 갱신 될줄 알았지만?


-결과-
??
신기하게 FBX파일의 mesh를 들고 있는 Prefab번들도 같이 갱신 됐습니다.
아마 연결된 에셋의 어드레서블 정보도 같이 저장하는게 아닐까 싶습니다.


-두번째 테스트-

두번째 테스트는 좀 더 신기합니다.

준비물 : mesh collider에 mesh를 넣고 프리팹화 하여 FBX파일과 다른 번들에 넣어 줍니다.


-번들 생성-



-수정 사항-

그 뒤에 해당 프리셋의 옵션을 변경 해주고 다시 재빌드를 하면?

당연히 프리팹만 갱신 될줄 알았지만


-결과-


???????
연결되어 있던 FBX파일의 번들또한 갱신되는 것을 확인 할 수 있었습니다.
이건 첫번째와 달리 왜 이렇게 되었는지 생각나는게 없네요

일단 두 경우 모두 그렇게 자주 있는 일은 아니여서 번들 관리는 블로그에 있는 툴로 여전히 파일 종류 별로 관리중입니다만

좀 더 갱신을 덜하고 싶으신분들은 참고 해볼 수 있을것 같습니다.

1.18.15 테스트를 좀 더 하게 될거같은데 이러한 일이 있을 때 추가로 올리도록 하겠습니다.


-----2021-08-18

Addressable에 등록하지 않은 스프라이트가 addressable에 등록되어 있는 아틀라스에 추가 되서 아틀라스가 갱신되면 해당 스프라이트가 있는 번들 또한 같이 갱신 됩니다.





2021년 8월 5일 목요일

Addressable 1.16.19 Bug

 Addressable 1.16.19 Bug


버그도 많이 잡혀서 잠잠해질 무렵 자사 게임의 번들이 업데이트하는것에 비해 더 많이 업데이트되어서 다운 받는다는 제보가 들어와서 조사에 들어갔습니다.

FBX파일을 모아둔 번들이 실제로 변경된 번들 수(2x)에 비해 더 많은 양(8x)이 변경되서 갱신되는걸 확인을 이것저것 해봤는데

그냥 addressable 버그였습니다;;

unity 2019.x 버전을 사용하시는 분들의 경우 기본 addressable의 버전이 1.16.19 이실겁니다.

-테스트용 번들-

테스트 방법은 이렇습니다.
FBX파일이 들어있는 번들하나와 해당 FBX의 mesh를 사용하는 프리팹을 다른 번들에 넣고 빌드를 합니다.


-테스트 결과-

정상적으로 catalog와 번들2개가 나온것을 확인 할 수 있습니다.


-테스트용 번들2-

그런데 이때 FBX번들이 들어가 있는 번들에 아무 에셋이나 하나 추가를 한다면?
예상되는 결과는 FBX그룹만 바뀌었으니 새로운 catalog와 해당 그룹만 새로 나와야합니다.


-테스트 결과2-
??
예상과 다르게 prefab번들 까지 갱신되어서 나오는 것을 확인 할 수 있습니다.
이것 말고도 애니메이션이 들어가 있는 FBX의 애니메이션을 사용하는 애니메이터를 다른 번들에 위치 시키고 FBX가 들어가 있는 번들에 리소스를 추가하거나 제거하면
애니메이터가 들어가 있는 번들까지 같이 갱신되는 것을 확인 할 수 있습니다.

아마 확인 하지 못한 케이스도 많을 것으로 예상 됩니다.

해당 버그의 수정 방법은 아직 업데이트 밖에 없는 것 같습니다.
가장 최신 버전인 1.18.15 버전에서 같은 테스트를 해봤습니다.

-1.18.15 버전에서 재 테스트-

정상적으로 FBX번들만 갱신되는 모습을 볼 수 있습니다.

다만 유니티에서 2019.x 버전에서 인증한 버전은 아직 1.16.19 버전까지 이므로 변경 후에 실제 apk에서 정상 동작하는지 테스트는 따로 해보셔야합니다.




2021년 8월 2일 월요일

GC Alloc를 줄이자

 GC Alloc를 줄이자


CPU 최적화를 최대한 해주기 위해서 GC Alloc를 최대한 줄일려고 노력을 한 적이 있었다.
그 중에서 GC Alloc를 천 단위로 계속 호출해서 프레임에도 심한 영향을 준 케이스와 백 단위로 계속 호출해서 약한 영향을 준 케이스가 2개 있어서 적어볼려고 한다.

첫번째 심했던 케이스는
-string 끼리 더하는 코드 예시-

-해당 코드 프로파일링 결과-

공부를 하다보면 누구나 한번쯤 들어봤을 string 끼리 더하는 코드였다.
발견 당시에 2년 넘게 개발중인 게임에서 이런 식의 코드가 있었다는게 좀 황당했던 기억이 남아있다.

형태에 따라 stringbuilder를 사용해도 상관없지만 당시에는 구조를 조금 바꿔서 아예 지워버렸던 것으로 기억한다.

의외로 별거 아니라고 생각하는 사람도 있는데 update문이나 자주 사용하는 함수의 for문등에 사용 할때는 문제가 될 수 있으므로 그냥 해당 형태를 최대한 피하는 것이 좋다.

두번째 케이스는 
-AddRange와 FindAll을 반복해서 사용 하는 형태-


-해당 코드 프로파일링 결과-

두번째도 마찬가지로 update문에서 돌아가는 코드였는데 addrange와 findall을 사용하는 형태였다.
사실 개인적으로 저것을 자주 사용하지 않아서 GC Alloc를 유발하는지 모르고 있었는데 프로파일링을 하다보니 알게 되었다.
해당 코드의 수정방법은 다소 무식하게 하면 간단하다.

-2중 For문과 if조건문으로 수정한 코드-

                                           -해당 코드 프로파일링 결과-

좀 코드가 길어지더라도 for문을 한번 더 사용해서 같은 결과만 나오게 해주면 해결된다 그리고 실제 코드에서 리스트 초기화하는 부분도 따로 빠져있지 않았어서 불편해서 수정했다.

회사에서 이렇게 프로파일링을 해서 코드들을 수정하고 나서부터는 이러한 것들이 한번 호출하고 끝나는 코드에서는 별 상관 없지만 습관이 생긴다고 평소에도 의식하면서 코드를 짜게 만들어 주었다.

혹시 이글을 보게 된다면 자신의 게임의 GC Alloc call을 한번쯤 확인해보는 것은 어떨까

2021년 7월 21일 수요일

Unity Text(UIVertex[]) Memory Leak

 Unity Text(UIVertex[]) Memory Leak


메모리 검사를 하던 중 씬을 이동 할때 마다 uivervex[] 라는 항목이 계속 증가하는 것을 확인했는데 따로 text를 저장하는 코드가 없어서 많이 해매였다.

-테스트용 씬 스크립트-

간단한 테스트용 스크립트를 만들었다 폰트를 변경하고 버튼을 누르면 B씬을 불러오고 그 씬에서도 폰트를 변경하고 다시 A씬으로 돌아오는 것을 반복하는 스크립트이다.


-처음 프로파일러에 잡히는 uivertex-

-씬을 여러차례 이동 후에 잡히는 uivertex-

테스트 씬에서 재현을 한 후에 레퍼런스 연결을 따라가보니 FontUpdateTracker 라는 곳에서 해당 텍스트들을 전부 들고 있는 걸 확인했는데 해당 스크립트는 유니티 스크립트라 유니티에서 함수 내부를 확인하지는 못했고 깃허브를 이용해 스크립트를 찾을 수 있었다.(https://github.com/liuqiaosz/Unity/blob/master/UGUI%E6%BA%90%E4%BB%A3%E7%A0%81/UnityEngine.UI/UI/Core/FontUpdateTracker.cs)

보자마자 (static Dictionary<Font, List<Text>> m_Tracked) 해당 딕셔너리가 문제가 있는게 보였고 Add하는 부분과 Remove하는 부분을 찾아보니 몇 군데 있었지만 중요한건 enable과 disable 그리고 font를 수정할 때 add를 하거나 remove를 하는데 여기서 문제였던게

우리 게임에서는 폰트를 코드로 추가해주는데 해당 컴포넌트가 들어가있는 오브젝트가 꺼져있든 켜져있는 수정을 해주는데 이때 add가 되고 만약에 해당 오브젝트가 켜지기 전에 씬을 이동할 경우 disable이 동작하지 않기 때문에 딕셔너리에서 빠지지 않아서 계속 쌓이는 것이였다.

원인을 찾았으니 수정을 해주면된다.

-수정된 폰트 교체 코드-

여기서 FontUpdateTracker.UntrackText는 딕셔너리에서 텍스트를 빼주는 역할을 한다. 꺼져있는 텍스트의 경우 수동으로 처리를 해주는것이다.

수정 후에 재 테스트를 하였다.

-재 테스트 결과-

위에 있던 이미지와 같은 이미지가 아니다 이번에는 확실히 여러번 이동 했음에도 메모리가 정상적으로 빠지는 것을 확인 할 수 있었다.

해당 내용이 비록 작은 게임에서는 그렇게 큰 메모리 문제로 나타나지 않을 수 있으나 텍스트가 많을 경우 그리고 씬을 자주 이동할 경우 심하면 계속 같은 양 만큼 증가 하기 때문에 10mb 20mb 30mb 씩 늘어나는 경우도 있었는데 이러한 내용을 알려주지 않은 유니티에 당황스럽기도하고(어디 있는데 못찾은 걸 수도 있다.) ondestroy에 remove를 넣거나 add할때 if문 하나만 걸어줘도 방지 할 수 있는 걸 지금까지 방치한것도 문제가 있어보인다.

해당 사항은 추후에 다시 정리해서 버그리포트에 올릴 계획입니다.


















2021년 7월 18일 일요일

Unity 2019.4.28f 버그?

 이번에 유니티 버전을 4.22f에서 4.28f로 올리면서 지난버전에서는 없었던 크래쉬 이슈가 다량으로 올라왔다.

확인 결과 ios나 android 스마트폰에서는 해당 증상이 없었으나 에뮬레이터에서 발생한 것을 알 수 있었고 매번 크래쉬가 나지는 않고 간혈적으로 발생했다. 

크래쉬 로그를 뽑아서 확인 해보니깐 모두 같은 함수를 타다가 크래쉬가 났는데 유니티 자체 함수에서 발생하는 것을 알 수 있었다 바로 FindObjectOfType 와 FindObjectsOfType였고 매개변수만 다른 몇몇 개로 수정해서 테스트 해봤으나 여전히 크래쉬가 발생했고 결국 할 수 있는 선택지는 해당 함수를 제거하고 같은 기능을 다른 방식으로 구현했다.

그 결과 크래쉬는 발생하지 않았다.

특이한 점은 새로운 프로젝트를 만들어서 해당 함수를 사용했을 때는 크래쉬가 발생하지 않았다는 점인데 무언가 그 타이밍에 돌던 다른 것과 충돌하는 것일 수도 있을거 같긴하다.

아무튼 유니티는 버전이 바뀔 때마다 새로움을 준다.

---

4.28f 업그레이드 이후에 GetPixels 함수에서도 간혈적으로 크래쉬가 일어나 확인중

-21-07-19) GetPixels32 로 바꾼 후에 크래쉬는 제거 됐으나 GetPixels32를 쓰고 있는 다른 스크립트에서 memory leak이 발견되어 추가 확인중

2021년 7월 16일 금요일

Bloom Shader 최적화

 Bloom Shader 최적화

유니티의 다양한 기본 포스트 프로세싱 중에 Bloom이라는 기능이 있다.

광원에 좀 더 효과를 주는 기능인데 잘 조절 하면 이쁘게 나오지만 성능에 제법 부하를 주는 친구다. 특히 게임이 모바일이라면 더욱 더

우선 Bloom 효과를 적용하고 프레임 디버그를 이용해 확인 해보면 다음 과 같은 결과를 볼 수 있다.
-프레임 디버그로 확인한 Bloom-

여러장의 텍스처를 이용해 찍어내는데 메모리도 있지만 draw call도 상당히 증가하고 덕분에 프로파일러를 통해 확인해보면 

-프로파일러로 확인한 Bloom-


괜찮은 성능의 pc에뮬로 기본 씬에서 찍어도 0.5~1.0ms를 차지한다.
실제 개발중인 모바일게임 프로파일링 당시에는 많게는 8ms를 차지 하기도 했다.(8ms면 30프레임 기준으로 혼자서 1/4을 차지 하는 수준이다.)

그러면 이러한 문제를 어떻게 해결할 수 있을까
답은 간단하다 Bloom이 찍는 텍스처 수를 줄이면 된다.

-PostProcessPass-

Package - Universal RP - Runtime -  Passes - PostProcessPass.cs
해당 스크립트의 50번째 줄 정도를 보면 k_MaxPyramidSize라는 변수가 있다.

해당 변수가 Bloom효과를 적용할 때 횟수를 정해준다 과감하게 2로 줄인다.


-2로 줄인 후의 결과-

Draw call은 22 -> 4로 감소 하였고, 0.5ms -> 0.1ms로 감소 하였다.
실제 모바일게임에서는 8ms에서 1ms까지 감소하는 효과를 보여주었다.

물론 해당 방법을 사용하게 되면 적용되는 Bloom효과가 다소 다르게 보여지기 때문에 수치 조절은 다시 해줘야한다.

해당 기능 성능이 다소 부담되기도 하고 줄여도 어느 정도 수치 조절로 커버가 가능한 모바일 게임에서 유용하게 사용 할 수 있을 것으로 보인다.

해당 수정사항들은 패키지 파일의 스크립트를 수정 하는 것이니 패키지 파일을 Package 폴더로 따로 이동해 custom하게 사용을 해야한다.(그렇지 않으면 유니티를 재부팅하면 원상복구된다.)

2021년 7월 13일 화요일

쉐이더 메모리 최적화

쉐이더 메모리 최적화

쉐이더에는 multi_compile와 shader_feature 라는 기능이 있다.

쉐이더의 베리언트를 나눠서 하나의 쉐이더에서 여러 기능을 구현 할 수 있도록 해주는 것인데 둘의 차이라면 multi_compile는 모든 분기가 빌드에 포함되고 shader_feature 사용중인 분기만 빌드에 포함된다는 것이다.

그러면 shader_feature 만 쓰면 된다고 생각 할 수도 있지만 런타임중에 분기가 바뀌는 경우에는 shader_feature 의 경우 빌드에 포함되지 않았기 때문에 동작이 제대로 되지 않는다.

그렇다고 multi_compile를 전부 쓰면 쉐이더의 베리언트가 배로 계속 늘어 무식하게 커지게 된다.

pc게임이라면 어느정도 사이즈는 신경 쓰지 않을 수 있지만 회사에서 모바일 게임을 개발 중이기 때문에 민감하지 않을 수 가 없었다.

-Unity Urp Lit Shader-

빌트 인 쉐이더의 standard 쉐이더를 대체하는 urp의 기본 lit 쉐이더의 기본 베리언트들이다.
일반적인 게임에서는 그냥 사용해도 별 무리가 없으나 해당 쉐이더를 개조해서 게임에서 사용중이 였기 때문에 multi_compile로 더늘고 최종본에서는 최대 140mb 가까이 메모리를 먹었었다.

이 경우 필요없는 베리언트들을 줄이는 작업을 해주면 좋다.

-수정된 Lit Shdaer-

_MAIN_LIGHT_SHADOWS_CASCADE
캐스케이드 관련 키워드이다 모바일게임이다 보니 사용하지 않아 지웠다.

_ADDITIONAL_LIGHTS
_ADDITIONAL_LIGHTS_VERTEX는 사용하지 않아서 지웠다.

_ADDITIONAL_LIGHT_SHADOWS
추가 라이트의 그림자는 지원하지 않아서 지웠다.

_MIXED_LIGHTING_SUBTRACTIVE, DIRLIGHTMAP_COMBINED, LIGHTMAP_ON
라이트맵 관련 키워드인데 라이트맵을 사용하지 않아 지웠다.

multi_compile_instancing
GPU instancing 기능을 사용할 때 사용하는 키워드인데 어차피 srp를 사용할거기 때문에 제거했다.(terrain의 경우 srp적용이 안되서 terrain lit 쉐이더를 수정할 경우 남겨둔다.)


기본 lit 쉐이더에서 해당 작업을 진행 할 경우 베리언트 수가 9로 줄어 들게 된다.
모든 기본 쉐이더에서 해당 작업을 진행하면 제법 쏠쏠한 이득을 챙길 수 있다.

해당 수정사항들은 패키지 파일의 스크립트를 수정 하는 것이니 패키지 파일을 Package 폴더로 따로 이동해 custom하게 사용을 해야한다.(그렇지 않으면 유니티를 재부팅하면 원상복구된다.)








2021년 7월 12일 월요일

유니티 어드레서블 그룹 정리 툴

어드레서블 정리 툴 

사용 하던 유니티에 어드레서블을 적용하고 나서 몇몇 문제들이 있었습니다.

그중에 내가 직접 넣은 에셋말고 그 에셋에 포함되서 들어오는 에셋들 때문에 그룹 업데이트가 빈번하게 되서 암시적으로 오는 에셋들을 전부 분리해서 에셋으로 빼니깐 양이 많아져서 그룹 정리하는게 일일이 손으로 하기 힘들어서 툴을 만들게 되었습니다.

암시적으로 들어가 있는 에셋들을 분리 해주는 툴은 어드레서블 패키지를 한줄 수정해야하고 좀 불안정해서 그룹핑 하는 툴만 올려봅니다.


 -어드레서블 그룹핑 툴-

사용법 및 스크립트는 깃허브에 있습니다.

에디터 폴더에 넣으시면됩니다.

github : https://github.com/do-won-kim/AddressableGrouping


2021년 7월 10일 토요일

어드레서블과 아틀라스(레거시) 최적화 문제

어드레서블과 아틀라스(레거시) 최적화 문제


-테스트 용으로 빌드한 apk의 메모리 프로파일러 같은 아틀라스가 3개 올라와있다.-


 메모리 최적화를 위해 메모리 프로파일링을 하던 도중 아틀라스가 중복되서 올라오는 현상을 발견했다.

워낙 큰 아틀라스라 처음에는 여러장 있는 건줄 알고 있었으나 같은 아틀라스인 걸 확인 후에 레거시는 새로나온 V2처럼 수동조작으로 하는게 아닌데 왜 중복으로 올라가는지 찾아본 결과

어드레서블을 이용해 번들을 나눌 때 파일 형식 별로 갯수와 용량을 맞추었는데 아틀라스가 같은 스프라이트가 번들이 나눠져 있을 경우 나눠진 만큼 아틀라스도 중복되서 올라가는 것이였다.

-같은 아틀라스를 쓰는 스프라이트를 같은 번들에 모두 넣었을 때 메모리가 하나만 올라갔다-

스프라이트의 경우 아틀라스가 없으면 일정 갯수대로 있으면 아틀라스 별로 번들 그룹을 정하는 것으로 해결


- 참고1 테스트용 apk를 만들면서 이상한 점을 발견했는데 빈 프로젝트에 아틀라스 패킹을 하고 번들을 찢어 놓으면 메모리가 여러개 올라가지 않고 하나만 올라갔다는 점이다.

이상해서 이것 저것 테스트 하던 와중 번들내에 있는 스프라이트를 하나 어드레서블 로드를 했을 때 갑자기 여러장이 올라가서 이게 문제 인가 했지만 테스트 했던 코드를 모두 지우고 다시 빌드 했을 때도 여전히 여러장의 메모리가 올라가는 것을 확인했다.

유니티는 정말 봐도 봐도 신기하다는걸 이번 테스트로 다시 확인 했다.

결국 정확한 원인은 파악하지 못했지만 해결 방법은 같은 아틀라스를 쓰는 스프라이트들은 같은 번들에 넣는 걸로 해결이 가능하였다.

- 참고2 해당 문제는 apk빌드 후에 프로파일링을 해야 발견 됐다 에디터에서는 간혹 캐쉬가 남아서 한두개 더 찍히기도 했지만 껐다가 키면 다시 아틀라스가 하나씩만 메모리에 올라가는 것을 확인 하였다.