Backface Culling
백페이스 컬링이란 간단하게 카메라가 볼 수 없는 면을 그리지 않는 것이다.
위의 그림의 경우 안쪽 면까지 다 보이는 상태인데 여기서 백페이스 컬링을 적용하게 되면 다음과 같이 보인다.
게임을 하면서 관심 있는 부분은 우리 눈에 보이는 부분이다.
보이는 부분만 그리는 것과 보이지 않는 부분까지 그리는 것의 성능 차이가 상당하고,
게임 속 캐릭터나 배경 오브젝트 같은 경우는 겉 모습만 보이기 때문에 안쪽 부분까지 그릴 필요가 없다.
그러므로 3D 엔진에서는 다각형을 그리기 전에
다각형을 구성하는 각 면이 보이지 않는 면인가를 빠르게 판단해야 한다.
각 면이 보이지 않는 면인가를 판단하려면,
삼각형을 보았을 때
0 => 1과 0 => 2로 가는 벡터를 만들어주고,
외적을 시키게 되면은 해당 형태에서 노말 값이 나오게 되는데,
Backface Culling Cross Product(백페이스 컬링의 외적 공식)은 다음과 같다.
여기서 카메라의 정면(-Z 방향)과 노말 값을 내적 해줬을 때 같은 방향이 나오게 되면 면을 그리지 않도록 해주면 된다.
Result
Perspective Projection
먼저 FOV가 60도로 정해져있다고 가정해보면 시야 각은 절반인 30도가 되고,
절반이라는 부분이 있을 때 여기서 투영할 전체 3D 공간에서 거리를 정하는 것이다.
여기서 초점거리는 어떻게 정해지는 것인가?
예를 들어서 3D 공간에 여러 가지 물체가 있다고 했을 때 임의의 평면을 하나 만들어서 부착을 시켜주게 되면
Perspective 모드에 맞춰서 3차원에 있는 물체를 2차원에 매핑을 시키는 작업이 필요하게 되는 상황이 발생하게 되는데,
이 상태에서 내가 투영할 스크린을 1로 정규화 시키는 것이다.
1로 정규화를 시키게 되면은 초점거리에 대해서는 다음과 같은 공식을 통해서 구할 수가 있다.
이 상태를 가정하고 다음과 같이 평면 밖에 있는 어떤 점을 투영을 시키게 되면 Pndc의 좌표가 나오게 되는데
여기서 Pndc의 좌표는 절반의 크기가 1이기 때문에 1이 넘어갈 수가 없기 때문에
0과 1 사이에 들어온다는 결론이 나게 되는데, 결과적으로는 다음과 같이 비례 삼각형을 하나 더 그리게 되면
뷰 좌표계로 본다고 했을 때 카메라 앞 방향의 길이는 -Zview가 되고,
d : -Zview의 비율은
가 나오게 된다.
이렇게 해서 Yndc의 값을 구할 수 있게 됐고,
Xndc도 같은 방식으로 구할 수 있는데 여기서 화면 비율이 정사각형이면 아무런 문제가 없는데
항상 종횡비(4:3, 16:9)를 가지고 있기 때문에 정사각형과 똑같이 1이라고 가정하고
원을 그렸을 때 정사각형 화면은 제대로 나오지만 종횡 비율 화면은 다음과 같이 찌그러진 형태가 나오게 된다.
이렇게 되면 종횡 비율 화면에서 지저분하게 보이기 때문에 이것을 보정하기 위해서는
종횡비를 미리 계산해놓고 이것을 나눠주게 되면은 1의 사이즈가 줄어들게 되는데,
이렇게 세로를 기준으로 하고 가로로 똑같은 크기로 나올 수 있게 보정해 주는 작업이 필요해서
a는
가 되고, 최종적으로 다음과 같이 보정이 된 상태로 나오게 된다.
이렇게 투영된 X, Y좌표를 구하게 되었고,
다음은 Yndc와 Xndc에 대한 행렬식이다.
y 값을 구할 때 x는 영향을 미치지 않기 때문에 나머지를 0으로 놓고 x에 대해서만 계산을 하면 된다.
그러면 다음과 같이 행렬식의 두 개에 대한 부분에 대해서 채울 수가 있다.
그러면 여기서 x와 y의 공통점은 무엇일까?
분모가 둘 다 -Zview인 것이다.
-Zview가 z랑 연관이 있을 수도 있기 때문에 분모를 뺀 상태로 다음과 같이 넣어본다.
이렇게 되면 뭔지는 모르겠지만 w'가 -Zview가 나오도록 해보는 것이다.
그러면 위에 두 식은 다르지만 w' 값을 하나 구할 수 있게 되었고,
x, y 값은 나중에 1/-Zview로 나눠주면 되기 때문에 행렬을 이런 식으로 해놓은 것이다.
그 상태에서 이제 마지막으로 z'의 값을 구해야 되는데,
평면에는 x, y 값만 있으면 되기 때문에 z'값은 필요가 없어서 다음과 같은 식으로 해놓아도 상관이 없다.
그러면 무엇 때문에 z'값을 구해야 될까?
z 값을 구해야 되는 이유는 카메라 안에서 평면에는 똑같이 그려지지만
z 값이 어떤 깊이에 있는지를 알아야 되기 때문이다.
왜냐하면 점이 같은 위치에 있더라도 서로 깊이가 다르다는 것을 z 값으로 구별할 수가 있기 때문이다.
결론적으로 깊이에 대한 값을 판단해서 앞에 물체가 있으면 바로 뒤에 있는 물체는 그리지 않는 것이다.
그런데 만약 깊이 정보가 없으면 어떤 문제가 발생할까?
이런 식으로 그리는 경우에는 그리는 순서에 영향을 받는다.
매쉬 순서에 의해서 앞에 있는 물체가 먼저 그려지게 되면은
뒤에 있는 물체를 나중에 그렸을 때 무조건 덮어 씌우게 된다.
어떤 물체를 그릴 때 삼각형을 먼저 그리게 된다.
여기서 뒤에 있는 삼각형이 앞에 있는 삼각형에 가려져야 되는데,
뒤에 있는 삼각형이 카메라 위치 또는 매쉬 순서에 의해서 가장 나중에 그려지게 되면은 덮어 씌우게 댄다.
그래서 깊이 정보가 없이 만들게 되면은 다음과 같이 이도 저도 아닌 물체가 나오게 된다.
이제 z 값에 대해서 어떻게 정할지 생각을 해봐야 한다.
위아래, 양옆에 대한 정보는 -1 ~ 1로 규정을 해서 구해놓았고,
z 값의 정보, near랑 far에 대한 정보를 어떻게 줄 것인가에 대한 문제가 있는 것인데.
near에 대한 부분도 x, y와 똑같이 -1과 1을 넣어주고 계산하는 것이다.
그러면 z값이 near에 위치한 경우 -1, far에 위치한 경우 1이 나오게 되고,
이것을 기반으로 해서 다음과 같이 방정식을 푸는 것이다.
뷰 좌표계에서 -n 값, 즉 카메라가 있으면 (0, 0, -1, 1)은 카메라가 n 만큼 간 위치에 해당하는 것이다.
그러면 여기서 z 축으로만 간 것이기 때문에 x, y랑은 상관이 없어서 x, y의 값은 0이 되는 것이다.
그런데 여기에 모르는 미지수 두 개가 존재한다.
여기서 모르는 미지수 두 개를 다음과 같이 곱해서 -n이 나와야 하는데
왜 -n이 나와야 되는 것인가?
앞에서 w'를 -Zview로 저장한 이유는 -Zview가 x, y와 연관성이 있기 때문에 w'에 저장을 시키고
나중에 계산할 때 나누는 것이 이 과정에 함축돼있는 것이기 때문이고,
네 번째 행에 대해서 이렇게 계산을 할 수 있게 된 것이다.
그래서 아래와 같이 나온 이 좌표들을 -Zview로 나눠줘야 한다.
그렇게 최종적으로 Zview는
-Zview는
가 나오게 된다.
이번에는 far 값을 계산해보자
여기서 far 값은 -f, 1이 되고 w 값은 f로 나와 있는데
z 값은 f이기 때문에 계산 결과가 1이 나오게 되었고,
n과 f 서로 k, l이 뭐가 나와야 하는지 모르겠지만
각각 n과 f가 나와야 되는 이유가 밝혀졌다.
그러면 이것을 이제 방정식으로 만들어서 똑같이 l과 f로 나눠서 각각 구해주게 되면은 다음과 같은 공식이 나오게 된다.
이렇게 k와 l에 대한 값을 구했으므로 다음과 같이 최종 투영 행렬을 완성할 수 있게 된다.
그런데 다음과 같은 식을 곱했을 때 나오는 결과는 w를 아직 나누지 않은 상태로 값이 나오게 된다.
이것은 정규화를 시키기 위한 준비 과정이라고 볼 수 있는데,
w 값을 나누지 않은 이 값을 Clip Coordinate라고 한다.
여기서 w의 값은
가 되고 이것은 뷰 좌표계에서 카메라부터의 깊이 값이라고 할 수 있다.
그리고 w 값을 나누게 되면 NDC 값이 만들어지게 되고,
Normalized Device Coordinate(NDC)
-1 ~ 1값으로 정규화가 된다.
지금까지 한 것을 정리해보면 다음과 같다.
'Programming > Soft Renderer_2020' 카테고리의 다른 글
GameEngine 3D : 사원수 대수, 사원수 회전, 사원수의 활용 (1) | 2020.07.16 |
---|---|
GameEngine 3D : Frustum Culling, Euler angle, Rodrigues Rotation (0) | 2020.07.02 |
GameEngine 3D : Space, Rotation, Camera (0) | 2020.06.17 |
지수 함수, 맥클로린 급수 (0) | 2020.06.12 |
복소수 (cosθ, sinθ) 가 회전 행렬과 동일함을 증명 (0) | 2020.06.12 |