光线追踪3-Rays, a Simple Camera, and Background

本文介绍了光线追踪中的射线类概念,展示了如何构建一个基本的光线追踪器,包括计算从像素出发的光线,物体相交检测,以及使用渐变效果来确定颜色。通过设置长宽比和viewport,文章详细描述了相机设置和图像坐标系统的处理方法。
摘要由CSDN通过智能技术生成

所有光线追踪都包含射线类(ray class)和计算沿射线所见颜色的过程。我们可以将射线视为函数 P(t)=A+tb。在这里,P是三维空间中一条直线上的位置。A是射线的起点,b是射线的方向。射线参数t是一个实数(在代码中通常为双精度浮点数)。通过输入不同的t值,P(t)会沿着射线移动点的位置。加入负的t值,你可以在三维线上到达任何位置。对于正的t值,你只能得到A前方的部分,这通常被称为半线段或射线

    我们可以将射线表示为一个类,并将函数P(t)表示为我们称之为ray::at(t)的函数:

#ifndef RAY_H
#define RAY_H

#include "vec3.h"

class ray {  
public:
ray(){  }    
  ray(const point3& origin, const vec3& direction) : orig(origin), dir(direction) {}    
  point3 origin() const  { return orig; }    
vec3 direction() const { return dir; }
point3 at(double t) const {        
	return orig + t*dir;    
}  
private:    
	point3 orig;    
	vec3 dir;
};

#endif

 

4.2 将光线投射到场景中
现在我们准备转向并制作一个光线追踪器。在其核心部分,光线追踪器通过像素发送光线,并计算在这些光线方向上所见的颜色。涉及的步骤包括:

1. 计算从“眼睛”穿过像素的光线

2. 确定光线与哪些物体相交

3. 为最近的交点计算颜色
 

当初次开发光线追踪时,我通常会使用简单的相机来让代码快速运行起来。

    使用正方形图像进行调试时,我经常因为频繁转置 x 和 y 而遇到麻烦,因此我们将使用非正方形图像。正方形图像具有1∶1的长宽比,因为其宽度与高度相同。由于我们想要一个非正方形图像,我们将选择16∶9,因为它非常常见。16∶9的长宽比意味着图像宽度与图像高度的比例为16∶9。换句话说,给定一个16∶9长宽比的图像,

width/height=16/9=1.7778

    以一个实际例子来说,一个宽度为800像素、高度为400像素的图像具有2∶1的长宽比。

    图像的长宽比可以通过其高度与宽度的比例确定。然而,由于我们已经有一个给定的长宽比,更容易设置图像的宽度和长宽比,然后使用这个信息计算其高度。这样,我们可以通过改变图像的宽度来放大或缩小图像,而不会破坏我们所期望的长宽比。我们需要确保求解得到的图像高度至少为1。

除了设置渲染图像的像素尺寸之外,我们还需要设置一个virtual viewport来传递场景光线。viewport是3D世界中的一个虚拟矩形,包含了图像像素位置的网格。如果像素在水平和垂直方向上的间距相等,边界视口将具有与渲染图像相同的长宽比。两个相邻像素之间的距离称为像素间距,正方形像素是标准。

首先,我们将选择一个任意的 viewport 高度为2.0,并根据所需的长宽比来缩放viewport宽度。下面是代码的一部分示例:

auto aspect_ratio = 16.0 / 9.0;
int image_width = 400;

// Calculate the image height, and ensure that it's at least 1.
int image_height = static_cast<int>(image_width / aspect_ratio);   // 225
image_height = (image_height < 1) ? 1 : image_height;

// Viewport widths less than one are ok since they are real valued.
auto viewport_height = 2.0;
Auto viewport_width=viewport_height*(static_cast<double>(image_width)/image_height);//3.555556

    如果你想知道为什么我们在计算viewport宽度时不直接使用aspect_ratio,那是因为aspect_ratio所设定的值是理想的比例,它可能并不是image_width和image_height之间的实际比例。如果允许image_height为实数而不仅仅是整数,那么使用aspect_ratio是可以的。但是image_width和image_height之间的实际比例可以根据代码的两个部分而变化。首先,integer_height被向下取整到最近的整数,这可以增加比例。其次,我们不允许integer_height小于1,这也会改变实际的长宽比。

请注意,aspect_ratio是一个理想的比例,我们用基于整数的image宽度与image高度的比例尽量逼近它。为了使我们的viewport比例与图像比例完全匹配,我们使用计算得到的图像长宽比来确定最终的viewport宽度。

接下来,我们将定义相机中心:一个在3D空间中的点,所有场景光线都将从这个点发出(这通常也被称为眼点)。从相机中心到viewport中心的向量将与viewport垂直。我们将初始设置viewport与相机中心点之间的距离为一个单位。这个距离通常被称为焦距。

为了简单起见,我们将相机中心设置在(0,0,0)处。我们将y轴朝上,x轴朝右,负z轴指向观察方向。(这通常被称为右手坐标系。)

 

现在是不可避免的棘手部分。虽然我们的3D空间遵循上述规定,但这与我们的图像坐标冲突,我们希望将第一个像素设在左上角,并从上到下逐渐移动到右下角的最后一个像素。这意味着我们的图像坐标Y轴是反向的:Y增加时向下移动图像。

当我们扫描图像时,我们将从左上角的像素(像素0,0)开始,从左到右扫描每一行,然后逐行从上到下扫描。为了帮助在像素网格中导航,我们将使用从左边缘到右边缘的向量(Vu),以及从上边缘到下边缘的向量(Vv).

我们的像素网格将从viewport边缘向内缩进半个像素间距。这样,我们的视口区域就被均匀地分成宽度×高度个相同的区域。以下是我们的视口和像素网格的样子:

在这个图中,我们有视口(viewport),用于7*5分辨率图像的像素网格,视口左上角Q,像素P0,0位置,视口向量Vu(viewport_u),视口向量Vv(viewport_v),以及像素增量向量Δu和Δv。
基于这些信息,下面是实现相机功能的代码。我们将插入一个名为ray_color(const ray& r)的函数,该函数返回给定场景光线的颜色——目前我们将其设置为始终返回黑色。

#include "color.h"
#include "vec3.h"
#include "ray.h"

#include <iostream>

color ray_color(const ray& r) { 
   return color(0,0,0); 
}

int main() {
// Image
auto aspect_ratio = 16.0 / 9.0;
int image_width = 400; 

// Calculate the image height, and ensure that it's at least 1. 
int image_height = static_cast<int>(image_width / aspect_ratio); 
image_height = (image_height < 1) ? 1 : image_height; 

// Camera auto focal_length = 1.0; 
auto viewport_height = 2.0; 
auto viewport_width = viewport_height * (static_cast<double>(image_width)/image_height); 
auto camera_center = point3(0, 0, 0); 

// Calculate the vectors across the horizontal and down the vertical viewport edges. auto viewport_u = vec3(viewport_width, 0, 0); 
auto viewport_v = vec3(0, -viewport_height, 0); 

// Calculate the horizontal and vertical delta vectors from pixel to pixel. 
auto pixel_delta_u = viewport_u / image_width; 
auto pixel_delta_v = viewport_v / image_height; 

// Calculate the location of the upper left pixel. 
auto viewport_upper_left = camera_center - vec3(0, 0, focal_length) - viewport_u/2 - viewport_v/2; 
auto pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);

 // Render    
std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";
for (int j = 0; j < image_height; ++j) {        
	std::clog << "\rScanlines remaining: " << (image_height - j) << ' ' << 	std::flush;        
	for (int i = 0; i < image_width; ++i) {
	  auto pixel_center = pixel00_loc + (i * pixel_delta_u) + (j * pixel_delta_v); 	  
	auto ray_direction = pixel_center - camera_center; 
	ray r(camera_center, ray_direction); 
	color pixel_color = ray_color(r);
	write_color(std::cout, pixel_color);
    }    
}
std::clog << "\rDone.                 \n";
}

    请注意,在上面的代码中,我没有将ray_direction转换为单位向量,因为我认为这样做可以使代码更简洁且略微更快。

现在我们将填写ray_color(ray)函数来实现一个简单的渐变效果。该函数将根据将射线方向缩放为单位长度后的y坐标的高度,线性混合白色和蓝色。因为我们是在对向量进行归一化后查看y坐标的高度,所以除了垂直渐变之外,您还会注意到水平渐变的颜色。

我将使用一个标准的图形技巧来线性缩放0.0≤a≤1.0。当a=1.0时,我想要蓝色。当a=0.0时,我想要白色。在两者之间,我希望进行混合。这形成了一个“线性混合”或“线性插值”。这通常被称为两个值之间的插值。

插值始终采用下面的形式:

blendedValue=(1−a)⋅startValue+a⋅endValue,

将a从0到1进行线性插值。

将所有这些组合在一起,我们得到以下结果:
 

#include "color.h"
#include "ray.h"
#include "vec3.h"

#include <iostream>

color ray_color(const ray& r) {
	vec3 unit_direction = unit_vector(r.direction());
	auto a = 0.5*(unit_direction.y() + 1.0);
	return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}...

这样会得到(根据射线Y坐标产生一个从蓝色到白色的渐变。):

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值