Rasterization: a Practical Implementation Perspective Correct Interpolation and Vertex Attributes
barycentric coordinate으로 vertex attribute도 interpolation할 수 있다. 이전 글에서 설명한 것처럼 depth coordinate은 z coordinate의 역수로 barycentric coordinates을 이용, interpolation하여 찾을 수 있다. 이를 vertex attribute에도 적용 할 수 있다. 대표적인 vertex attribute는 color와 texture, normal 이 있다. texture coordinate은 2차원 coordinate으로 texturing에 사용한다. normal은 shading에 사용하고 물체의 방향을 정의한다.
rasterize를 단계별로 나누면 다음과 같을 것이다.
1. camera space에 원하는 만큼의 vertex attribute를 할당한다.
2. camera space 삼각형을 screen space로 project한다. (camera space -> ndc space -> raster space)
3. screen space 픽셀이 삼각형과 overlap하면 barycentric coordinate를 계산한다.
4. vertex attribute를 barycentric coordinate 이용하여 interpolation한다.
그런데 위 식과 같은 방법은 잘 작동하지 않는다.
위 사각형을 다른 각도로 보았을 때 아래 사다리 꼴과 같이 screen에 그렸다고 가정해보자. 점 P는 사각형의 가운데로 만약 color interpolation 했다면 v1 color와 v2 color의 중간 값이 나와야 한다.(위 사각형) 하지만 아래 사각형 점 P에서는 중간값이 나와야 하지만, 0.666이라는 값이 나온다. 왜냐하면 점 P가 V0에 더 가깝기 때문이다.
이 문제를 해결하기 위한 방법을 알아보자.
삼각형에 Z0, Z1 점을 잡고 둘을 이으면 직선을 만든다. 이 직선위의 점 Z는 Z0,Z1을 interpolation하여 구할 수 있다. color 값도 interpolation하여 구할 수 있고 그 관계는 아래와 같다.
이전 글에서 Z의 값을 interpolation하는 방법을 소개했었다.
1/Z0, 1/Z1 을 없애기 위해 분자 분모에 Z0Z1을 곱하여 풀면 아래와 같다.
위 식을 이용하여 color에 대해 풀면,
분모, 분자에 1/Z0Z1을 곱하면 아래와 같다.
위 식은 rasterization에서 기본이 되는 식이다.
이를 코드로 작성해보자.
#define PERSP_CORRECT
#include <cstdio>
#include <cstdlib>
#include <fstream>
typedef float Vec2[2];
typedef float Vec3[3];
typedef unsigned char Rgb[3];
inline float edgeFunction(const Vec3& a, const Vec3& b, const Vec3& c)
{
return (c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]);
}
int main(int argc, char** argv)
{
Vec3 v0 = { 13.0f, 34.0f, 114.0f };
Vec3 v1 = { 29.0f, -15.0f, 44.0f };
Vec3 v2 = { -48.0f, -10.0f, 82.0f };
Vec3 c0 = { 0, 0, 1 };
Vec3 c1 = { 0, 1, 0 };
Vec3 c2 = { 1, 0, 0 };
const uint32_t w = 512;
const uint32_t h = 512;
// project triangle onto the screen
v0[0] /= v0[2], v0[1] /= v0[2];
v1[0] /= v1[2], v1[1] /= v1[2];
v2[0] /= v2[2], v2[1] /= v2[2];
// convert from screen space to NDC then raster
v0[0] = (1 + v0[0]) * 0.5f * w, v0[1] = (1 + v0[1]) * 0.5f * h;
v1[0] = (1 + v1[0]) * 0.5f * w, v1[1] = (1 + v1[1]) * 0.5f * h;
v2[0] = (1 + v2[0]) * 0.5f * w, v2[1] = (1 + v2[1]) * 0.5f * h;
#ifdef PERSP_CORRECT
// divide vetex-attribute by the vertex z-coordinate
c0[0] /= v0[2], c0[1] /= v0[2], c0[2] /= v0[2];
c1[0] /= v1[2], c1[1] /= v1[2], c1[2] /= v1[2];
c2[0] /= v2[2], c2[1] /= v2[2], c2[2] /= v2[2];
// pre-compute 1 over z
v0[2] = 1 / v0[2], v1[2] = 1 / v1[2], v2[2] = 1 / v2[2];
#endif
Rgb* framebuffer = new Rgb[w * h];
memset(framebuffer, 0x0, w * h * 3);
float area = edgeFunction(v0, v1, v2);
for (uint32_t j = 0; j < h; ++j)
{
for (uint32_t i = 0; i < w; ++i)
{
Vec3 p = { i + 0.5f, h - j + 0.5f, 0.f };
float w0 = edgeFunction(v1, v2, p);
float w1 = edgeFunction(v2, v0, p);
float w2 = edgeFunction(v0, v1, p);
if (w0 >= 0 && w1 >= 0 && w2 >= 0)
{
w0 /= area;
w1 /= area;
w2 /= area;
float r = w0 * c0[0] + w1 * c1[0] + w2 * c2[0];
float g = w0 * c0[1] + w1 * c1[1] + w2 * c2[1];
float b = w0 * c0[2] + w1 * c1[2] + w2 * c2[2];
#ifdef PERSP_CORRECT
float z = 1 / (w0 * v0[2] + w1 * v1[2] + w2 * v2[2]);
// if we use perspective correct interpolation we need to
// multiply the result of this interpolation by z, the depth
// of the point on the 3D triangle that the pixel overlaps.
r *= z, g *= z, b *= z;
#endif
framebuffer[j * w + i][0] = (uint8_t)(r * 255);
framebuffer[j * w + i][1] = (uint8_t)(g * 255);
framebuffer[j * w + i][2] = (uint8_t)(b * 255);
}
}
}
std::ofstream ofs;
ofs.open("./raster2d.ppm");
ofs << "P6\n" << w << " " << h << "\n255\n";
ofs.write((char*)framebuffer, w * h * 3);
ofs.close();
delete[] framebuffer;
return 0;
}
어디가 문제인지는 아직 잘 모르겠다.
댓글 영역