개념

C 카메라가 있는 위치, B를 screen space, A를 3D space라고 할 때, A를 B로 projection하는 방법에 대해서 알아본다.

(그림은 특정 좌표의 단면을 본다고 가정한다.)

C로 시작해서 B, A를 이어가는 벡터로 볼 수 있으므로, B와 A는 서로에게 linearly dependent한 상태이다. 따라서 아래와 같은 식으로 나타낼 수 있다.

다시 그림을 보면, 좌표를 z축에 투영하면 삼각형이 하나 나오는 것을 상상할 수 있다. 또한 해당 삼각형이 서로 비례한다는 것도 상상할 수 있게 된다. 이를 이용해서 다음과 같은 식을 얻을 수 있다.

위의 두 식을 결합하면 최종적으로 B의 좌표를 얻을 수 있게 된다.

projection 코드

const float scale = distEyeToScreen / (distEyeToScreen + pointWorld.z);
pointProj *= scale;

왜곡 보정

perspective projection을 수행하고 나면 한가지 문제가 생기는데, interpolation을 수행하는 것이 왜곡되어 표현된다는 점이다. 왜곡이 발생하는 이유로는 projection 이전에 interpolation이 필요하지만, projection 이후에, interpolation을 수행하기 때문에 발생한다.

따라서 왜곡을 보정하기 위해서는 projection 이전인 3D space에서 interpolation한 값이 필요하다.

해당 그림에서 O를 시작점으로 볼 때, a와 a’, b와 b’, c와 c’, p와 p’은 서로에게 linearly dependent한 것을 알 수 있다.

식으로 표현하면 다음과 같다.

여기서 p는 interpolation된 값을 나타내므로 다음과 같이 표현할 수 있다.

이제 위에서 얻은 식을 섞어서 다음과 같이 표현할 수 있다.

위 두식을 연립하여 다시 해당 식을 얻을 수 있다.

berycentic coordinates는 합이 1이므로 아래와 같이 나타낼 수 있다.

(Barycentric Coordinates 참고)

위 식을 정리해서 t 값을 얻어낼 수 있게 된다.

이제 위에서 구한 식에 t를 대입하면 아래와 같은 최종 값을 얻을 수 있다.

이제 해당 식으로 interpolation을 수행하면 왜곡을 보정할 수 있다.

correct interpolation 코드

const float z0 = this->vertexBuffer[i0].z + distEyeToScreen;
const float z1 = this->vertexBuffer[i1].z + distEyeToScreen;
const float z2 = this->vertexBuffer[i2].z + distEyeToScreen;
 
if (this->usePerspectiveProjection &&
    this->usePerspectiveCorrectInterpolation) {
    const float t = w0 / z0 + w1 / z1 + w2 / z2;
    w0 = w0 / z0 / t;
    w1 = w1 / z1 / t;
    w2 = w2 / z2 / t;
}

코드에서는 원점이 sceen space의 중간 지점이므로, distEyeToScreen + this→vertexBuffer[i0].z의 값이 3D space의 z값이 된다.