Positionable Camera
카메라는 유전체처럼 디버깅하기 까다로우므로, 항상 점진적으로 개발합니다. 먼저, 조정 가능한 시야각(fov)을 허용해봅시다. 이것은 렌더링된 이미지의 가장자리에서 가장자리까지의 시각적 각도입니다. 이미지가 정사각형이 아니기 때문에, fov는 수평과 수직에서 다릅니다. 저는 항상 수직 fov를 사용합니다. 또한 보통 각도 단위로 지정하고 생성자 내부에서 라디안으로 변환합니다 — 개인적인 취향의 문제입니다.
Camera viewing Geometry
먼저, 광선이 원점에서 출발하여 z=−1 평면으로 향하도록 유지하겠습니다. z=−2 평면이나 다른 것으로 만들 수도 있지만, h를 해당 거리에 대한 비율로 만들어야 합니다. 다음은 우리의 설정입니다:
*그림 18: 카메라 관찰 기하학 (측면에서)*
이것은 다음을 의미합니다
. 이제 우리의 카메라는 다음과 같습니다:
class Camera
{
public:
double aspectRatio = 1.0; // 이미지 너비 대 높이 비율
int imageWidth = 100; // 렌더링된 이미지 너비(픽셀 단위)
int samplesPerPixel = 10; // 각 픽셀당 랜덤 샘플 수
int maxDepth = 10; // 장면으로의 최대 광선 반사 횟수
double vfov = 90; // 수직 시야각(시야)
void Render(const Hittable& world)
{
...
}
private:
...
void Initialize()
{
mImageHeight = int(imageWidth / aspectRatio);
mImageHeight = (mImageHeight < 1) ? 1 : mImageHeight;
mPixelSamplesScale = 1.0 / samplesPerPixel;
mCenter = Point3(0, 0, 0);
// 뷰포트 크기 결정
auto focalLength = 1.0;
auto theta = DegreesToRadians(vfov);
auto h = std::tan(theta / 2);
auto viewportHeight = 2 * h * focalLength;
auto viewportWidth = viewportHeight * (double(imageWidth) / mImageHeight);
// 수평 및 수직 뷰포트 가장자리를 가로지르는 벡터 계산
auto viewportU = Vec3(viewportWidth, 0, 0);
auto viewportV = Vec3(0, -viewportHeight, 0);
// 픽셀 간 수평 및 수직 델타 벡터 계산
mPixelDeltaU = viewportU / imageWidth;
mPixelDeltaV = viewportV / mImageHeight;
// 왼쪽 상단 픽셀의 위치 계산
auto viewportUpperLeft =
mCenter - Vec3(0, 0, focalLength) - viewportU / 2 - viewportV / 2;
mPixel00Loc = viewportUpperLeft + 0.5 * (mPixelDeltaU + mPixelDeltaV);
}
...
};
C++
복사
*리스트 80: [camera.h] 조정 가능한 시야각(fov)을 가진 카메라*
90° 시야각을 사용하여 두 개의 맞닿은 구로 이루어진 간단한 장면으로 이러한 변경 사항을 테스트해봅시다.
int main()
{
HittableList world;
auto R = std::cos(Pi / 4);
auto materialLeft = make_shared<Lambertian>(Color(0, 0, 1));
auto materialRight = make_shared<Lambertian>(Color(1, 0, 0));
world.Add(make_shared<Sphere>(Point3(-R, 0, -1), R, materialLeft));
world.Add(make_shared<Sphere>(Point3(R, 0, -1), R, materialRight));
//auto materialGround = std::make_shared<Lambertian>(Color(0.8, 0.8, 0.0));
//auto materialCenter = std::make_shared<Lambertian>(Color(0.1, 0.2, 0.5));
//auto materialLeft = std::make_shared<Dielectric>(1.50);
//auto materialBubble = std::make_shared<Dielectric>(1.0 / 1.50);
//auto materialRight = std::make_shared<Metal>(Color(0.8, 0.6, 0.2), 1.0);
//world.Add(std::make_shared<Sphere>(Point3(0.0, -100.5, -1.0), 100.0, materialGround));
//world.Add(std::make_shared<Sphere>(Point3(0.0, 0.0, -1.2), 0.5, materialCenter));
//world.Add(std::make_shared<Sphere>(Point3(-1.0, 0.0, -1.0), 0.5, materialLeft));
//world.Add(std::make_shared<Sphere>(Point3(-1.0, 0.0, -1.0), 0.4, materialBubble));
//world.Add(std::make_shared<Sphere>(Point3(1.0, 0.0, -1.0), 0.5, materialRight));
Camera camera;
camera.aspectRatio = 16.0 / 9.0;
camera.imageWidth = 400;
camera.samplesPerPixel = 100;
camera.maxDepth = 50;
camera.vfov = 90;
camera.Render(world);
}
C++
복사
결과는 다음과 같습니다:
*이미지 19: 광각 뷰*
Positioning and Orienting the Camera
임의의 시점을 얻기 위해 먼저 우리가 관심 있는 점들의 이름을 정해봅시다. 카메라를 배치하는 위치를 lookfrom이라 하고, 바라보는 점을 lookat이라 하겠습니다. (나중에 원한다면, 바라볼 점 대신 바라볼 방향을 정의할 수도 있습니다.)
또한 카메라의 롤(roll), 즉 좌우 기울기를 지정하는 방법이 필요합니다. 이는 lookat-lookfrom 축을 중심으로 한 회전입니다. 다르게 생각하면, lookfrom과 lookat을 고정하더라도 코를 중심으로 머리를 회전시킬 수 있습니다. 우리에게 필요한 것은 카메라의 "위쪽(up)" 벡터를 지정하는 방법입니다.
*Figure 19: Camera view direction*
뷰 방향과 평행하지만 않다면 원하는 어떤 위쪽 벡터든 지정할 수 있습니다. 이 위쪽 벡터를 뷰 방향에 직교하는 평면에 투영하여 카메라 상대 위쪽 벡터를 얻습니다. 저는 이를 "뷰 업(view up)" (vup) 벡터라고 부르는 일반적인 관례를 사용합니다. 몇 번의 외적과 벡터 정규화를 거치면, 카메라 방향을 설명하는 완전한 정규 직교 기저 (u,v,w)를 갖게 됩니다. u는 카메라 오른쪽을 가리키는 단위 벡터, v는 카메라 위쪽을 가리키는 단위 벡터, w는 뷰 방향의 반대를 가리키는 단위 벡터(우리는 오른손 좌표계를 사용하므로)이며, 카메라 중심은 원점에 있습니다.
*Figure 20: Camera view up direction*
이전처럼, 고정된 카메라가 −Z를 향했을 때와 마찬가지로, 임의의 뷰 카메라는 −w를 향합니다. vup을 지정하기 위해 월드 업(0,1,0)을 사용할 수 있지만 반드시 그럴 필요는 없다는 점을 기억하세요. 이것이 편리하며 미친 카메라 각도를 실험하기로 결정하기 전까지는 카메라를 자연스럽게 수평으로 유지해줍니다.
class Camera
{
public:
double aspectRatio = 1.0; // 이미지 너비 대 높이 비율
int imageWidth = 100; // 렌더링된 이미지 너비(픽셀 단위)
int samplesPerPixel = 10; // 각 픽셀당 랜덤 샘플 수
int maxDepth = 10; // 장면으로의 최대 광선 반사 횟수
double vfov = 90; // 수직 시야각(시야)
Point3 lookfrom = Point3(0,0,0); // 카메라가 바라보는 위치
Point3 lookat = Point3(0,0,-1); // 카메라가 바라보는 점
Vec3 vup = Vec3(0,1,0); // 카메라 상대 "위쪽" 방향
...
private:
int mImageHeight; // 렌더링된 이미지 높이
double mPixelSamplesScale; // 픽셀 샘플 합계에 대한 색상 스케일 팩터
Point3 mCenter; // 카메라 중심
Point3 mPixel00Loc; // 픽셀 0, 0의 위치
Vec3 mPixelDeltaU; // 오른쪽 픽셀로의 오프셋
Vec3 mPixelDeltaV; // 아래 픽셀로의 오프셋
Vec3 u, v, w; // 카메라 프레임 기저 벡터
void Initialize()
{
mImageHeight = int(imageWidth / aspectRatio);
mImageHeight = (mImageHeight < 1) ? 1 : mImageHeight;
mPixelSamplesScale = 1.0 / samplesPerPixel;
mCenter = lookfrom;
// 뷰포트 크기 결정
auto focalLength = (lookfrom - lookat).Length();
auto theta = DegreesToRadians(vfov);
auto h = std::tan(theta/2);
auto viewportHeight = 2 * h * focalLength;
auto viewportWidth = viewportHeight * (double(imageWidth)/mImageHeight);
// 카메라 좌표 프레임에 대한 u,v,w 단위 기저 벡터 계산
w = UnitVector(lookfrom - lookat);
u = UnitVector(Cross(vup, w));
v = Cross(w, u);
// 뷰포트의 수평 및 수직 가장자리를 가로지르는 벡터 계산
Vec3 viewportU = viewportWidth * u; // 뷰포트 수평 가장자리를 가로지르는 벡터
Vec3 viewportV = viewportHeight * -v; // 뷰포트 수직 가장자리를 따라 내려가는 벡터
// 픽셀 간 수평 및 수직 델타 벡터 계산
mPixelDeltaU = viewportU / imageWidth;
mPixelDeltaV = viewportV / mImageHeight;
// 왼쪽 상단 픽셀의 위치 계산
auto viewportUpperLeft = mCenter - (focalLength * w) - viewportU/2 - viewportV/2;
mPixel00Loc = viewportUpperLeft + 0.5 * (mPixelDeltaU + mPixelDeltaV);
}
...
private:
};
C++
복사
*리스트 82: [camera.h] 위치 및 방향 지정 가능한 카메라
이전 장면으로 돌아가서 새로운 시점을 사용하겠습니다:
int main()
{
HittableList world;
//auto materialLeft = std::make_shared<Lambertian>(Color(0, 0, 1));
//auto materialRight = std::make_shared<Lambertian>(Color(1, 0, 0));
//world.Add(std::make_shared<Sphere>(Point3(-R, 0, -1), R, materialLeft));
//world.Add(std::make_shared<Sphere>(Point3(R, 0, -1), R, materialRight));
auto materialGround = std::make_shared<Lambertian>(Color(0.8, 0.8, 0.0));
auto materialCenter = std::make_shared<Lambertian>(Color(0.1, 0.2, 0.5));
auto materialLeft = std::make_shared<Dielectric>(1.50);
auto materialBubble = std::make_shared<Dielectric>(1.00 / 1.50);
auto materialRight = std::make_shared<Metal>(Color(0.8, 0.6, 0.2), 1.0);
world.Add(std::make_shared<Sphere>(Point3(0.0, -100.5, -1.0), 100.0, materialGround));
world.Add(std::make_shared<Sphere>(Point3(0.0, 0.0, -1.2), 0.5, materialCenter));
world.Add(std::make_shared<Sphere>(Point3(-1.0, 0.0, -1.0), 0.5, materialLeft));
world.Add(std::make_shared<Sphere>(Point3(-1.0, 0.0, -1.0), 0.4, materialBubble));
world.Add(std::make_shared<Sphere>(Point3(1.0, 0.0, -1.0), 0.5, materialRight));
Camera camera;
camera.aspectRatio = 16.0 / 9.0;
camera.imageWidth = 400;
camera.samplesPerPixel = 100;
camera.maxDepth = 50;
camera.vfov = 90;
camera.lookfrom = Point3(-2,2,1);
camera.lookat = Point3(0,0,-1);
camera.vup = Vec3(0,1,0);
camera.Render(world);
}
C++
복사
다음과 같은 결과를 얻습니다:
*이미지 20: 먼 거리 뷰
그리고 시야각(field of view)을 변경할 수 있습니다:
camera.vfov = 20;
C++
복사
다음과 같은 결과를 얻습니다:
*이미지 21: 줌인








