1. 전장의 안개(Fog Of War) 원리

2025. 6. 13. 14:09Unity

전장의 안개(Fog Of War, FOW)는 간단하게 우리가 많이 해본 롤, 스타크래프트에서와 같이 유닛의 시야를 제한하는 기능을 하여 나 또는 아군 유닛들의 시야 범위에 있는 유닛들만 보여주는 시스템이다.

리그오브레전드 전장의 안개

 

FOW를 구현하는 방법은 다양하겠지만 내가 알고있는 방법으로 간단하게 원리를 설명하자면,

1. 전장의 안개를 표현할 텍스처 생성 (컬러가 필요한 경우 4채널, 그렇지 않은 경우 싱글 채널)

2. 캐릭터 시야 내 픽셀들의 Alpha를 0, 시야 밖 픽셀들의 Alpha는 1로 설정한다.

3. 2번까지 처리된 텍스처를 배경과 알파블렌딩한다.

 

1번은 간단한 것 같지만, FOW 구현 방법에 따라 텍스처의 해상도를 정하는 방법이 달라진다.

예를 들어 맵 영역을 타일로 나누는 기법(Shadowcasting 등)의 경우는 텍스처의 해상도가 그리드의 사이즈와 같아야 한다.

가시 영역을 메시(Mesh)로 만드는 기법(레이캐스팅 등)은 해상도 설정에 좀 더 자유롭다.

 

2번이 쉽지 않은데, 캐릭터 주변 가시 영역에 해당하는 픽셀들을 찾아야하기 때문이다. 즉, FOW의 핵심 알고리즘에 해당한다고 볼 수 있다. 가시 영역을 구하는 알고리즘은 여러가지가 있는데 내가 알고있는 방법들은 다음과 같다.

  1. 렌더링 파이프라인만을 이용한 방법이다. 이 방법은 카메라에 메시가 보이는 영역이 곧 가시 영역이기 때문에 따로 가시 영역을 구하기 위한 알고리즘을 구현하지 않아도 되기 때문에 간단하다고 볼 수 있다. 단점은 메시의 형태가 고정이기 때문에 장애물 뒤에 다른 캐릭터가 가려져있더라도 메시 안에 있다면 보이게 되는 문제점이 있다. 즉, 장애물에 따른 가시 영역 판정이 불가능하다.
  2. 레이캐스팅을 이용한 방법이다. 이 방법은 매 프레임 또는 특정 시간 간격마다 가시 영역 계산을 통해 메시로 만든다. 위 방법과는 다르게 레이캐스팅을 이용하여 장애물이 가리고 있는 영역과 가리고 있지 않은 영역을 알 수 있다. 이 방법은 구현해보지 않아서 자세한 설명은 생략한다.
  3. 마지막으로 내가 구현해본 그리드를 이용한 방법이다. 이 방법은 기본적으로 맵 영역을 NxM의 타일로 나누고 각 타일에 이 타일이 장애물인지 아닌지 표시한다. 장애물인지 아닌지 판별은 유니티의 Physics.Check*, Physics.*Cast와 같은 충돌 체크 함수를 통해 각 타일마다 충돌이 일어나는지 확인해보면 된다. 그 결과 다음과 같이 맵에서 벽 타일 부분만 빨간색으로 표시된 것을 알 수 있다.


이제 다음 그림을 보자. 

초록색은 캐릭터 타일이고, 빨간색은 장애물 타일, 파란색 물음표는 캐릭터의 위치에서 가시 타일인지 확인하고 싶은 타일이라고 해보자. 보다시피 장애물 타일 여부만으로는 파란색 물음표 타일이 캐릭터의 위치에서 보이는 타일인지 아닌지 알 수 없다. 따라서 가시 타일 판정 알고리즘이 별도로 필요하다. 내가 알고있는 가시 타일 판정 알고리즘은 두 가지가 있는데, 

첫 번째는 라인 드로잉 알고리즘을 이용하는 것이다. A 타일에서 B 타일까지 직선을 그린다고 하면 두 타일 사이를 거쳐가는 타일들을 알 수 있고, 두 타일 사이에 장애물 타일이 존재한다면 가시 타일이 아님을 알 수 있다(구현 참조). 단점은 B 타일 주변에 있는 타일들은 비슷한 타일들을 거쳐가므로 중복 처리가 많아진다는 것이다.

두 번째는 Shadowcasting 알고리즘을 이용하는 것이다. 이 알고리즘은 라인 드로잉 알고리즘보다는 복잡하지만 중복 처리를 최소화하는 장점이 있다. 어떻게 동작하는지는 위 사이트에 들어가서 직접 확인해 보는 것이 이해가 빠를 것이다. 간단하게 말하자면,

위 그림과 같이 캐릭터 타일을 기준으로 동서남북 4개의 사분면으로 나누고, 각각의 사분면에 있는 두 개의 노란색 기울기 선 안쪽에 있는 타일들이 장애물 타일인지 아닌지 검사하며 기울기를 조절하며, 최종적으로 두 기울기 선 안쪽에 있는 타일들이 가시 타일들이 되는 알고리즘이다.

 

지금까지의 결과를 통해 캐릭터가 볼 수 있는 범위에 있는 타일들 중 가시 타일과 비가시 타일을 구분할 수 있게 됐으며, 이제 이 정보를 통해서 텍스처의 Alpha 값을 조정해야 한다. 위에서 맵 영역을 그리드로 나누는 기법은 그리드의 사이즈와 텍스처의 해상도가 같아야 한다고 했다. 따라서 그리드의 타일과 텍스처의 픽셀이 1:1로 대응되므로 텍스처에서 가시 타일은 Alpha를 0, 비가시 타일은 Alpha를 1로 설정하면 다음 그림과 같이 텍스처가 만들어질 것이다.
(나는 싱글 채널 텍스처를 사용했기 때문에 Alpha가 1인 부분은 빨강으로 나오는 것)

 

3번은 지금까지의 결과물을 활용하는 단계이다. 가시 및 비가시 타일들을 판별하여 텍스처를 만들어냈으니, 이 텍스처를 배경과 블렌딩하여 가시 영역은 보이게(밝게), 비가시 영역은 안보이게(어둡게) 만들어야 한다. 그럼 3번을 구현 해보자.

 

먼저 최소 FOW가 적용될 영역 전체를 덮을만큼 큰 플레인(Plane)이 필요하다. 

위 사진의 노란색 영역은 타일 크기 1을 가진 64x64의 그리드 영역(4096개의 타일)으로, 런타임에 저 사이즈 만큼의 플레인이 생성된다. 그리드의 사이즈가 커질수록 FOW 영역이 커질 것 이기 때문에 플레인의 사이즈도 비례해서 커질 것이다(타일의 크기는 고정이라고 가정).

 

플레인이 생성되었다면 배경 픽셀들과 블렌딩을 위한 셰이더를 작성하고 플레인의 머티리얼에 할당해야 한다. 셰이더는 알파블렌딩 셰이더를 구현해서 사용하면 되며, FOW 블렌딩 결과가 깊이 테스트에 의해 폐기되지 않도록 ZTest Always로 설정한다. 블렌딩 결과는 다음과 같다.

 

캐릭터 카메라 시점에서 보면 다음과 같다.

 

지금까지 전장의 안개 구현에 대한 원리를 단계별로 살펴보았는데, 위에 대로만 구현해도 전장의 안개는 구현할 수 있지만 사실 내가 바로 위에 첨부한 결과물에는 많은 과정이 생략되어 있다. 예를 들어, FOW 텍스처의 해상도가 낮기 때문에 그대로 사용하면 앨리어싱이 보일 것이며, 캐릭터가 이동할때 이전 프레임과 다음 프레임의 FOW 텍스처 보간이 들어가지 않으면 맵이 갑자기 확 밝아지거나 어두워지는 현상이 있을 것이다.

 

다음에 작성할 글에서는 FOW 텍스처의 앨리어싱을 개선하기 위해 내가 어떤 방법을 사용했는지 공유해보도록 하겠다.

'Unity' 카테고리의 다른 글

유니티 오디오 설정  (0) 2025.04.07