6. Surface Normals and Multiple Objects(표면 법선과 다중 객체)
6.1 Shading with Surface Normals(표면 법선을 이용한 셰이딩)
먼저 셰이딩을 위해 표면 법선을 구해봅시다. 표면 법선은 교차점에서 표면에 수직인 벡터입니다.
코드에서 법선 벡터에 대해 중요한 설계 결정을 내려야 합니다. 법선 벡터가 임의의 길이를 가질 것인지, 아니면 단위 길이로 정규화될 것인지 선택해야 합니다.
벡터 정규화에는 비용이 큰 제곱근 연산이 포함되므로, 필요하지 않다면 건너뛰고 싶을 수 있습니다. 그러나 세 가지 중요한 관찰 사항이 있습니다.
첫째, 단위 길이 법선 벡터가 한 번이라도 필요하다면, 여러 위치에서 "혹시 모르니까"라며 반복하는 대신 처음에 한 번만 수행하는 것이 좋습니다.
둘째, 여러 곳에서 단위 길이 법선 벡터를 실제로 필요로 합니다.
셋째, 법선 벡터가 단위 길이여야 한다면, 특정 기하학 클래스에 대한 이해를 바탕으로 생성자나 hit() 함수에서 해당 벡터를 효율적으로 생성할 수 있습니다.
예를 들어, 구의 법선은 반지름으로 나누기만 하면 단위 길이로 만들 수 있어 제곱근을 완전히 피할 수 있습니다.
이를 고려하여 모든 법선 벡터가 단위 길이를 갖도록 하는 정책을 채택하겠습니다.
구의 경우, 외향 법선은 충돌점에서 중심을 뺀 방향입니다.
*Figure 6: Sphere surface-normal geometry*
지구에서 이것은 지구의 중심에서 당신까지의 벡터가 곧장 위를 가리킨다는 것을 의미합니다. 이제 이것을 코드에 넣고 셰이딩해봅시다. 아직 조명 같은 것은 없으므로 법선을 색상 맵으로 시각화하겠습니다. 법선을 시각화하는 일반적인 방법은 각 구성 요소를 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);
}
C++
복사
그러면 다음과 같은 그림이 생성됩니다:
*이미지 4: 법선 벡터에 따라 색상이 지정된 구*
6.2 Simplifying the Ray-Sphere Intersection Code(광선-구 교차 코드 단순화)
광선-구 함수를 다시 살펴봅시다:
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);
}
C++
복사
먼저, 벡터를 자기 자신과 내적하면 해당 벡터의 제곱 길이와 같다는 것을 기억하세요.
둘째, b에 대한 방정식에 −2라는 인수가 있다는 점을 주목하세요. b = −2h일 때 이차 방정식이 어떻게 단순화되는지 살펴봅시다:
이것은 깔끔하게 단순화되므로 이를 사용하겠습니다. h를 구하면:
이러한 관찰을 사용하여 구-교차 코드를 다음과 같이 단순화할 수 있습니다:
double HitSphere(const Point3& center, double radius, const Ray& ray)
{
Vec3 oc = center - ray.Origin();
auto a = ray.Direction().LengthSquared();
auto h = Dot(ray.Direction(), oc);
auto c = oc.LengthSquared() - radius * radius;
auto discriminant = h * h - a * c;
if (discriminant < 0)
{
return -1.0;
}
return (h - std::sqrt(discriminant)) / a;
}
C++
복사
6.3 An Abstraction for Hittable Objects(충돌 가능한 객체를 위한 추상화)
이제 구가 여러 개라면 어떨까요? 구의 배열을 사용하고 싶을 수 있지만, 더 깔끔한 해결책이 있습니다. 광선이 충돌할 수 있는 모든 것에 대한 "추상 클래스"를 만들고, 구와 구의 목록 모두를 충돌 가능한 객체로 만드는 것입니다. 이 클래스의 이름을 정하는 것은 다소 어렵습니다. "object"라고 부르고 싶지만 "객체 지향" 프로그래밍과 혼동될 수 있습니다. "Surface"가 자주 사용되지만, 볼륨(안개, 구름 등)도 포함하고 싶을 수 있다는 단점이 있습니다. "hittable"은 이들을 통합하는 멤버 함수를 잘 나타냅니다. 완벽하지는 않지만, "hittable"로 하겠습니다.
이 hittable 추상 클래스는 광선을 받는 hit 함수를 가집니다. 대부분의 레이 트레이서는 tmin에서 tmax까지의 유효한 충돌 구간을 추가하면 편리하다는 것을 발견했습니다. 따라서 tmin<t<tmax일 때만 충돌로 "계산"됩니다. 초기 광선의 경우 이것은 양의 t이지만, tmin에서 tmax까지의 구간을 사용하면 코드를 단순화할 수 있습니다. 한 가지 설계 질문이 있습니다. 충돌했을 때 법선 같은 것을 바로 계산할 것인가? 검색 중에 더 가까운 객체와 충돌할 수 있으며, 가장 가까운 것의 법선만 필요합니다. 간단한 해결책을 선택하여 여러 정보를 계산하고 구조체에 저장하겠습니다. 다음은 추상 클래스입니다:
#ifndef HITTABLE_H
#define HITTABLE_H
#include "Ray.h"
class HitRecord
{
public:
Point3 P;
Vec3 Normal;
double T;
};
class Hittable
{
public:
virtual ~Hittable() = default;
virtual bool Hit(const Ray& r, double rayTMin, double rayTMax, HitRecord& rec) const = 0;
};
#endif
C++
복사
*Listing 15: [hittable.h] The hittable class*
그리고 여기 구가 있습니다:
#ifndef SPHERE_H
#define SPHERE_H
#include "Hittable.h"
#include "Vec3.h"
class Sphere : public Hittable
{
public:
Sphere(const Point& center, double radius)
: mCenter(center)
, mRadius(std::fmax(0.0, radius))
{
}
bool Hit(
const Ray& ray,
double rayTMin,
double rayTMax,
HitRecord& hitRecord
) const override
{
Vec3 originToCenter = mCenter - ray.Origin();
auto a = ray.Direction().LengthSquared();
auto h = Dot(ray.Direction(), originToCenter);
auto c = originToCenter.LengthSquared() - mRadius * mRadius;
auto discriminant = h * h - a * c;
if (discriminant < 0.0)
{
return false;
}
auto squareRootDiscriminant = std::sqrt(discriminant);
// Find the nearest root that lies in the acceptable range
auto root = (h - squareRootDiscriminant) / a;
if (root <= rayTMin || rayTMax <= root)
{
root = (h + squareRootDiscriminant) / a;
if (root <= rayTMin || rayTMax <= root)
{
return false;
}
}
hitRecord.t = root;
hitRecord.point = ray.At(hitRecord.t);
hitRecord.normal = (hitRecord.point - mCenter) / mRadius;
return true;
}
private:
Point mCenter;
double mRadius;
};
#endif
C++
복사
*Listing 16: [sphere.h] sphere 클래스*
(여기서 우리는 C++ 표준 함수 std::fmax()를 사용하는데, 이는 두 부동 소수점 인수 중 최댓값을 반환합니다. 마찬가지로, 나중에 두 부동 소수점 인수 중 최솟값을 반환하는 std::fmin()을 사용할 것입니다.)
6.4 Front Faces Versus Back Faces(앞면 대 뒷면)
법선에 대한 두 번째 설계 결정은 법선이 항상 밖을 향해야 하는지 여부입니다. 현재, 찾은 법선은 항상 중심에서 교차점 방향을 가리킵니다(법선이 밖을 향함). 광선이 구의 외부에서 교차하면 법선은 광선의 반대 방향을 가리킵니다. 광선이 구의 내부에서 교차하면 법선(항상 밖을 향함)은 광선과 같은 방향을 가리킵니다. 또는 법선이 항상 광선의 반대 방향을 가리키도록 할 수 있습니다. 광선이 구의 외부에 있으면 법선은 밖을 향하지만, 광선이 구의 내부에 있으면 법선은 안쪽을 향합니다.
*그림 7: 구 표면 법선 지오메트리의 가능한 방향들*
우리는 이러한 가능성 중 하나를 선택해야 합니다. 왜냐하면 결국 광선이 어느 쪽 표면에서 오는지 결정하고 싶을 것이기 때문입니다. 이는 양면 종이의 텍스트처럼 각 면에서 다르게 렌더링되는 객체나 유리 공처럼 안과 밖이 있는 객체에 중요합니다.
법선이 항상 밖을 향하도록 결정하면, 색을 칠할 때 광선이 어느 쪽에 있는지 결정해야 합니다. 광선과 법선을 비교하여 이를 알아낼 수 있습니다. 광선과 법선이 같은 방향을 향하면 광선은 객체 내부에 있고, 광선과 법선이 반대 방향을 향하면 광선은 객체 외부에 있습니다. 이는 두 벡터의 내적을 취하여 결정할 수 있으며, 내적이 양수이면 광선은 구 내부에 있습니다.
if (dot(ray_direction, outward_normal) > 0.0)
{
// ray is inside the sphere
...
}
else
{
// ray is outside the sphere
...
}
C++
복사
*리스팅 17: 광선과 법선 비교*
법선이 항상 광선의 반대 방향을 가리키도록 결정하면, 내적을 사용하여 광선이 표면의 어느 쪽에 있는지 결정할 수 없습니다. 대신 그 정보를 저장해야 합니다:
bool front_face;
if (dot(ray_direction, outward_normal) > 0.0)
{
// ray is inside the sphere
normal = -outward_normal;
front_face = false;
}
else
{
// ray is outside the sphere
normal = outward_normal;
front_face = true;
}
C++
복사
*리스팅 18: 표면의 쪽 기억하기*
법선이 항상 표면에서 "밖을 향하도록" 설정하거나, 항상 입사 광선의 반대를 향하도록 설정할 수 있습니다. 이 결정은 지오메트리 교차 시점에 표면의 쪽을 결정할지, 색칠 시점에 결정할지 여부에 따라 결정됩니다. 이 책에서는 지오메트리 타입보다 머티리얼 타입이 더 많으므로, 작업을 줄이고 지오메트리 시점에 결정하겠습니다. 이것은 단순히 선호의 문제이며, 문헌에서 두 구현 모두를 볼 수 있습니다.
front_face bool을 hit_record 클래스에 추가합니다. 또한 이 계산을 해결할 함수를 추가합니다: set_face_normal(). 편의상 새로운 set_face_normal() 함수에 전달되는 벡터가 단위 길이라고 가정합니다. 매개변수를 명시적으로 정규화할 수도 있지만, 특정 지오메트리에 대해 더 많이 알 때 일반적으로 더 쉽기 때문에 지오메트리 코드가 이를 수행하는 것이 더 효율적입니다.
class HitRecord
{
public:
void SetFaceNormal(const Ray& r, const Vec3& outwardNormal)
{
// 히트 레코드 법선 벡터를 설정합니다.
// 참고: 매개변수 `outwardNormal`은 단위 길이를 가진다고 가정합니다.
bFrontFace = Dot(r.Direction(), outwardNormal) < 0;
Normal = bFrontFace ? outwardNormal : -outwardNormal;
}
Point3 P;
Vec3 Normal;
double T;
bool bFrontFace;
};
C++
복사
*리스팅 19: [hittable.h] hit_record에 앞면 추적 추가*
그런 다음 클래스에 표면 쪽 결정을 추가합니다:
class Sphere : public Hittable
{
public:
bool Hit(
const Ray& ray,
double rayTMin,
double rayTMax,
HitRecord& hitRecord
) const override
{
...
hitRecord.t = root;
hitRecord.point = ray.At(hitRecord.t);
Vec3 outwardNormal = (hitRecord.point - mCenter) / mRadius;
hitRecord.SetFaceNormal(ray, outwardNormal);
return true;
}
...
private:
Point mCenter;
double mRadius;
};
C++
복사
*리스팅 20: [sphere.h] 법선 결정을 포함한 sphere 클래스*
A List of Hittable Objects (히트 가능한 객체 리스트)
광선이 교차할 수 있는 hittable이라는 일반 객체가 있습니다. 이제 hittable 리스트를 저장하는 클래스를 추가합니다:
#pragma once
#ifndef HITTABLE_LIST_H
#define HITTABLE_LIST_H
#include "Hittable.h"
#include <memory>
#include <vector>
class HittableList : public Hittable
{
public:
HittableList() = default;
explicit HittableList(const std::shared_ptr<Hittable>& object)
{
Add(object);
}
void Clear()
{
mObjects.clear();
}
void Add(const std::shared_ptr<Hittable>& object)
{
mObjects.push_back(object);
}
bool Hit(
const Ray& ray,
double rayTMin,
double rayTMax,
HitRecord& hitRecord
) const override
{
HitRecord temporaryHitRecord;
bool bHitAnything = false;
auto closestSoFar = rayTMax;
for (const auto& object : mObjects)
{
if (object->Hit(ray, rayTMin, closestSoFar, temporaryHitRecord))
{
bHitAnything = true;
closestSoFar = temporaryHitRecord.T;
hitRecord = temporaryHitRecord;
}
}
return bHitAnything;
}
private:
std::vector<std::shared_ptr<Hittable>> mObjects;
};
#endif
C++
복사
*리스팅 21: [hittable_list.h] hittable_list 클래스*
몇 가지 새로운 C++ 기능
hittable_list 클래스는 C++에 익숙하지 않은 경우 혼란스러울 수 있는 몇 가지 기능을 사용합니다: vector, shared_ptr, make_shared.
shared_ptr은 참조 카운팅을 사용하는 포인터입니다. 다른 공유 포인터에 할당할 때마다 참조 카운트가 증가합니다. 공유 포인터가 범위를 벗어나면 (블록이나 함수가 끝날 때) 참조 카운트가 감소합니다. 카운트가 0이 되면 객체가 자동으로 삭제됩니다.
공유 포인터는 일반적으로 새로 할당된 객체로 초기화됩니다:
std::shared_ptr<double> doublePointer = std::make_shared<double>(0.37);
std::shared_ptr<Vec3> vec3Pointer =
std::make_shared<Vec3>(1.414214, 2.718281, 1.618034);
std::shared_ptr<Sphere> spherePointer =
std::make_shared<Sphere>(Point(0.0, 0.0, 0.0), 1.0);
C++
복사
*리스팅 22: shared_ptr을 사용한 할당 예제*
make_shared는 생성자 매개변수로 thing 타입의 새 인스턴스를 할당하고, shared_ptr을 반환합니다.
타입은 다음의 반환 타입에서 자동으로 추론되므로
std::make_shared<double>(0.37);...
Plain Text
복사
, C++의 auto 타입 지정자를 사용하여 더 간단하게 작성할 수 있습니다:
auto doublePointer =
std::make_shared<double>(0.37);
auto vec3Pointer =
std::make_shared<Vec3>(1.414214, 2.718281, 1.618034);
auto spherePointer =
std::make_shared<Sphere>(Point(0.0, 0.0, 0.0), 1.0);
C++
복사
*리스팅 23: auto 타입과 함께 shared_ptr을 사용한 할당 예제*
우리는 코드에서 공유 포인터를 사용할 것입니다. 왜냐하면 여러 지오메트리가 공통 인스턴스를 공유할 수 있고 (예를 들어, 모두 같은 색상 머티리얼을 사용하는 여러 구), 메모리 관리를 자동화하고 추론하기 쉽게 만들기 때문입니다.
std::shared_ptr은 memory 헤더에 포함되어 있습니다.
익숙하지 않을 수 있는 두 번째 C++ 기능은 std::vector입니다. 이는 임의 타입의 일반 배열과 유사한 컬렉션입니다. 위에서 우리는 hittable에 대한 포인터 컬렉션을 사용합니다. std::vector는 더 많은 값이 추가될 때 자동으로 증가합니다: objects.push_back(object)는 std::vector 멤버 변수 objects의 끝에 값을 추가합니다.
std::vector는 vector 헤더에 포함되어 있습니다.
마지막으로, 리스팅 21의 using 문은 컴파일러에게 std 라이브러리에서 shared_ptr과 make_shared를 가져올 것임을 알려주므로, 이들을 참조할 때마다 std:: 접두사를 붙일 필요가 없습니다.
Common Constants and Utility Functions(공통 상수 및 유틸리티 함수)
편의상 별도의 헤더 파일에 정의할 몇 가지 수학 상수가 필요합니다. 지금은 무한대만 필요하지만, 나중에 필요할 파이(pi) 값도 함께 정의하겠습니다. 공통으로 사용되는 유용한 상수들과 향후 유틸리티 함수들도 여기에 포함할 것입니다. 이 새로운 헤더인 rtweekend.h는 우리의 일반적인 메인 헤더 파일이 될 것입니다.
#ifndef RTWEEKEND_H
#define RTWEEKEND_H
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
// Constants
constexpr double Infinity = std::numeric_limits<double>::infinity();
constexpr double Pi = 3.1415926535897932385;
// Utility Functions
inline double DegreesToRadians(double degrees)
{
return degrees * Pi / 180.0;
}
// Common Headers
#include "Color.h"
#include "Ray.h"
#include "Vec3.h"
#endif
C++
복사
*리스팅 24: [rtweekend.h] rtweekend.h 공통 헤더*
프로그램 파일들은 rtweekend.h를 먼저 포함하게 되므로, 다른 모든 헤더 파일들(우리 코드의 대부분이 위치할)은 rtweekend.h가 이미 포함되었다고 암묵적으로 가정할 수 있습니다. 헤더 파일들은 여전히 필요한 다른 헤더 파일들을 명시적으로 포함해야 합니다. 이러한 가정을 염두에 두고 몇 가지 업데이트를 진행하겠습니다.
#include <iostream>
Plain Text
복사
*리스팅 25: [color.h] color.h에 대한 rtweekend.h 포함 가정*
#include "Ray.h"
Plain Text
복사
*리스팅 26: [hittable.h] hittable.h에 대한 rtweekend.h 포함 가정*
#include <memory>
#include <vector>
using std::make_shared;
using std::shared_ptr;
Plain Text
복사
*리스팅 27: [hittable_list.h] hittable_list.h에 대한 rtweekend.h 포함 가정*
#include "vec3.h"
Plain Text
복사
*리스팅 28: [sphere.h] sphere.h에 대한 rtweekend.h 포함 가정*
#include <cmath>
#include <iostream>
Plain Text
복사
*리스팅 29: [vec3.h] vec3.h에 대한 rtweekend.h 포함 가정*
그리고 이제 새로운 메인 함수입니다:
#include "RTWeekend.h"
#include "Hittable.h"
#include "HittableList.h"
#include "Sphere.h"
#include "Ray.h"
#include "Color.h"
#include "Vec3.h"
#include <iostream>
double HitSphere(const Point& center, double radius, const Ray& ray)
{
...
}
Color RayColor(const Ray& ray, const Hittable& world)
{
HitRecord hitRecord;
if (world.Hit(ray, 0.0, Infinity, hitRecord))
{
return 0.5 * (hitRecord.Normal + Color(1.0, 1.0, 1.0));
}
Vector3 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);
}
int main()
{
// Image
auto aspectRatio = 16.0 / 9.0;
int imageWidth = 400;
// Calculate the image height, and ensure that it's at least 1
int imageHeight = static_cast<int>(imageWidth / aspectRatio);
imageHeight = (imageHeight < 1) ? 1 : imageHeight;
// World
HittableList world;
world.Add(std::make_shared<Sphere>(Point(0.0, 0.0, -1.0), 0.5));
world.Add(std::make_shared<Sphere>(Point(0.0, -100.5, -1.0), 100.0));
// Camera
auto focalLength = 1.0;
auto viewportHeight = 2.0;
auto viewportWidth = viewportHeight * (static_cast<double>(imageWidth) / imageHeight);
auto cameraCenter = Point(0.0, 0.0, 0.0);
// Calculate the vectors across the horizontal and down the vertical viewport edges
auto viewportU = Vec3(viewportWidth, 0.0, 0.0);
auto viewportV = Vec3(0.0, -viewportHeight, 0.0);
// Calculate the horizontal and vertical delta vectors from pixel to pixel
auto pixelDeltaU = viewportU / imageWidth;
auto pixelDeltaV = viewportV / imageHeight;
// Calculate the location of the upper left pixel
auto viewportUpperLeft =
cameraCenter
- Vec3(0.0, 0.0, focalLength)
- viewportU / 2.0
- viewportV / 2.0;
auto pixel00Location = viewportUpperLeft + 0.5 * (pixelDeltaU + pixelDeltaV);
// Render
std::cout << "P3\n" << imageWidth << ' ' << imageHeight << "\n255\n";
for (int scanlineIndex = 0; scanlineIndex < imageHeight; scanlineIndex++)
{
std::clog
<< "\rScanlines remaining: "
<< (imageHeight - scanlineIndex)
<< ' '
<< std::flush;
for (int pixelIndex = 0; pixelIndex < imageWidth; pixelIndex++)
{
auto pixelCenter =
pixel00Location
+ (pixelIndex * pixelDeltaU)
+ (scanlineIndex * pixelDeltaV);
auto rayDirection = pixelCenter - cameraCenter;
Ray ray(cameraCenter, rayDirection);
Color pixelColor = RayColor(ray, world);
WriteColor(std::cout, pixelColor);
}
}
std::clog << "\rDone. \n";
}
C++
복사
이것은 구들이 위치한 장소를 표면 법선과 함께 시각화한 이미지를 생성합니다. 이는 기하학적 모델의 결함이나 특정 특성을 보는 훌륭한 방법입니다.
*이미지 5: 지면과 함께 법선으로 색칠된 구의 렌더링 결과*
An Interval Class
계속 진행하기 전에, 최솟값과 최댓값을 가진 실수 구간을 관리하기 위한 interval 클래스를 구현하겠습니다. 앞으로 진행하면서 이 클래스를 매우 자주 사용하게 될 것입니다.
#pragma once
#ifndef INTERVAL_H
#define INTERVAL_H
#include "RTWeekend.h"
struct Interval
{
Interval()
: Min(+Infinity)
, Max(-Infinity)
{
}
Interval(double minimum, double maximum)
: Min(minimum)
, Max(maximum)
{
}
double Size() const
{
return Max - Min;
}
bool Contains(double value) const
{
return Min <= value && value <= Max;
}
bool Surrounds(double value) const
{
return Min < value && value < Max;
}
static const Interval Empty;
static const Interval Universe;
double Min = 0.0;
double Max = 0.0;
};
const Interval Interval::Empty(+Infinity, -Infinity);
const Interval Interval::Universe(-Infinity, +Infinity);
#endif
C++
복사
*리스팅 31: [interval.h] 새로운 interval 클래스 소개*
// Common Headers
#include "color.h
#include "interval.h"
#include "ray.h"
#include "vec3.h"
C++
복사
*리스팅 32: [rtweekend.h] 새로운 interval 클래스 포함*
class Hittable
{
public:
virtual ~Hittable() = default;
virtual bool Hit(
const Ray& ray,
const Interval& rayT,
HitRecord& hitRecord
) const = 0;
};
C++
복사
*리스팅 33: [hittable.h] interval을 사용하는 hittable::hit()*
#pragma once
#ifndef HITTABLE_LIST_H
#define HITTABLE_LIST_H
#include "RtWeekend.h"
#include "Hittable.h"
#include <vector>
class HittableList : public Hittable
{
public:
HittableList() = default;
explicit HittableList(const std::shared_ptr<Hittable>& object)
{
Add(object);
}
void Clear()
{
mObjects.clear();
}
void Add(const std::shared_ptr<Hittable>& object)
{
mObjects.push_back(object);
}
bool Hit(
const Ray& ray,
const Interval& rayT,
HitRecord& hitRecord
) const override
{
HitRecord temporaryHitRecord;
bool bHitAnything = false;
auto closestSoFar = rayT.Max;
for (const auto& object : mObjects)
{
Interval currentRayT(rayT.Min, closestSoFar);
if (object->Hit(ray, currentRayT, temporaryHitRecord))
{
bHitAnything = true;
closestSoFar = temporaryHitRecord.T;
hitRecord = temporaryHitRecord;
}
}
return bHitAnything;
}
private:
std::vector<std::shared_ptr<Hittable>> mObjects;
};
#endif
C++
복사
*리스팅 34: [hittable_list.h] interval을 사용하는 hittable_list::hit()*
class Sphere : public Hittable
{
public:
bool Hit(
const Ray& ray,
const Interval& rayT,
HitRecord& hitRecord
) const override
{
...
// 허용 가능한 범위 내에 있는 가장 가까운 근을 찾습니다
auto root = (h - squareRootDiscriminant) / a;
if (!rayT.Surrounds(root))
{
root = (h + squareRootDiscriminant) / a;
if (!rayT.Surrounds(root))
{
return false;
}
}
hitRecord.t = root;
hitRecord.point = ray.At(hitRecord.t);
Vec3 outwardNormal = (hitRecord.point - mCenter) / mRadius;
hitRecord.SetFaceNormal(ray, outwardNormal);
return true;
}
private:
Point mCenter;
double mRadius = 0.0;
};
C++
복사
*리스팅 35: [sphere.h] interval을 사용하는 sphere*
Color RayColor(const Ray& ray, const Hittable& world)
{
HitRecord hitRecord;
if (world.Hit(ray, Interval(0.0, Infinity), hitRecord))
{
return 0.5 * (hitRecord.normal + Color(1.0, 1.0, 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);
}
C++
복사







