6. Surface Normals and Multiple Objects(표면 법선과 다중 객체)

6.1 Shading with Surface Normals(표면 법선을 이용한 셰이딩)

먼저 셰이딩을 위해 표면 법선을 구해봅시다. 표면 법선은 교차점에서 표면에 수직인 벡터입니다.

코드에서 법선 벡터에 대해 중요한 설계 결정을 내려야 합니다. 법선 벡터가 임의의 길이를 가질 것인지, 아니면 단위 길이로 정규화될 것인지 선택해야 합니다.

벡터 정규화에는 비용이 큰 제곱근 연산이 포함되므로, 필요하지 않다면 건너뛰고 싶을 수 있습니다. 그러나 세 가지 중요한 관찰 사항이 있습니다.

첫째, 단위 길이 법선 벡터가 한 번이라도 필요하다면, 여러 위치에서 "혹시 모르니까"라며 반복하는 대신 처음에 한 번만 수행하는 것이 좋습니다.

둘째, 여러 곳에서 단위 길이 법선 벡터를 실제로 필요로 합니다.

셋째, 법선 벡터가 단위 길이여야 한다면, 특정 기하학 클래스에 대한 이해를 바탕으로 생성자나 hit() 함수에서 해당 벡터를 효율적으로 생성할 수 있습니다.

예를 들어, 구의 법선은 반지름으로 나누기만 하면 단위 길이로 만들 수 있어 제곱근을 완전히 피할 수 있습니다.

이를 고려하여 모든 법선 벡터가 단위 길이를 갖도록 하는 정책을 채택하겠습니다.

구의 경우, 외향 법선은 충돌점에서 중심을 뺀 방향입니다.

Figure 6: Sphere surface-normal geometry

Gemini_Generated_Image_v6qfsdv6qfsdv6qf.png

지구에서 이것은 지구의 중심에서 당신까지의 벡터가 곧장 위를 가리킨다는 것을 의미합니다. 이제 이것을 코드에 넣고 셰이딩해봅시다. 아직 조명 같은 것은 없으므로 법선을 색상 맵으로 시각화하겠습니다. 법선을 시각화하는 일반적인 방법은 각 구성 요소를 0에서 1까지의 구간으로 매핑한 다음 (x,y,z)를 (빨강,녹색,파랑)으로 매핑하는 것입니다. n이 단위 길이 벡터라고 가정하면 각 구성 요소가 −1과 1 사이에 있으므로 쉽고 직관적입니다. 법선을 시각화하려면 단순히 충돌 여부뿐만 아니라 충돌점이 필요합니다. 장면에는 구가 하나만 있고 카메라 바로 앞에 있으므로 아직 t의 음수 값은 고려하지 않겠습니다. 가장 가까운 충돌점(가장 작은 t)만 필요하다고 가정하겠습니다. 다음 코드로 법선 n을 계산하고 시각화할 수 있습니다:

double HitSphere(const Point& center, double radius, const Ray& ray)
{
    Vec3 originToCenter = center - ray.Origin();
    auto a = Dot(ray.Direction(), ray.Direction());
    auto b = -2.0 * Dot(ray.Direction(), originToCenter);
    auto c = Dot(originToCenter, originToCenter) - radius * radius;
    auto discriminant = b * b - 4.0 * a * c;

    if (discriminant < 0.0)
    {
        return -1.0;
    }

    return (-b - std::sqrt(discriminant)) / (2.0 * a);
}

Color RayColor(const Ray& ray)
{
    auto t = HitSphere(Point(0.0, 0.0, -1.0), 0.5, ray);
    if (t > 0.0)
    {
        Vec3 surfaceNormal = UnitVector(ray.At(t) - Vec3(0.0, 0.0, -1.0));
        return 0.5 * Color(
            surfaceNormal.X() + 1.0,
            surfaceNormal.Y() + 1.0,
            surfaceNormal.Z() + 1.0
        );
    }

    Vec3 unitDirection = UnitVector(ray.Direction());
    auto a = 0.5 * (unitDirection.Y() + 1.0);

    return (1.0 - a) * Color(1.0, 1.0, 1.0)
         + a * Color(0.5, 0.7, 1.0);
}

Listing 12: [main.cc] 구의 표면 법선 렌더링

그러면 다음과 같은 그림이 생성됩니다:

이미지 4: 법선 벡터에 따라 색상이 지정된 구