DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 2. FOW ( Fog Of War )

Vfly 2025. 9. 2. 03:36

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client

 

GitHub - HyangRim/DirectX11-Engine-Client

Contribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.

github.com


Fog of War(전장의 안개) 시스템 구현

개요

Fog of War(FOW) 시스템은 전략 게임이나 RPG에서 플레이어의 시야를 제한하여 게임플레이에 전술적 깊이와 몰입감을 더하는 기능이다.

 

이번 프로젝트에서는 RTS 게임 스타일의 FOW 시스템을 구현한 것이다.

 

예시)

위 사진에서 밝은 원 부분에 있는 오브젝트들만 보인다.

 

 

 

시스템 아키텍쳐

핵심 구성요소

class FogOfWar : public MonoBehaviour, public IFogOfWar

 

FOW 시스템은 다음과 같은 주요 컴포넌트로 구성된다.

  1. 가시성 관리 시스템
    • 플레이어 중심의 원형 시야 범위 계산
    • 거리 기반 오브젝트 가시성 판정
    • 실시간 업데이트 최적화
  2. 셰이더 파이프라인
    • G-Buffer 기반 디퍼드 렌더링
    • FOW 효과가 적용된 라이팅 계산
    • 그레이스케일 및 색조 보정

 

핵심 기술적 구현

가시성 판정 알고리즘

bool FogOfWar::ShouldRenderObject(shared_ptr<GameObject> _object)
{
    if (!_object) return false;
    if (IsMapObject(_object)) return true;  // 맵은 항상 렌더링
    if (_object == GetGameObject()) return true;  // 플레이어 자신
    
    Vec3 playerPos = GetTransform()->GetPosition();
    Vec3 objPos = _object->GetTransform()->GetPosition();
    float distance = Vec3::Distance(playerPos, objPos);
    
    return distance <= m_sightRange;
}

 

이 함수는 각 오브젝트가 렌더링될지 결정하는 핵심 로직이다.

  • 맵 오브젝트 : 항상 표시(지형 정보)
  • 플레이어 : 항상 표시
  • 기타 오브젝트 : 시야 범위 내에서만 표시

 

동적 FOW 데이터 구조

struct FogOfWarConstantData {
    Vec3 playerWorldPos;    // 플레이어 월드 좌표
    float sightRange;       // 시야 범위
    float darkness;         // 어둠 강도
    float fadeDistance;     // 페이드 거리
    float smoothness;       // 부드러움 정도
    float time;            // 애니메이션 시간
    Vec2 padding;          // 16바이트 정렬
};

 

 

성능 최적화 전략

업데이트 주기 제어

void FogOfWar::Update()
{
    m_curTime += DT;
    if (m_curTime > m_updateTime) {
        m_curTime = 0.f;
        m_needsUpdate = true;
        UpdateFOWSystem();
    }
}

 

변경 감지 최적화

if (!m_isFirstUpdate && memcmp(&m_lastFowData, &fowData, sizeof(FogOfWarData)) == 0)
    return;

 

메모리 비교를 통해 데이터가 변경되지 않은 경우 불필요한 셰이더 업데이트를 방지한다.

 

 

셰이더 구현

G-Buffer 기반 렌더링

GBufferOutput PS_GBuffer(MeshOutput input)
{
    GBufferOutput output;
    
    float4 albedo = DiffuseMap.Sample(LinearSampler, input.uv);
    if (albedo.a < 0.05f) discard;
    
    output.albedo = albedo;
    output.normal = float4(normalize(input.normal), 1.0f);
    output.position = float4(input.worldPosition, input.position.z);
    output.material = float4(objectTypeValue, 0.0f, 0.0f, 1.0f);
    
    return output;
}

 

FOW 계산

// FOW 계산 함수
float CalculateFogOfWar(float3 worldPos)
{
    float distance = length(worldPos - g_playerWorldPos);
    
    if (distance > g_sightRange * 1.2f)
    {
        return max(g_darkness, 0.4f);
    }
    
    float fogFactor = 1.0f;
    
    if (distance > g_sightRange)
    {
        fogFactor = max(g_darkness, 0.4f);
    }
    else if (distance > (g_sightRange - g_fadeDistance))
    {
        float fadeRatio = (distance - (g_sightRange - g_fadeDistance)) / g_fadeDistance;
        fadeRatio = smoothstep(0.0f, 1.0f, fadeRatio);
        fadeRatio = pow(fadeRatio, g_smoothness);
        
        fogFactor = lerp(1.0f, max(g_darkness, 0.4f), fadeRatio);
    }
    
    return fogFactor;
}

 

수식의 의미

  • fadeRatio : 페이드 영역 내에서의 상대적 위치 ( 0 ~ 1)
  • smoothstep : 부드러운 곡선 보간 ( 급격한 변화 방지 )
  • pow(smoothness) : 페이드 커브 조절 ( 값이 클수록 급격한 변화 )

 

FOW 효과 적용

float4 PS_FOW(MeshOutput input) : SV_TARGET
{
    float shadow = CalcShadowFactor(ShadowMap, input.shadowPosH);
    float4 baseColor = ComputeLight(input.normal, input.uv, input.worldPosition, shadow);
    
    // FOW 계산
    float fogFactor = CalculateFogOfWar(input.worldPosition);
    
    // 그레이스케일 변환 및 색조 보정
    float3 grayColor = dot(baseColor.rgb, float3(0.299, 0.587, 0.114));
    grayColor = grayColor * float3(0.7, 0.7, 0.8); // 푸른빛 회색
    
    float grayIntensity = saturate(g_smoothness * 0.5f);
    float3 foggedColor = lerp(baseColor.rgb, grayColor, (1.0f - fogFactor) * grayIntensity);
    
    // 최소 밝기 보장
    float minBrightness = max(g_darkness, 0.4f);
    float3 finalColor = foggedColor * max(fogFactor, minBrightness);
    
    // 분위기 연출을 위한 푸른빛 색조
    if (fogFactor < 0.6f)
    {
        float blueTint = (0.6f - fogFactor) * 0.1f;
        finalColor = lerp(finalColor, finalColor * float3(0.9f, 0.95f, 1.05f), blueTint);
    }
    
    return float4(finalColor, baseColor.a);
}

 

 

성능 고려 사항

QuadTree 기반 공간 분할

const auto& objects = CURSCENE->GetQuadTree()->GetInsertedObject();
for (auto& obj : objects) {
    if (CURSCENE->GetQuadTree()->IsObjectVisible(obj, camera)) {
        // FOW 처리
    }
}

 

 QuadTree를 활용하여 카메라 절두체 컬링과 FOW 시스템을 결합, 불필요한 연산을 최소화

  • 변경 감지 : 메모리 비교를 통합 스킵
  • 공간 분할 : QuadTree 기반 효율적 탐색

업데이트 빈도 최적화

void FogOfWar::Update()
{
    m_curTime += DT;
    if (m_curTime > m_updateTime) {
        m_curTime = 0.f;
        m_needsUpdate = true;
        UpdateFOWSystem();
    }
}
  • 업데이트 주기 : 0.05초 (20fps) 

 

시연영상

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.