DirectX11

[DX11] Light Culling

yeoul0714 2025. 4. 12. 16:24

1. 개요


컬링(Culling)

3D 그래픽스 및 게임 개발에서 불필요한 연산을 줄이기 위해 사용되는 최적화 기법입니다.

 

기본적으로 "필요 없는 것을 걸러내는" 과정을 의미합니다.

 

지난번 Point Light구현시에 광원의 최대 수를 제한한적이 있습니다.

 

그러나 우리 게임 월드에서는 이보다 많은 빛이 Rendering될 수 있고, 이에 대한 대응도 필요합니다.

 

이번 시간엔 대응 하는 방법을 적용해보려고 합니다.

 

https://yeoul0714.tistory.com/24

 

[DX11] Point Light 구현하기 (내적, 정사영)

1. 개요 게임에서 빛은 매우 중요합니다. 빛을 이용하면 다양한 분위기도 표현이 가능하고, 다양한 연출도 가능하게 합니다. 그렇다면 DX에서는 이 Point Light를 어떻게 구현할까요? 우선 개발

yeoul0714.tistory.com

 


 

2. Frustrum Culling


Frustrum이란 말은 절두체라는 뜻입니다. 

 

한마디로 머리가 잘렸다는 것이죠. 

Frustrum

 

Frustrum은 우리가 카메라를 보는 범위(시야 범위)를 나타냅니다.

 

Frustrum Culling은 사다리꼴 범위안에 들어온 물체들만 Rendering해주고 그렇지 않은 물체들은 Rendering을 안해줘서

 

비용을 아끼는 최적화 기법입니다.

 

사다리꼴에 들어왔다는 것은 우리가 보고있다는 의미이고 들어오지 않았다는 것은 우리 시야에 보이지 않는 물체라는 뜻입니다.

 

보이지 않는 물체를 Rendering하지 않는 것은 어찌보면 당연한 일입니다.

 

참고자료 : Real Time Rendering 1089p


 

3. Frustrum Culling (라이트)


그렇다면 우리는 이 Frustrum Culling을 어떻게 Light에 적용할 수 있을까요?

 

 

3-1. 문제 1

우선 제가 처음 했던 생각은 단순히 Point Light의 위치가 절두체안에 들어오냐 들어오지 않냐로 Culling을 하려고 했습니다.

 

그러나 이 방법은 문제가 있었습니다.

 

바로 아래의 사진과 같은 경우 입니다.

 

사진을 보면 Light의 Radius에 큐브가 들어와 있는 상태입니다.

 

다시말하면 빛을 받고 있는 상태이지요.

 

그러나 Frustrum이 사진과 같이 된다면 큐브는 빛을 받고 있는 상태이지만 빛이 Culling되어서 빛 연산이 되지 않을 것입니다.

 

아래 영상을 참고해보면 Light가 시야범위에서 벗어나자 즉시 빛 연산이 되지 않는 것을 볼 수 있습니다.

 

      if (Frustrum.ContainsPoint(LightPos))
      {
          LightBufferData.gLights[LightCount] = Light->GetLightInfo();
          LightBufferData.gLights[LightCount].Position = Light->GetWorldLocation();
          LightCount++;
      }

 


 

3-2. 문제 1 해결방안

그렇게 생각해낸 방안은 바로 Culling의 범위를 좀더 느슨하게 하자였습니다.

 

위의 문제점은 light의 위치를 기준으로 Culling을 했다면 이번엔 light의 위치와 radius를 고려해서 

 

Frustrum이 light가 영향을 미치는 구(Sphere)를 벗어나면 Culling이 되어서 빛연산이 안되도록 만들었습니다.

 

 

아래는 해당 방식을 이용하여서 적용한 영상입니다.

if (Frustrum.IntersectsSphere(LightPos, LightRange))
{
    LightBufferData.gLights[LightCount] = Light->GetLightInfo();
    LightBufferData.gLights[LightCount].Position = Light->GetWorldLocation();
    LightCount++;
}

 

 


3-3. 3-2의 문제

잘 해결된 것 같아 보였지만 사실 3-2의 방법에도 문제가 존재합니다.

 

바로 Frustrum안에 light의 최대보다 더 많은 light가 있을 때입니다.

 

영상을 보면 시야범위에 적은 수의 light가 있을 땐 문제없이 light가 작동하는 것을 볼 수 있습니다.

 

그러나 시야 범위안에 MAX이상의 light가 들어오게 되면 그중에서 어떤 빛을 우선적으로 연산해 주어야할지

 

정해주지 않아서 어색하게 보이는 문제가 발생합니다.

 

이를 해결하기 위해서는 현재 Frustrum안에 들어온 Light들을 일정한 기준으로 정렬해주는 방법이 필요합니다.

 

현재는 그 기준을 카메라와 Light의 거리로 하였습니다.


3-4. 3-2의 문제 해결

이렇게 Culling된 Point light를 VisiblePointLights TArray에 넣고

 

거리기반 정렬을 한 뒤에 Constant Buffer에 넘겨주게 되면

 

현재 나의 위치랑 가장 가까운 빛들만 Rendering되게 됩니다.

 

물론 이방법은 공간분할이 되어 있지 않아서 모든 VisiblePointLights를 정렬해야 하는 문제가 있습니다.

    for (auto Light : PointLights)
    {
        FVector LightPos = Light->GetWorldLocation();

        // 광원의 유효 범위
        float LightRange = Light->GetAttenuationRadius();

        if (FFrustrum::Get().IntersectsSphere(LightPos, LightRange))
        {   
            VisiblePointLights.Add(Light);
        }

    }

    SortLightsByDistance(VisiblePointLights, Viewport->ViewTransformPerspective.GetLocation());

 

 

영상을 보면 현재 시야에 있는 Light들 중에 가까운 순서대로 Rendering됩니다.