이전 ray tracing 기법에서 했던대로 곧이 곧대로 구현하기만 하면, 왜곡이 일어난다.
이를 수정하는 방법을 배우고,
또한 우리의 눈의 위치와 물체의 위치와의 거리에 따라 보이는게 달라진다.
예를 들어 물체와 가까워질수록 기울어진 각도등이 과장돼서 보이고,
멀어질수록 평면적으로 보인다.
Z축은 투영되니까 그냥 0이 되고,
Y축은 그림처럼 삼각형의 닮음을 이용한 비례식으로 구할 수 있다.
X축도 마찬가지로 위에서 봤을때라고 생각하면 Y와 마찬가지로 구할 수 있다.
이것만 가지고 구하면 다음처럼 왜곡된 형태가 나온다.
// 원근투영(Perspective projection)
if (this->usePerspectiveProjection) {
// ...
const float scale =
distEyeToScreen / (this->distEyeToScreen + pointWorld.z);
pointProj = vec2(pointWorld.x, pointWorld.y) * scale;
}
왜곡이 생기는 이유
우리가 원하는 Interpolation은 A와 B사이의 값K를 Interpolation하는 것이다.
그러나 우리가 구현한 것은 A'과 B'사이의 값 K'를 Interpolation한 것이다.
즉, 프로젝션 된 값들을 보간한 것이라서 엉뚱한 값을 구한 것.
우리는 3차원 공간상의 A와 B를 Interpolation해서 K를 구해야 한다.
Orthographic perspective 에서는 A와 A', B와 B', K와 K'이 같았기 때문에 문제가 없었던 것.
왜곡 해결법
사실 Graphics API에서 내부적으로 자동으로 해결해주는 부분이기는 하다.
그러나 이런 문제가 일어날 수 있다는 것을 알고있고 원리와 해결법, 구조를 이해하는 것이 좋다.
나중에 API에서 제공하지 않는 새로운 기능을 구현하다가 이런 문제를 직면하면, 모르고 있다가 그걸 처음 보면 당황할 수 있다.
// Perspective-Correct Interpolation
// 논문
// https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
// 해설글
// https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes
// OpenGL 구현
// https://stackoverflow.com/questions/24441631/how-exactly-does-opengl-do-perspectively-correct-linear-interpolation
if (this->usePerspectiveProjection &&
this->usePerspectiveCorrectInterpolation) {
// w0, w1, w2를 z0, z1, z2를 이용해서 보정
const float zeta0 =
distEyeToScreen + this->vertexBuffer[i0].z;
const float zeta1 =
distEyeToScreen + this->vertexBuffer[i1].z;
const float zeta2 =
distEyeToScreen + this->vertexBuffer[i2].z;
const float w0p =
(w0 / zeta0) /
((w0 / zeta0) + (w1 / zeta1) + (w2 / zeta2));
const float w1p =
(w1 / zeta1) /
((w0 / zeta0) + (w1 / zeta1) + (w2 / zeta2));
const float w2p =
(w2 / zeta2) /
((w0 / zeta0) + (w1 / zeta1) + (w2 / zeta2));
w0 = w0p;
w1 = w1p;
w2 = w2p;
}
위 코드를 좀 정리하면 아래처럼 된다.
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) {
// w0, w1, w2를 z0, z1, z2를 이용해서 보정
//const float w0p =
// (w0 / z0) /
// ((w0 / z0) + (w1 / z1) + (w2 / z2));
//const float w1p =
// (w1 / z1) /
// ((w0 / z0) + (w1 / z1) + (w2 / z2));
//const float w2p =
// (w2 / z2) /
// ((w0 / z0) + (w1 / z1) + (w2 / z2));
w0 /= z0;
w1 /= z1;
w2 /= z2;
const float wSum = w0 + w1 + w2;
w0 /= wSum;
w1 /= wSum;
w2 /= wSum;
}