3. The vec3 Class(vec3 클래스)

거의 모든 그래픽 프로그램에는 기하학적 벡터와 색상을 저장하는 클래스가 있습니다. 많은 시스템에서 이러한 벡터는 4D입니다(기하학의 경우 3D 위치 + 동차 좌표, 색상의 경우 RGB + 알파 투명도 구성 요소). 우리의 목적을 위해서는 3개의 좌표면 충분합니다. 우리는 색상, 위치, 방향, 오프셋 등 모든 것에 동일한 vec3 클래스를 사용할 것입니다. 어떤 사람들은 색상에서 위치를 빼는 것과 같은 어리석은 일을 방지하지 못하기 때문에 이것을 좋아하지 않습니다. 그들의 지적은 타당하지만, 명백히 잘못되지 않은 경우 항상 "코드를 적게" 작성하는 경로를 택할 것입니다. 그럼에도 불구하고 우리는 vec3에 대해 두 가지 별칭인 point3color를 선언합니다. 이 두 타입은 vec3의 별칭일 뿐이므로 point3을 기대하는 함수에 color를 전달해도 경고가 표시되지 않으며, point3color에 추가하는 것도 막을 수 없지만, 코드를 조금 더 읽고 이해하기 쉽게 만듭니다.

우리는 새로운 vec3.h 헤더 파일의 상단 절반에 vec3 클래스를 정의하고, 하단 절반에 유용한 벡터 유틸리티 함수 세트를 정의합니다:

#ifndef VEC3_H
#define VEC3_H

#include <cmath>
#include <iostream>

struct Vector3
{
    Vector3() : E{ 0,0,0 } {}
    Vector3(double e0, double e1, double e2) : E{ e0, e1, e2 } {}

    double X() const { return E[0]; }
    double Y() const { return E[1]; }
    double Z() const { return E[2]; }

    Vector3 operator-() const { return Vector3(-E[0], -E[1], -E[2]); }
    double operator[](int i) const { return E[i]; }
    double& operator[](int i) { return E[i]; }

    Vector3& operator+=(const Vector3& v)
    {
        E[0] += v.E[0];
        E[1] += v.E[1];
        E[2] += v.E[2];
        return *this;
    }

    Vector3& operator*=(double t)
    {
        E[0] *= t;
        E[1] *= t;
        E[2] *= t;
        return *this;
    }

    Vector3& operator/=(double t)
    {
        return *this *= 1 / t;
    }

    double Length() const
    {
        return std::sqrt(LengthSquared());
    }

    double LengthSquared() const
    {
        return E[0] * E[0] + E[1] * E[1] + E[2] * E[2];
    }

    double E[3];
};
typedef Vector3 Vec3;

// Point3은 Vec3의 별칭입니다. 코드의 기하학적 명확성을 위해 유용합니다.
using Point3 = Vector3;

// 벡터 유틸리티 함수들

inline std::ostream& operator<<(std::ostream& out, const Vector3& v)
{
    return out << v.E[0] << ' ' << v.E[1] << ' ' << v.E[2];
}

inline Vector3 operator+(const Vector3& u, const Vector3& v)
{
    return Vector3(u.E[0] + v.E[0], u.E[1] + v.E[1], u.E[2] + v.E[2]);
}

inline Vector3 operator-(const Vector3& u, const Vector3& v)
{
    return Vector3(u.E[0] - v.E[0], u.E[1] - v.E[1], u.E[2] - v.E[2]);
}

inline Vector3 operator*(const Vector3& u, const Vector3& v)
{
    return Vector3(u.E[0] * v.E[0], u.E[1] * v.E[1], u.E[2] * v.E[2]);
}

inline Vector3 operator*(double t, const Vector3& v)
{
    return Vector3(t * v.E[0], t * v.E[1], t * v.E[2]);
}

inline Vector3 operator*(const Vector3& v, double t)
{
    return t * v;
}

inline Vector3 operator/(const Vector3& v, double t)
{
    return (1 / t) * v;
}

inline double Dot(const Vector3& u, const Vector3& v)
{
    return u.E[0] * v.E[0]
        + u.E[1] * v.E[1]
        + u.E[2] * v.E[2];
}

inline Vector3 Cross(const Vector3& u, const Vector3& v)
{
    return Vector3(u.E[1] * v.E[2] - u.E[2] * v.E[1],
        u.E[2] * v.E[0] - u.E[0] * v.E[2],
        u.E[0] * v.E[1] - u.E[1] * v.E[0]);
}

inline Vector3 UnitVector(const Vector3& v)
{
    return v / v.Length();
}

#endif

리스트 4: [vec3.h] vec3 정의와 헬퍼 함수들

여기서는 double을 사용하지만, 일부 레이 트레이서는 float를 사용합니다. doublefloat에 비해 더 높은 정밀도와 범위를 가지지만, 크기는 두 배입니다. 이러한 크기 증가는 제한된 메모리 조건에서 프로그래밍하는 경우(하드웨어 셰이더 등) 중요할 수 있습니다. 둘 다 괜찮으므로 자신의 취향에 따르세요.

3.1 Color Utility Functions(색상 유틸리티 함수)

새로운 vec3 클래스를 사용하여, 단일 픽셀의 색상을 표준 출력 스트림에 작성하는 유틸리티 함수를 정의하는 새로운 color.h 헤더 파일을 만들겠습니다.

#ifndef COLOR_H
#define COLOR_H

#include "Vec3.h"

#include <iostream>

using Color = Vec3;

void WriteColor(std::ostream& out, const Color& pixelColor)
{
    auto r = pixelColor.X();
    auto g = pixelColor.Y();
    auto b = pixelColor.Z();

    // [0,1] 범위의 컴포넌트 값을 바이트 범위 [0,255]로 변환합니다.
    int rByte = int(255.999 * r);
    int gByte = int(255.999 * g);
    int bByte = int(255.999 * b);

    // 픽셀 색상 컴포넌트를 출력합니다.
    out << rByte << ' ' << gByte << ' ' << bByte << '\\n';
}

#endif

리스트 5: [color.h] 색상 유틸리티 함수들

이제 main에서 이 두 가지를 모두 사용하도록 변경할 수 있습니다:

#include "Color.h"
#include "Vec3.h"

#include <iostream>

int main()
{
    // Image
    int ImageWidth = 256;
    int ImageHeight = 256;

    // Render
    std::cout << "P3\\n" << ImageWidth << ' ' << ImageHeight << "\\n255\\n";

    for (int j = 0; j < ImageHeight; j++) 
    {
        std::clog << "\\rScanlines remaining: " << (ImageHeight - j) << ' ' << std::flush;
        for (int i = 0; i < ImageWidth; i++) 
        {
            auto PixelColor = Color(double(i)/(ImageWidth-1), double(j)/(ImageHeight-1), 0);
            WriteColor(std::cout, PixelColor);
        }
    }

    std::clog << "\\rDone.                 \\n";
}

리스트 6: [main.cc] 첫 번째 PPM 이미지를 위한 최종 코드

그러면 이전과 정확히 같은 그림을 얻을 것입니다.