3.1 光线追踪计算的工作原理
定义多个对象;
确定各个对象的材质;
定义相应的光照;
定义像素的窗口;
for(各个像素)
从像素中心向对象投射一束光线;
计算光线与对象之间的最近碰撞点(如果存在);
if(光线与某一对象发生碰撞)
通过对象的材质以及光照计算像素的颜色值;
else
将像素的颜色值设定为黑色;
在光线追踪计算中,光线可以穿透对象,即使该对象是不透明的。
位于视平面上的像素将垂直于光线,称之为视平面像素。
3.2 场景世界
场景世界包含多个几何对象、光源、相机、视平面以及背景颜色。全部场景元素的位置和方向将使用代表世界坐标或仅采用(x,y,z)。
3.3 光线
一条光线可看作是定义于某一源点o、方向为单位向量d的无限延伸的直线。另外,可通过参数t实现光线的参数化,且t=0处代表了光线的源点。因而,光线上的任一点p可表示为:p=o+td。
光源以及方向在执行光线-对象间的相交计算前,通常定义于世界坐标系统中。
在光线追踪计算中,一般采用下列光线类型
- 主光线,始于各像素中心位置,并位于透视投影中的相机处;
- 次级光线,一般为反射光线,且始于对象表面处;
- 阴影光线,主要用于着色且源于对象表面某处;
- 光照光线,来自于相应的光源并用于模仿全局光照。
在本章中主要讨论主光线。
Ray.h代码如下:
#ifndef __RAY__
#define __RAY__
#include "Point3D.h"
#include "Vector3D.h"
class Ray
{
public:
Point3D o;
Vector3D d;
Ray(void);
Ray(const Point3D& origin, const Vector3D& dir);
Ray(const Ray& ray);
Ray& operator = (const Ray& rhs);
~Ray(void);
};
3.4 光线-对象相交测试
3.4.1 概述
将在区间[x, INF]内计算最小t值。其中x为一个较小的证书,例如t=10^-6^以保证在后续章节中计算结果的正确性。
3.4.2 光线和隐式表面
隐式表面定义:f(x,y,z)=0
采用f(o+td)=0来计算碰撞点。
3.4.3 几何对象
全部几何对象均继承自基类GrametricObject。在本章中奖使用某些简化的数据结构,包括GrametricObject类、Plane类以及Sphere类。
GrametricObject类中的部分声明如下,其中包含了RGBColor,之后会用材质指针来代替RGBColor字段。
class GrametricObject
{
public:
...
virtual bool hit(const Ray& ray, double& tmin, ShadeRec& sr) const = 0;
protected:
RGBColor color;
};
hit()函数参数列表中的ShadeRec对象充当一个工具类,用以储存光线追踪器所需的全部信息,并对光线-对象间的碰撞点进行着色。着色将会计算反射光线的颜色值。ShadeRec类代码如下:
class ShadeRec
{
public:
bool hit_an_object; //did the ray hit an object?
Point3D local_hit_point; //world coordinates of hit point
Normal normal; //normal at hit point
RGBColor color; //used in Chapter 3 only
World& d; //worle reference for shading
ShadeRec(World& wr); //constructor
ShadeRec(const ShadeRec& sr) //copy constructor
~ShadeRec(void); //destructor
ShadeRec& operator = (const ShadeRec& rhs);
//assignment operator
};
ShadeRec::ShadeRec(World& wr) : hit_an_object(false),
local_hit_point(),
normal(),
color(black),
w(wr)
{}
3.4.4 平面
平面的方程为:(p-a)n=0,n为法向量。
将(o+td)代入得,。计算得到t值并可得到碰撞点坐标(更加高效)。
Plane类储存了顶点和发现数据。代码如下:
class Plane : public GeomerricObject
{
public:
Plane(void);
Plane(const Point3D p, const Normal& n);
...
virtual bool hit(const Ray& ray, double& t, ShadeRec& s) const;
private:
Point3D point; //point through which plane passes
Normal normal; //normal to the plane
static const double kEpsilon; //see Chapter 16
}
bool Plane::hit(const Ray& ray, double& t, ShadeRec& s) const
{
double t = (point - ray.o) * normal / (ray.d * normal);
if (t > kEpsilon)
{
tmin = t;
sr.normal = normal;
sr.local_hit_point = ray.o + t * ray.d;
return true;
}
else
{
return false;
}
}
该碰撞函数仅为一个简单的测试版本,碰撞测试一般都较为复杂。
3.4.5 球体
球体方程可以改写为。
代入得。
相当于解一个一元二次方程。
代码如下,并没有对d=0时的相切状态加以测试。
bool Sphere::hit(const Ray& ray, double& tmin, ShadeRec& sr) const
{
double t;
Vector3D temp = ray.o - center;
double a = ray.d * ray.d;
double b = 2.0 * temp * ray.d;
double c = temp * temp - radius * radius;
double disc = b * b - 4.0 * a * c;
if (disc < 0.0)
{
return false;
}
else
{
double e = sqrt(disc);
double denom = 2.0 * a;
t = (-b - c) / denom;
if (t > kEpsilon)
{
tmin = t;
sr.normal = (temp + t * ray.d) / radius;
sr.local_hit_point = ray.o + t * ray.d;
return true;
}
t = (-b + c) / denom;
if (t > kEpsilon)
{
tmin = t;
sr.normal = (temp + t * ray.d) / radius;
sr.local_hit_point = ray.o + t * ray.d;
return true;
}
return false;
}
}
3.5 颜色值
另
a和p为两个浮点数。相关计算包括:
操作 | 定义 | 返回类型 |
---|---|---|
c1 + c2 | (r1 + r2, g1 + g2, b1 + b2) | RBG color |
ac | (ar, ag, ab) | RBG color |
ca | (ar, ag, ab) | RBG color |
c / a | (r / a, g / a, b / a) | RBG color |
c1 = c2 | (r1 = r2, g1 = g2, b1 = b2) | RBG color reference |
c1*c2 | (r1r2, g1g2, b1b2) | RBG color |
c^p | (r^p,g^p,b^p) | RBG color |
c1 += c2 | (r1 += r2, g1 += g2, b1 += b2) | RBG color reference |
3.6 基本的光线追踪器
3.6.1 工作类
需要12个工作类,如下所示:
几何对象 | 跟踪器 | 工具类 | 场景 |
---|---|---|---|
GeometricObject | Tracer | Normal | ViewPlane |
Sphere | SingleSphere | Point3D | World |
- | - | Ray | - |
- | - | RGBColor | - |
- | - | ShadeRec | - |
- | - | Vector3D | - |
在目前阶段,World类、ViewPlane类、ShadeRec类以及GeometricObject类仅采用简化版本。
World类内容见P52。
3.6.3 主函数
int main(void)
{
World w;
w.build();
w.render_scene();
return 0;
}
3.6.3 视平面
ViewPlane类储存了水平、垂直方向上的全部像素以及像素尺寸。
class ViewPlane
{
public:
int hres; //horizontal image resolution
int vres; //vertical image resolution
float s; //pixel size
float gamma; //monitor gamma factor
float inv_gamma; //one over gamma
...
}
光线均始于各像素点的中心。z~w~坐标用于确定黄线的源点且各光线源点的z~w~均相同。
3.6.4 像素和图像
在一般的观察条件下,通过渲染窗口获得的可见场景常称为视域。
视域取决于水平方向和垂直方向上的像素数量及其尺寸。同时,它们还定义了光线源点以及场景采样点的位置。
3.6.5 build()函数
build()函数如下:
void World::build(void)
{
vp.set_hers(200);
vp.set_vres(200);
vp.set_pixel_size(1.0);
vp.set_gamma(1.0);
background_color = black;
tracer_ptr = new SingleSphere(this);
sphere.set_center(0.0);
sphere.set_radius(85.0);
}
3.6.6 渲染场景
函数World::render_scene()负责渲染场景。代码见P57。
3.7 跟踪器
跟踪函数如下:
RGBColor SingleSphere::trace_ray(const Ray& ray) const
{
ShadeRec sr(*world_ptr);
double t;
if (world_ptr->sphere.hit(ray, t, sr))
{
return red;
}
else
{
return black;
}
}
3.8 颜色显示
display_pixel()函数将各个像素的颜色值转化为显示器可以支持的颜色,这个过程设计到3个步骤:
- 色调映射
- gamma值修正
- 整数映射
显示器的亮度值通常与工作电压呈线性关系,因而gamma值修正是必要的。一般有。其中$v$表示工作电压,$\gamma$表示当前显示器的gamma值。修正之后的表达式为:。
3.9 对多个物体实施光线追踪
此时要在World类中添加多个物体,并返回最小值的颜色值进行绘制。