유니티 그래픽스 최적화 스타트업책을 참고해서 작성하였습니다.

 

텍스처를 압축해야 하는 이유?

무한한 메모리를 가지고 있는 기기는 없기 때문이다. 그렇기에 텍스처 압축 기술을 통해 텍스처 역시 디스크나 메모리의 크기를 절약해야 한다. 

 

메모리 이슈

텍스처는 수많은 비트 데이터들도 이루어진 이미지 데이터이다. 그렇기에 GPU가 이 데이터를 이용하려면 대역폭이 필요하고 성능이슈가 발생하게 된다. 우리가 원하는 것은 적은 메모리로 합리적인 품질을 내야한다. 그렇기에 텍스처 데이터 사이즈를 관리해야한다.

하지만 텍스처 사이즈는 대역폭만의 문제만이 아니라 메모리 문제도 있다. 복잡한 씬을 렌더링하거나 데이터 사이즈가 큰 텍스처들을 한꺼번에 많이 올려두는 경우들이 있다. 이런 문제는 PC보다는 적은 메모리에 모바일 디바이스에서 더 큰 문제로 다가 올 수 있다.  

패키지용량 이슈(모바일)

모바일 앱마켓에서 게임을 받을 때 WIFI에서만 다운로드가 가능하거나 권장하는 문구를 본적이 있다. 이렇게 큰 용량의 앱들을 받게 될때 거부감이 들때가 있다. 특히 WIFI가 없는 곳에서는 더더욱! 그렇기 때문에 초기에 모두 받지 않고 설치 이후 패치등으로 데이터드를 다운로드 받게 하는데 이 역시도 너무 큰 용량이라면 거부감이 들 수있다.   

압축방식

이미지 압축은 크게 손실압축과 비손실 압축으로 두 종류가 존재한다. 비손실 압축은 원본 이미지를 그대로 유지하면서 데이터만 압축하는 방식이며, 비손실 압축은 원본 이미지의 퀄리티를 어느정도는 훼손하는 대신에 데이터 압축 비율을 높여서 데이터 크기 절약에 집중하는 방식이다. JPG와PNG는 GPU가 바로 읽어 들일 수 없다. 그렇기 때문에 텍스처를 위한 별도의 압축 방식을 이용해야한다. 유니티는 어떤 이미지를 Import하면 원본 이미지 포맷이 PNG인지 JPEG인지 등은 상관없이 특정 포맷의 이미지로 변환하여 사용한다. 텍스처 압축은 플랫폼별 특성이 다르므로 플랫폼마다 적절한 압축 포맷을 설정해주는게 좋다. 

압축종류의 대한 자세한 설명

https://ozlael.tistory.com/84

반응형

1. 그림자

빛이 지나가는 경로에 불투명한 물체가 존재하여 빛이 통과하지 못해 생기는 어두운 부분이다. 게임에서 그림자는 모든 물체를 입체감을 더해주는 요소이다. 그림자가 있으면 사물의 공간산 위치를 인지하기 쉽게 만들어주기 때문이다. 유니티는 그림자 또한 쉽게 표현하도록 기능이 구현되어있다. 

2. 그림자의 원리

유니티는 쉐도우맵기법을 사용하고 있다.  쉐도우 맵은 깊이 텍스처 값을 이용한 기법이다. 이 기법은 모든 굴곡에서 대응하고 셀프-쉐도우가 처리되는 등 높은 퀄리티를 보여준다.

쉐도우 맵의 원리? (포워드 렌더링)

먼저 뎁스 텍스처를 생성한다. 카메라를 통해서 현재의 씬이 렌더링 되는 정보를 렌더링 하는 과정이다. 렌더링 될 픽셀의 컬러 대신 카메라로부터 픽셀의 위치까지의 거리를 렌더링 한다. 카메라의 가까이 있는 픽셀일 수로 록 검은색, 멀 수록 흰색이다(그림자에 영향을 받는 모든 오브젝트들을 렌더링). 그 후 그림자 처리를 위한 별도의 버퍼가 필요하다. 이 버퍼에는 광원에서 바라보는 오브젝트의 픽셀의 깊이를 저장한다. 이 버퍼가 그림자를 위한 정보를 담아서 쉐도우 맵이다. 

그림자의 깊이를 저장하는 버퍼를 만든 후 픽셀 쉐이더에서 깊이를 비교하는 과정을 진행한다. 또한 넓은 영역을 커버하기 위해 여러 구역으로 나누기도 하고 계단 현상을 없애기 위해 여러 번 샘플링하여 필터링 처리를 하는 부가 기능들을 추가한다. 그러다 보니 그림자가 렌더링 비용 중 많은 부분을 차지하게 된다.

뎁스 텍스처와 쉐도우 맵이 완성되면 뎁스 텍스처에 있는 정보와 쉐도우 맵에 있는 정보를 비교하여 그림자 영역을 계산한다. 뎁스 텍처에 있는 픽셀들을 순회하면서 해당 픽셀을 광원으로부터의 거리로 변환하여 비교한다.

2. 어떤 그림자를 이용해야 할까

물론 실시간으로 그림자를 활성화시키고 유니티의 그림자 구현능력을 믿어보면 간단하고 편하다. 하지만 그림자를 렌더링 하는 것은 높은 성능을 요구하기 때문에 이렇게 그냥 맡기는 것은 무책임하다. 그렇기에 만들려는 게임이 무엇인지, 어떤 디바이스를 타깃으로 만들 건지 생각하고 그에 맞는 세팅이 필요하다. 

아래는 유니티가 제공하는 컴포넌트에서 쉽게 설정할 수 있는 셋팅방법이다. 

Mesh Renderer는 메쉬 필터의 지오메트리를 사용하여 오브젝트의 Transform컴포넌트에서 정의된 위치에서 렌더링 한다. 프로퍼티를 간단하게 살펴보면 

Cast Shadows : 그림자 라이트가 비춰질 때 메쉬가 그림자를 만든다. 

Receive Shadows : 오브젝트에 다른 오브젝트의 그림자를 받는 설정이다. 실시간으로 그림자가 적용된다. 

1. 그림자를 필요로 하지 않는 오브젝트인 경우는 CastShadow를 꺼놓아야 드로우콜을 절약할 수 있다.

2. ShadowsOnly로 선택하면 최종 화면에는 렌더링 되지 않지만 쉐도우 맵에는 반영되어 그림자를 만드는 오브젝트가 된다. 즉 화면에는 보이지 않는 특성이 있어 그림자 처리만을 위한 3D 모델에 활용할 수 있다.

씬뷰에는 메시만 보이고 게임 뷰에서는 그림자만 보인다.

3. Light -> Shadow Type

Soft Shadows 가 좋은 만큼 성능도 많이 차지한다. 픽셀 쉐이더의 부담이 된다. 모바일이라면 되도록 HardShadows를 사용한다.

HardShadows

 

SoftShadows

4. Light -> Ressolution

쉐도우맵의 해상도를 나타낸다.

Low Resolution
Very High Resolution

5. Shadow Distance

Edit -> Project Setting -> Quality -> Shadow Distance

Shadow Distance는 카메라로부터 그림자가 그려지는 거리를 의미한다.

이 값이 낮을수록 멀리 있는 그림자는 그리지 않는다. 그러는 대신에 쉐도우 맵에서 오브젝트의 픽셀이 차지하는 비중이 커져 시각적으로 해상도가 올라가는 효과를 낸다.

멀리 있는 슬라임은 그림자를 그리지 않는다.

멀리있는 슬라임의 그림자가까지 그리지만 비교적으로 낮은 품질의 그림자로 보인다.

7. Shadow Cascades 

케스트 케이드 쉐도우 맵은 쉐도우 맵을 범위별로 여러 개를 만들어서 사용하는 기법이다. 원근법이 적용된 뷰 프러스텀 공간에 오브젝트를 렌더링하는 유니티에서는 가까운 오브젝트일수록 차지하는 픽셀의 수가 적다. 

이러한 한계를 해결하기 위해 프러스텀 영역을 몇 단계로 나누어서 서로 다른 쉐도우 맵을 사용 한다.

Two Cascades는 2등분, Four Cascades는 4등분

Two Cascades

많이 쪼갤수록 그만큼 드로우 콜 수도 늘어난다. 

반응형

7. 배칭

드로우 콜에 대해서 알았다면, 이제 이것을 줄이는 방법을 알아야 한다. 가장 효율적인 방법으로 배칭이 있다. 효율적인 방법이라고 한 것은 여러의 배치를 하나로 묶어서 하나의 배치로 만드는 것이 바로 배칭인 것이다.  한 가지 예를 들어보면 오브젝트가 3개 있다면 원래는 3개의 드로우 콜 즉, 3개의 배치가 필요하지만 조건에 따라서 1개까지 줄이는 것이다. 메시가 서로 다른 오브젝트이지만 같은 머테리얼을 사용하면 하나의 배치로 만들 수 있다. 

배칭을 위해서는 다른메시를 이용하더라도 머티리얼을 공유해서 사용해야 한다. 파츠가 여러 개인 경우 메시는 각각 다르지만 하나의 머티리얼을 통해 색상을 입히는 것이다. 이렇게 하기 위해서는 여러 장의 텍스처를 하나를 묶어 텍스처 아틀라스 기법으로 하나의 텍스처로 여러 메시들이 사용할 수 있게 하는 것이다.

같은 머테리얼이라는 의미는 동일한 인스턴스를 말하며, 같은 텍스처를 사용하는 머테리얼이지만 그것이 2개라면 같은 인스턴스가 아닌 것이다.

다른 머테리얼

이러한 이유로 코드상에서 머티리얼을 접근할때 유의할 점이있다.  머테리얼 색상을 바꾸는 코드는 GetComponent<Renderer>().material.color = Color.red;을 사용하면 색상을 바꿀 수 있다. 하지만 이 코드를 사용할 때마다 계속해서 새로운 머테리얼을 복사 생성하게 된다. 그러면 다른 머테리얼의 인스턴스를 생성하는 것이다. 하지만 같은 인스턴스를 사용하는 방법도 있다. 머테리얼의 속성을 바꾸는 Renderer.sharedMaterial을 사용하면 된다. 물론 상황에 따라 사용하겠지만 한 가지 예시로,

아래 그림처럼 스켈레톤 몬스터는 모두 같은 머티리얼을 사용하고 있다. 그러면 현재는 한 개의 인스턴스가 있는 것이다. 그런데 플레이어가 스켈레톤을 공격하면 빨간색으로 피격 효과를  받게 되는데 같은 인스턴스를 공유하고 있기 때문에 피격을 당하지 않은 몬스터까지도 피격 효과(빨갛게) 변할 것이다. 

이것은 분명 개발 기획의도는 아닐 것이다. 그렇다면 어쩔 수 없이 몬스터마다 각각 다른 인스턴스를 생성해서 적용해야 하는 걸까?

2019/10/03 - [유니티/레퍼런스] - 유니티)MaterialPropertyBlock

 

유니티)MaterialPropertyBlock

sharedMaterial 머테리얼의 속성을 변경할때 SharedMaterial으로 색상을 바꾼다던지 쉐이더코드의 프로퍼티값들을 변경한다. 그런데 이 런타임중에 변경된 머테리얼의 값들은 종료해도 바뀐 값 그대로 유지된다...

funfunhanblog.tistory.com

MaterialPropertyBlock를 이용하면 객체의 변화를 주는 동안만 배칭이 되지 않고 작업이 끝나면 다시 배칭을 하는 방법이다.

 

8.  스태틱배칭

스태틱 배칭은 말 그대로 정적인 오브젝트 즉 움직이지 않는 오브젝트를 위한 기법이다. 주로 배경 오브젝트들이 해당되는데 이 기법을 이용하려면  Static 플래그를 켜야 한다. 이 플래그를 켜야 유니티가 이 오브젝트는 움직이지 않는 스태틱 오브젝트라는 것을 알 수 있기 때문이다. 

그런 후 다른 작업 없이 게임을 시작하면 자동으로 유니티는 스태틱 플래그가 켜진 오브젝트를 배칭 처리 작업을 진행한다. 오브젝트 버텍스가 많을수록 렌더링 연산 비용이 크다. 당연히 버텍스 수가 많으면 런타임 과정 중에 부담이 크다. 하지만 스태틱 배칭 된 오브젝트는 런타임에 연산이 이루어지지 않는 게 장점이 되는 것이다.

드로우콜수를 줄이기 위해 오브젝트들을 합쳐 내부적으로 하나의 메시로 만들어준다. 만일 3개의 오브젝트가 1개의 메시만 사용하더라도 3개의 메시를 합친 만큼 추가 메모리가 필요하게 되는 것이다. (내 생각에는 SpriteAtlas처럼 생각하면 될듯하다)

물론 이 작업은 유니티가 이러한 정보들을 저장해 야하기 때문에 메모리가 조금 더 필요하게 된다. 렌더링(드로우 콜)이냐 메모리냐 이 논제는 최적화에서 자주 결정해야 할 사항이다.  스태틱 배칭을 간단하게 정의하자면

대상 :움직이지 않는 오브젝트

장점 : 드로우콜 감소

단점 : 메모리 증가

9.  다이내믹 배칭

동적으로 움직이는 오브젝트들끼리 배칭 처리를 하는 기능이다. 동일한 머티리얼을 사용하며 자동으로 배칭이 이루어진다. 배칭 처리는 런타임상에 이루어지며 Static플래그가 체크되어 있지 않는 오브젝트들의 버텍스들을 모아서 합쳐주는 과정을 거친다. 이러한 버텍스들을 모아서 다이내믹 배칭에 쓰이는 버텍스 버퍼와 인덱스 버퍼에 담는다. GPU는 이를 가져가서 렌더링 하는 것이다. 매프 레임 데이터 구축과 갱신이 발생하기 때문에 오버헤드가 발생하게 된다. 다이내믹은 자동으로 이루어지지만 여러 가지 조건들이 있다.

1. 버텍스 900개 이하로 된 메시로만 적용된다.

2. 트랜스폼을 미러링 한 오브젝트들은 배칭 되지 않는다.(예를 들어 Scale값이 1인 오브젝트를 -1로 적용했을 때)

3. 다른 머티리얼 인스턴스를 사용하면 같이 배칭 되지 않는다.

4. 라이트맵이 있는 오브젝트는 추가 렌더러 파라미터를 가지는데, 일반적으로 동적으로 라이트 매핑된 오브젝트가 완전히 동일한 라이트맵의 지점에 있어야 배칭 된다.

5. 멀티 패스 셰이더를 쓰면 배칭이 되지 않는다.

스태틱 배칭은 버텍스 쉐이더에서 월드 스페이스로의 변환이 이루어진다. 즉 GPU에서 연산이 된다. 하지만 다이내믹 배칭은 실시간으로 오브젝트의 위치가 변환되기 때문에 CPU에서 연산이 이루어진다. 

10.  2D 스프라이트 배칭

2D도 배칭 작업이 가능하다. 텍스처 아틀라스 기법처럼 여러 이미지들을 한 이미지에 모아서 사용하는 방법과 SpriteAtlas를 사용하여 배칭 처리한다.

2019/01/09 - [유니티/최적화] - 유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

 

유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

스프라이트 아틀라스 유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다. 텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다. 아틀라스를 사용하면 드로우콜을 줄일 수..

funfunhanblog.tistory.com

11.  GPU 인스턴 싱

적은 수의 드로우 콜을 사용하여 동일한 메시의 여러 복제본을 한 번에 그리거나 렌더링 한다. 특히 씬에서 반복적으로 나타나는 건물, 나무, 풀 등의 오브젝트를 보여줄 때 좋다. 또 같은 메시를 사용하더라도 간단한 변화 컬러, 스케일 등의 변화를 줄 수 있다. 별도의 메시를 메시를 생성하지 않기에(트랜스폼 정보를 별도의 버퍼에 저장) 다이내믹 배칭과 스태틱 배칭보다 오버헤드가 적다.

https://docs.unity3d.com/kr/2017.4/Manual/GPUInstancing.html

반응형

 

1. 드로우콜  이란

간단하게 CPU가 GPU에게 렌더링 작업을 수행하도록 명령을 하는것이다.

게임은 실시간 렌더링 어플리케이션이다. 

실시간으로 렌더링을 수행하기에, 한 프레임의 렌더링은 오브젝트를 하나하나 그릴때마다 여러 정보들을 CPU에서 GPU로 전달하여 그리도록 명령한다.

CPU가 중앙처리 장치인 만큼 GPU에게 명령하기 떄문에 CPU의 의해 핸들링 되는 구조이다.

여기서 알아야 할 것은 CPU와 GPU는 각각 자신들이 메모리 공간을 가지고 있다. CPU의 메모리는 RAM이 주로 사용된다. 즉 이 RAM에 각종 데이터들이 담기고 CPU가 이를 이용하여 연산을 수행하게 된다. 그래픽 처리도 GPU와 VRAM을 나누어 관리한다.

보통 모바일기기에서는 CPU와 GPU메모리를 물리적으로 나누지 않는다.(하나의 물리적인 메모리를 논리적으로 나누어 사용)

드로우 콜은 렌더의 상태를 알려주는 명령과 CPU가 GPU에게 그리라는 명령(DP Call)까지이며, 이런 과정은 한번에 보내는 것이 아니라 순차적으로 보내게된다. 이런 과정이 반복되며 화면에 오브젝트를 렌더링하게된다.

2. 데이터와 명령의 흐름

데이터의 흐름 : Storage(HDD, SDD ,SD) -> CPU메모리 -> GPU메모리 (메시정보, 텍스처, 등등)

드로우콜을 이해 하려면 데이터와 명령의 흐름을 알아야한다. 먼저 메시,텍스처 등 렌더링에 필요한 데이터들은 저장소 흔히 알고있는 HDD,SDD,SD에 먼저 위치한다. 그러고 CPU는 이 데이터들을 파싱하여 CPU메모리에 데이터를 옮긴다. 그 그런 후 마지막으로 GPU는 CPU의 메모리의 데이터들을 GPU메모리에 복사를 하게된다.

예를들어 GPU가 메시를 렌더링할 때 지오메트리 데이터를  GPU메모리에서 가져와 렌더링하게 된다. (GPU메모리에 렌더링에 필요한 데이터가 있다고 보면 됨)

메모리를 가져오고 전달하는 과정을 매프레임 때마다 하는것은 성능저하가 올 수 있기 때문에 특정 시점(로딩 시점, 씬전환시점)에 메모리 데이터를 올려 사용하도록 하는게 좋다. 계속해서 사용하는 데이터라면 메모리에 계속 올려두고 사용하고 그럴필요가 없는 메모리라면 데이터를 해제해 메모리 부담이 없도록 한다. 메모리에 영향이 없다면, 특정시점에 한번에 해제하는 것도 방법이다. 

3. 커맨드 버퍼 (Command Buffer)

렌더링은 CPU가 바로 GPU에 명령을 보낸다고 생각하지만 중간에 GPU가 할일을 보관하는 중재자가 있다. CPU와 GPU는 병렬작업을 통해 수행하는데 이러한 과정을 중재하는 커맨드버퍼가 있다. CPU가 GPU에게 명령을 하는 순간 GPU가 다른 일을 하고 있을 수 있기 때문에 그 명령을 쌓아 놓고 순차적으로 GPU가 처리 할 수 있도록 한다.  

4. CPU의 성능에 의존되는 드로우콜

렌더링이기 때문에 드로우콜은 CPU보다는 GPU의 영향이 크다고 생각하지만, GPU가 그리기 위한 신호들을 모두 CPU가 GPU에 맞게 변형하여 보내게된다. 이런 과정들은 곧 CPU바운더리의 오버헤드이며, 텍스쳐의 크기를 줄이거나, 폴리곤 수를 줄인다고 해서 드로우콜의 성능이 좋아지는 것은 아니다. (횟수를 줄여야함) 

5. 배치와 SetPass Call

드로우콜의 발생조건을 이해하기 전에 먼저 배치(Batch)와 SetPass를 알아야한다. 배치는 각각의 드로우콜 사이에서 그래픽스 드라이버 유효성 체크를 진행할때, GPU에 의해 접그되어지는  리소스들을 변경하는 일련의 작업들을 총칭한다. (드로우콜과 혼용하여 사용하지만 드로우콜을 포함하는 상위 개념인것이다.) SetPass는 쉐이더로 인한 렌더링 패스 횟수를 의미하고 쉐이더의 변경 시 SetPass카운트는 증가하게된다. 드로우콜이 일어날때 상태 변경의 발생 여부로 이해하면 된다. (메시 변경은 포함하지 않음) 

유니티 Stats를 보면 간단하게 몇번의 배치가 됐는지 볼수 있다.

6. 드로우콜의 발생 조건

오브젝트 하나를 그릴때 메시1개, 머테리얼도 1개로 이루어져있다면 배치는 1이다.(드로우콜 한번), 하지만 하나의 오브젝트가 여러 파츠로 나눠져있고, 메시가 여러개이면 하나의 머테리얼을 공유했다고 해도 파츠 수 만큼 드로우콜이 발생한다.  이러한 오브젝트가 여러개있다면 파츠수 x 오브젝트수  = 드로우콜 수 가되는것이다. 때로는 당연히 성능에 많은 영향을 미칠수 있다. 메시가 여러개인 경우말고도 머테리얼이 여러개 인 경우, 툰쉐이딩 처럼 외각선을 그리기위해 멀티패스로 이루어져있는 쉐이더를 사용하면 두번의 드로우콜이 발생하게 된다.  

 

2019/01/09 - [유니티/최적화] - 유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

 

 

유니티) 아틀라스 Sprite Atlas이용해 드로우콜을 줄여보자

스프라이트 아틀라스 유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다. 텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다. 아틀라스를 사용하면 드로우콜을 줄일 수..

funfunhanblog.tistory.com

 

 

 

 

반응형

라이트맵


라이트맵을 학습하기 전에 알아둬야 할 것이 있다.


GI

우리 모두는 빛이 들어옴으로써 눈이 작동하는 것은 알게된다. 

직접 광원을 바라보고 있지 않는 한 GI(전역광)은 우리의 눈으로 들어오기 전에 물체 사이를 돌아다닌다.

빛은 비추는 물체마다 다르게 반사되거나 흡수되어 새로운 방향으로 보내게된다.


컴퓨터는 그래픽에서 빛이 반동하는걸 두 가지 카테고리로 나눈다.

직접광, 간접광

직접광

광원으로부터 빛이 한번 튕겨지고 눈에 들어오는것을 말한다.

간접광

빛이 눈에 들어오기 전에 여러 표면에서 튕겨져서 우리 눈에 들어오는 광이다.


ENLIGHTEN = realtime 

리얼타임 GI를 전달하는 움직이는 라이트나 오브젝트가 있을 때 쓰면 좋지만 문제는 연산비용이 들기 때문에 미리 라이트를 계산해서 오브젝트에 덮어씌우는 큰 텍스처를 만들어 주는것이 좋다. 

=> 그것이 바로 베이킹


프로그레시브 라이트맵퍼

인라이튼을 써서 라이트를 굽는것말고 프로그레시브 라이트 맵퍼를 사용하는것이 좋다. 

프로그레시브 라이트 맵퍼는 경로 추적 기반의 라이트맵퍼로 주변에서 튕겨지는 빛을 사실적으로 시물레이션 할수 있다. 


이 방법은 카메라에서 광원한테 점진적으로 광선을 보내는것인데,  그래서 렌더링 할 때 에디터에서 바로바로 결과를 볼 수 있고, 씬을 다시 렌더링 할 필요없이 리이팅 프로퍼티를 설정 할 수 있다. 


테스트하기 위해 벽을 만들어 방처럼 오브젝트를 위치시켰다.

그리고 Directional Light은 삭제했다. (그래서 어둡게 보임)


Light가 될 plane 생성

왼쪽 벽에 붙이고 material을 생성 한 후 Emission을 Color 오렌지 색을 넣고 수치값을 1.5로 설정했다.

생성한 material을 plane에 넣어준다.


아직 아무 반응이없다. Emission이있는 오브젝트는 리얼타임에서는 빛이 퍼지지 않기 때문이다.

빛을 주려면 베이크를 해야한다. 

이 효과를 보기 위해서는 움직이지 않는 오브젝트들이니까 베이크해도된다고 유니티에게 알려야한다.

방, 큐브, plane모두 Static 객체로 만들어준다. 

Static 오브젝트를 만든순간 유니티는 베이크를 시작한다.

오렌지색 라이트가 생겼다.

반대편에는 블루라이트를 추가해줬다. 이미션값은 2로 설정

둘다 켜주면 뭔가 빛세기가 약해지는데 이 이유는 잘모르겠다 ㅠ


그 다음에 Lighting Scene을 설정해준다.

Scene 탭의 설정은 개별 게임 오브젝트가 아니라 전체 씬에 적용/ 이 설정은 조명 효과와 최적화 옵션도 제어

실내를 연출할 것이기 때문에 Ambient Color는 있어서 안된다. 

빛은 방금전에 만든 라이팅만 있어야하기 때문에 검은색으로 맞춰준다. (초기 셋팅은 회색으로 되어있음)


Realtime Lighting은 체크 해제 해준다.


Lightingmapping Seting설정

Lightmapper -> Progressive 로 바꿔준다.


그런데도 변화가 업다. 이유는?


프로그레시브 라이트 맵퍼를 사용할 때에는 이미시브 메테리얼로 가서

Global IlluminationRealtime 에서 Baked로 바꿔야한다. 


베이크 중

노이즈가 보이는데 베이크중이라서 그렇다.


베이크 후

라이팅 매퍼의 몇 가지 설정들에 대해서 알아보자

Lightmap Size : 오브젝트사이즈가 작다면 줄여주자 같은 결과이지만 라이트맵 용량이 크게 부여 되어 있을 수 있다.

Compress Lightmap : 라이트맵 압축 여부 

=> 용량이 클 수록(높은 사양)일 수록 베이크시간이 오래걸리기 때문에 작업수정할때는 낮췄다가 최종적으로 올리도록 한다.

Ambient Occlusion : 근처 두 오브젝트간의 그림자를 발생시킨다.







학습참고

1) https://www.youtube.com/watch?v=o39SjRcA__U

1) https://docs.unity3d.com/kr/2018.2/Manual/class-LightmapParameters.html

2) https://chulin28ho.tistory.com/443

3) https://docs.unity3d.com/kr/current/Manual/GlobalIllumination.html


반응형


라이트맵


라이트맵이란?

유니티 프로젝트를 새로 생성하면 "Directional Light"이 하이어라키상에 만들어져있다. 연극에서 무대를 비추는 조명이 있다면, 이 것은 게임장면을 비추는 조명이다.

당연히 게임이 진행되는 동안 조명 연산비용이 들어간다. 이  조명연산 또한 가볍지 않기 때문에 라이트맵을 사용한다.


라이트맵은 어떻게 연산비용을 줄이는가?

조명을 텍스처로 만들어서, 실시간으로 조명 연산을 하지 않아도 조명이 비추는것 처럼 표현 하는것이다.  


정적 오브젝트 (움직이지 않는 오브젝트)

조명을 텍스처로 만든다라고 하면, 물체에 있는 오브젝트에 그림자 등 조명이 비춰져 있는 상태를 미리 그려 놓는다는 것인데, 당연히 움직이는 오브젝트는 해당이 안되는것이다.(움직이는 오브젝트의 그림자를 미리 그려 놓을 수 없기 때문, 라이트맵의 종류에따라 표현방법이 다름)



라이트 맵 베이크 방법

라이트맵을 만드려면 일단 조명이 움직이지 않는 환경,물체 오브젝트를 베이크를 해야한다. 


1) 라이트 설정

라이트에는 필수로 두가지 설정이 필요하다.

첫 번째, Static 정적오브젝트로 만들어준다.

두 번째, Mode -> Baked or Mixed (Baked로 라이트맵을 만드는것과 Mixed로 만드는것은 차이점이 있다.)


2) 라이팅 셋팅 창

Window -> Rendering -> Lighting Settings창을 활성화 시킨다.

Scene탭에서 

라이트맵 종류에 대해서는 다음번에 정리를 해야겠다.

그 다음 Generate Light를 Bake해준다.

(Auto를 켜주면 변할때마다 자동으로 원치 않을때 Bake를 해서 불편 한 것같다.)


Baked Lightmap을 보면 텍스쳐럼 구워진게 보인다.



라이트맵# 2

https://funfunhanblog.tistory.com/94


전역 조명(GI)은 직접 표면에 닿는 광원(직접광)뿐만 아니라 다른 표면에 부딪혀 반사되는 광원(간접광)을 모델링하는 시스템입니다. 오브젝트는 서로의 형상에 영향을 미치기 때문에 간접 조명을 모델링하면 가상 세계를 더 현실적이고 연결된 것처럼 만들 수 있습니다. 예를 들어 빨간색 소파에 햇살이 비치면 붉은 광원이 그 뒤에 있는 벽 위로 반사됩니다. 또한 동굴 입구의 바닥에 햇살이 비춰지면 동굴 내부로 반사되어 동굴 안쪽이 밝게 비춰질 수 있습니다.

출처 : https://docs.unity3d.com/kr/2018.2/Manual/GIIntro.html


학습 참고 : 

1) https://chulin28ho.tistory.com/441?category=458928

2) https://docs.unity3d.com/kr/2018.2/Manual/class-LightmapParameters.html


반응형

스태틱 오브젝트


게임이 실행되는 동안 움직이지 않는 오브젝트를 스태틱으로 설정해 놓으면 유니티 에디터 상에서 미리 계산해놓아 실시간으로 계산하지 않도록 하여 비용을 줄이는 최적화 방법이다.



  • Lightmapping: 씬을 위한 고급 라이팅;
  • Occluder and Occludee: 특정 카메라 위치로부터 오브젝트의 가시성에 기반한 렌더링 최적화;
  • Batching: 여러 오브젝트를 더 큰 하나의 오브젝트로 결합하는 렌더링 최적화;
  • Navigation: 씬 안에서 캐릭터가 장애물을 지나게끔 만들어주는 시스템.
  • Off-mesh Links: Navigation 시스템에 의해 만들어지는 씬에서의 불연속적인 영역 간의 연결.
  • Reflection Probe: 모든 방향에 대한 주위 환경을 둥근 뷰의 형태로 캡처합니다.

유니티는 베이크된 GI(전역 조명)을 간접광에 대한 내용을 미리 계산해서 데이터화를 미리 가능하다. 




Occlusion Culling오클루젼 컬링


땅을 스태틱오브젝트로 설정하고 카메라가 비추는 곳만 그리게 된다.

오클루젼 컬링 (Occlusion Culling)은 오브젝트가 다른 오브젝트에 의해 가려져서 카메라에 보이지 않을 때 해당 오브젝트의 랜더링을 비활성화 하는 기능이다.

+ 이거는 절두체컬링이네.. 

정적오브젝트는 리소스가 사전준비가 되어있어야 하며 추가/변경 될때는 베이크를 해주어야 한다.  



참고자료 : https://docs.unity3d.com/kr/530/Manual/StaticObjects.html

반응형

오브젝트 풀링 Object Pooling

스페이스바를 누르면 총알을 생성되고, forward방향으로 이동하며 3초후에 사라진다.



1) 처음에 10개를 만들어 놓는다.

2) 플레이어가 스페이스바를 누르면 하나씩 SetActive(true)로 활성화 시켜 마치 새로 생성된 것처럼 보여준다.

3) 정해둔 시간이 되면 총알은 SetActiv(false)되어 삭제된 것처럼 보여준다.

4) 만약 씬안에 10개가 이상 총알이 생성돼야 한다면 추가적으로 10개씩 생성해준다.


위 gif처럼 천천히 총알을 쏘면 10개로 활/비활성화로 돌려 쓸수 있다.

하지만 빨리 쏘게되면 추가적으로 10개가 되는것을 볼 수 있다.


오브젝트를 생성과 삭제를 하지 않고 활/비활성화로 구현한다.

( Instantiate과 Destroy을 쓰지 않는 기법이다.)


오브젝트를 복제 생성하고 삭제하는 Instantiate과 Destroy을 사용하게 되면 가비지 콜렉터를 호출하게 된다.

가비지 콜렉터란?

C#에서는 메모리 관리를 프로그래머가 따로 관리 하지 않는다.

가비지 컬렉터는 메모리에 있는 참조가 끝난 객체를 쓰레기 치우는 것처럼 소멸 시키는 역할을 한다.

=> 가비지 컬렉터 호출이 많이 발생 할 수록 성능이 낮아 진다.



pool 컬렉터 셋팅

LinkedList<Bullet> m_lipool = new LinkedList<Bullet>(); //풀에 담을

LinkedList<Bullet> m_liActive = new LinkedList<Bullet>(); //(생성)활성화된 총알을 담을


    const int nMAKE_BULLET_COUNT = 10;


    public void Init(Transform a_objRoot, GameObject a_objBulletOrigin) 

    {

        m_objRoot      = a_objRoot;

        m_BulletOrigin = a_objBulletOrigin;


        MakeBullet(nMAKE_BULLET_COUNT);

    }


~~생략

풀에 담을 오브젝트를 담을 컬렉터와 게임에서 보여질 오브젝트를 담을 컬렉터를 만들어준다.



pool에 오브젝트(값) 삽입
private void MakeBullet(int a_nCount) { for (int i=0; i<=a_nCount; ++i) { var b = Instantiate(m_BulletOrigin).GetComponent<Bullet>(); b.transform.parent = m_objRoot.transform; b.gameObject.SetActive(false); //비활성화 m_lipool.AddLast(b); } }

씬에 생성을 하고 비활성화로 해주고 LinkedList풀에 Add해준다.


오브젝트 생성(생성될 위치, 데이터)

public void AddShot(Vector3 a_vcPos, ref BulletData a_vcData)

    {

        if(m_lipool.Count == 0) 

        {

            MakeBullet(nMAKE_BULLET_COUNT); //10개 

}

            var bullet = m_lipool.First.Value; 

            m_lipool.RemoveFirst() //삭제

            bullet.Init(ref a_vcData); 

            bullet.transform.localPosition = a_vcPos;

            bullet.gameObject.SetActive(true);

            m_liActive.AddLast(bullet); 

    }

오브젝트풀 카운트가 0이되면 추가적으로 다시 10개를 생성한다.

첫번째로 담았던 오브젝트를 위치와 데이터를 정보를 입력해주고 활성화 해준다.

m_lipool 풀에서는 지워준다.

m_liActive 에 추가 해준다.


pool반환

    List<Bullet> m_listemp = new List<Bullet>(); //리스트 생성

    private void Update()

    {

        foreach(var b in m_liActive) //활성화된 풀중에서

        {

            b.DoUpdate(); //DoUpdate(총알 위치 업데이트)

            if(b.IsDie == true) // 사라질 시간이 됐다면

            {

                m_listemp.Add(b); //사라진 총알은 리스트에 추가

            }

        }

        foreach(var b in m_listemp) //리스트에 저장된 사라진 총알들

        {

            b.gameObject.SetActive(false); //비활성화

            m_liActive.Remove(b); //활성화된 풀삭제

            m_lipool.AddLast(b); //다시 풀에  넣는다 (뒤에)

        }

        m_listemp.Clear(); //넣어준 리스트 클리어

    }

풀에 넣을 리스트를 생성해준다 중간 바구니라고 생각하면 쉽다. 

1) 사라질 시간이 될 총알들은 비활성화 해주고  

2) m_liActive에 지운다.

3) 그 총알들을 리스트에 담는다.

4) 바구니에 담은 총알들을 다시 m_lipool 생성풀에 넣어준다.


반응형

C#) 가비지 컬렉터 / 유니티) 가비지 컬렉터


가비지컬렉션?

메모리 관리 기법중 하나, 프로그램이 (동적)할당했던 메모리에서 필요없게 된 영역을 해제하는 과정이다.


명시적 해제가 필요없다

 C++에서는 개발자가 직접 메모리할당을 해제를 해야하는데 유니티에서 사용하는 C#프로그래밍에서는 가비지 컬렉터가 존재하기  명시적 메모리해제를 하지 않아도 된다. 또,유니티 Mono엔진 런타임 시스템 덕분에 메모리 노출 가능성도 크게 준다고 한다.


언제 발생?

힙을 할당해야하는데 사용가능한 메모리가 충분하지 않다고 판단될때, 그리고 아무도 모르는 시점(플랫폼마다 다르다고 한다.)


알 수 없는 메모리 해제 시점

가비지콜렉터는 메모리를 해제 할때 작업을 수행하는 데 시간이 소요된다. 메모리의 양과 프로그램을 실행 중인 플랫폼에 따라 다르고, 중요한 것은 메모리가 해제되는 시점을 개발자가 알 수 없다는 것이다.  게임과 같은 실시간으로 실행되는 프로그램에서는 큰 문제가 될 수 있다.


가비지컬렉터가 실행되면 동반되는 것들

힙에 할당된 변수가 스코프를 벗어나면, 사용되었던 메모리는 이때 해제 되지 않고, 가비지 컬렉터가 실행되어야 해제 되는 이이 때 일어나는 과정들이 있다. 

가비지 컬렉터가 힙 상의 모든 오브젝트를 검사하는데 오브젝트 참조값을 모두 확인해서 해당 오브젝트가 힙상에서 스코프내에 있는지 확인하여 삭제 여부를 판단하다. 당연히 힙 상의 오브젝트가 더 많을수록 가비지 컬렉터는 할 일이 많아진다. 


힙 파편화

힙에 메모리가 저장될 때 데이터의 크기에 따라 블록 단위로 저장이 되는데 서로 다른 크기의 메모리 블록이 저장되고 해제되면서 남은 공간이 생기게 된다. 그렇기 때문에 실제 개발자가 사용하려했던 프로그램 메모리 사용량이 늘어나고, 메모리를 찾기 위해 가비지 컬렉터가 더 자주 실행하게 된다.

 


참고 : https://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html?_ga=1.214538258.405170267.1480455402

http://ronniej.sfuh.tk/optimizing-garbage-collection-in-unity-games-2/

http://ronniej.sfuh.tk/optimizing-garbage-collection-in-unity-games-1/


반응형


스프라이트 아틀라스


유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다.

텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다.


아틀라스를 사용하면 드로우콜을 줄일 수 가 있다.


드로우콜?:

CPU가 GPU에게 어떠한 그림을 그려 달라고 요청하는것인데 많을 수록 게임이 무거워지고 프레임 저하가 올 수 도 있다.


프로젝트 셋팅하기

Unity 에디터 설정(Edit > Project Settings > Editor)에서 변경

빌드에 대해 활성화(Enabled for Builds): 플레이 모드에서가 아니라 빌드에 대해서만 패킹을 사용하고 싶을 때 

항상 활성화(Always Enabled): 패킹된 스프라이트가 플레이 모드 동안 스프라이트 아틀라스에서 텍스처를 확인하게 만들고, 편집 모드에서 원본 텍스처의 텍스처를 확인하게 만들고 싶을 때 사용




아틀라스 생성

Assets -> Create -> Sprite Atlas


이렇게 보라색 이미지가 생성된 것을 볼 수 있다.




이미지 패킹(묶기)!

그냥 드래그앤 드롭으로 끌어다 놓으면 된다.(폴더를 옮겨도 됨)

Type : Variant 다른 생성된 아틀라스를 포함시킬 수 있다.




스크립트 작성

using UnityEngine.U2D;

using UnityEditor.Experimental.U2D;


public class test : MonoBehaviour

{

public SpriteAtlas m_atlas;

public Image m_img1;


void Start()

{

m_img1.sprite = m_atlas.GetSprite("blue_boxCheckmark");

~~~생략

}

}




비교하기

<일반적인 Sprite를 넣었을때>


<아틀라스를 사용했을때>


Batches가 줄어든것을 볼 수 있다. 



반응형

버튼(프리펩,Prefab) 동적생성


   

BtnB를 클릭하면 그림 처럼 각각 다른 button 4개가 생성되게 만들겠습니다.


1)Panel 세팅



먼저 UI Panel 아래에 BtnGroupWindow를 만들어 줍니다.

button 4개 위치를  BtnGroupWindow 버튼 관리와 위치를 잡아주기 위해서입니다.


2)프리팹(견본생성)


동적으로 프리팹을 만들기 위해서는 먼저 복사?가 될 프리팹을 1개 만들어 주어야합니다.

GameObject -> UI -> Button

Button을 생성한 다음

Hierarchy창에서 Project창으로 드래그로 하면 프리팹이 만들어집니다.



3)BtnGroupWindow스크립트 작성 (BtnGroupWindow 오브젝트에 추가합니다.)

  

public class BtnGroupWindow : MonoBehaviour {

GameObject mbtnPrefab;

   public Transform panelPos;

mbtnPrefab이란 이름으로 프리팹으로 만들어 주기 위한 세팅입니다.


   string[] mstrImageName = new string[]

   {

        "bonbon_cha0025", "bonbon_cha0021"

                       ,"bonbon_cha0020", "bonbon_cha0014"

   };


버튼 4개가 모두 다른 이미지이기 때문에, 이미지를 바꿔 주기위해 배열을 세팅해줍니다.

이미지이름을 문자열로 넣어줍니다.


   void Start () {


mbtnPrefab = Resources.Load<GameObject> ("BtnAnimal");


if (mbtnPrefab ==null)

       { Debug.Log("mbtnpreprefab==null"); }


   }

mbtnPrefab에 아까 만들어 놓은 BtnAnimal 프리팹을 로드해서 넣어줍니다.


   public void CreateBtns()

   {

       for (int nn = 0; nn < 4; nn++)

       {

           

           GameObject button = Instantiate(mbtnPrefab);

//Instantiate(생성할 물체);

           RectTransform btnpos = button.GetComponent<RectTransform>();

//RectTransform을 잡기 위한 코드

button.transform.position = gameObject.transform.position;

//버튼프리팹의 최초 생성 postion값은 부모인 BtnGroup으로 맞춰줍니다.

//텍스처 바꾸기

Image image = button.GetComponent<Image>();

//생성할 버튼 Image 컴포넌트에 접근

Sprite btnsprite = Resources.Load<Sprite>(mstrImageName[nn]);

//nn이 0부터 3까지 도는 동안 각각에 맞는 이미지 sprite을 바꿔줍니다.

image.sprite = btnsprite;


btnpos.SetParent(gameObject.transform);

//SetParent 부모오브젝트 설정 여기서는 부모의 transform을 받기 위함입니다.

btnpos.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left,(20*nn), 36);

                       //왼쪽 모서리로 부터,얼마만큼 떨어져서 ,버튼의 크기

//버튼의 크기는 만들어 놓은 프리팹의 크기로 맞춰주는게 좋습니다.

       }

}

+원하는 위치에 생성이 안된다면 Panel과 BtnGroupWindow의 Rect Transform의 값을 설정해줍니다.


반응형

+ Recent posts