3 분 소요

컬링(Culling)

3D 그래픽스에서 불필요한 ‘도형’이나 ‘픽셀’을 그리지 않는 최적화 기법

  • GPU가 계산하지 않아도 되는 정보를 걸러내어 성능을 향상
  • ex : 화면 밖의 오브젝트, 뒤집힌 삼각형, 다른 물체에 가려진 물체 등

Image

GPU 연산량을 감소시키며
전처리를 통해 CPU/GPU의 최적화로 대규모 씬(오픈월드 등)에서
필요한 최적화

대표적인 컬링 종류 요약

컬링 종류 설명
Backface Culling 삼각형이 카메라 반대 방향을 향하면 제거
Frustum Culling 카메라 시야(Frustum) 밖에 있는 물체 제거
Occlusion Culling 다른 물체에 가려져서 안 보이는 물체 제거
Distance / LOD Culling 너무 멀거나, 카메라에 영향이 적은 경우 제거 또는 LOD 처리

Image


Unreal에선?

컬링 방식 설명
Backface Culling Mesh 설정에서 가능 (Two-Sided 옵션 끄기)
Frustum Culling 월드의 시야 밖 객체는 자동 제거
Occlusion Culling GPU 기반 자동 처리 (HLOD, Precomputed Visibility 사용 가능)
Distance Culling Cull Distance Volume 또는 LODDistanceFactor 설정으로 적용

Backface Culling (후면 컬링)

삼각형의 앞면 / 뒷면을 판단하여
‘뒷면’인 경우 카메라에
보이지 않으므로 대상에서 제외

판별 기준?

방식 설명 실제 사용 여부
법선 vs 카메라 방향 (ViewDir) normal과 view vector의 내적 ❌ 일반적인 컬링에는 사용 안 함 (조명 계산에 사용됨)
정점 순서 (Winding Order) 정점 나열 순서를 기준으로 시계/반시계 여부로 판단 ✅ GPU가 실제로 사용하는 방식

법선과 카메라 방향을 통한 방식은
복잡하고 느리기에 후자가 선호된다


후자를 통해 판별하는 예시와
Vector 2D 외적을 통해 이를 적용하는 방식을
알아볼 예정이다


Edge Function

두 벡터의 ‘외적’을 통해
‘점’이 어떠한 방향에 존재하는지를 판단하는 기능

Image

float EdgeFunction(const vec2 &v0, const vec2 &v1,
                                  const vec2 &point) {
    const vec2 a = v1 - v0;
    const vec2 b = point - v0;
    return a.x * b.y - a.y * b.x; // Cross
}

(이미 래스터화된 ‘스크린 좌표’이므로
vec2 로 다룰 수 있음)
(z값은 어차피 depth buffer에 존재)

2차원 벡터 간의 외적을 통하여 나오는 값은
두 벡터 a,b를 통해 만든 평행사변형의 ‘넓이’와 같음
따라서 그 넓이가 ‘양수’라면
v1-v0 과 p - vo 이 이루는 벡터는 ‘양의 부호’를 가진다고 판단 가능
(넓이가 0이라면 p가 사실상 두 선분위에 있다는 말이며
넓이가 -라면 p의 위치는 ‘음의 부호’측에 있다 판별 가능)

해당 결과를 result로 표현하면,

  • result > 0 : P가 v0->v1 의 왼쪽(반시계의 방향 : CCW)
  • result < 0 : P가 v0->v1 의 오른쪽(시계 방향 : CW)
  • result == 0 : P가 v0->v1 선분 위에 있음

이러한 기능을 통해

  • 삼각형 내부 판정 (Barycentric Coordinate)
  • 바리센트릭 좌표계(Barycentric Coordinate)
  • 면의 방향성 판정 (Backface Culling)

등이 가능해진다


삼각형 내부 판정법?

삼각형을 이루는 3개의 점을 v0,v1,v2 라 하고
판별하고 싶은 픽셀을 p라 지정한 경우
다음과 같은 방식으로 판정이 가능하다

if (Edge(v1, v2, p) >= 0 &&
    Edge(v2, v0, p) >= 0 &&
    Edge(v0, v1, p) >= 0)
{
    // p는 삼각형 내부
}

바리센트릭 좌표계?

전체 삼각형에서 특정한 점 p가
각 꼭지점에서 ‘얼마나’ 가까운지를 보간하는 좌표

alpha = Edge(v1, v2, p) 
beta = Edge(v2, v0, p)
gamma = Edge(v0, v1, p)
A = Edge(v0, v1, v2)

alpha /= A;
beta  /= A;
gamma /= A;

픽셀 값을 일일이 지정하지 않아도
삼각형을 이루는 정점에 대한 정보를 통하여
color, depth,uv 등의
상대적인 정보를 구하기 쉬워진다

예시)

const float area = alpha + beta + gamma; // A를 이렇게 구할 수 도 있음(한번더 함수를 안써도 됨)
const vec3 color =
    (alpha * c0 + beta * c1 + gamma * c2) / area;
const vec2 uv =
    (alpha * uv0 + beta * uv1 + gamma * uv2) / area;

const float depth = (alpha * this->vertexBuffer[i0].z +
                     beta * this->vertexBuffer[i1].z +
                     gamma * this->vertexBuffer[i2].z) /
                    area;

면의 방향성 판정?

그래픽스에서는 삼각형의 정점의 ‘순서’가 ‘반시계방향’(CCW)인 경우를 ‘앞면’으로 판단
(정점 순서 기반, Winding Order)

따라서 v0,v1,v2 삼각형의 ‘주어진 정점 순서’를 통해
해당 면이 ‘앞면’인지를 판단하고 그릴지 말지를 정할 수 있다

const float areaCheck = EdgeFunction(v0,v1, v2);
// 뒷면 컬링 중이면 이 삼각형을 그리지 않음
if (this->cullBackface && areaCheck < 0.0f)
    return;

v2가 v0,v1 벡터의 ‘왼쪽’에 존재(result > 0)하는지 확인하여
‘앞면’인지 확인하고 아니라면 그대로 return하여 면을 그리지 않음

정리

  1. Backface Culling은 카메라에서 볼 수 없는 ‘삼각형 뒷면’을 그리지 않는 최적화 기법이다

  2. ‘앞/뒷면’을 판별하는데에는 ‘정점의 나열 순서’를 보고
    시계/반시계 중 ‘설정된 값’을 통해 파악한다

  3. Edge Function을 통하여
    하나의 ‘점’이 특정 벡터의 ‘왼쪽/오른쪽’인지를 판별하고
    이를 통해 ‘정점’의 순서를 판별할 수 있다

  • 컬링을 꺼야할때?
    ‘양면 재질’, ‘단면 렌더링’, ‘얇거나 투명한 구조물’ 등에는
    컬링을 꺼야 의도된 재질을 연출할 수 있다

댓글남기기