ray 类:(位于ray.h中。ray.cpp为空)
#ifndef RAY_H
#define RAY_H
#include "vec3.h"
class ray
{
public:
ray() {}
ray(const vec3& a, const vec3& b) { A = a; B = b; }
vec3 orgin() const { return A; }
vec3 direction() const { return B; }
vec3 point_at_parameter(float t) const { return A + t*B; }
//已知t时,可以获得光线上该点的坐标(向量)
vec3 A;
vec3 B;
/*
virtual ~ray();
protected:
private:
*/
};
#endif // RAY_H
ray类中其实主要是定义两个向量:起点向量(坐标)A和方向向量B。
从ray的方程R(t)=A+t*B可以得知A,B两个向量即可决定一条光线。
#include <iostream>
#include <fstream>
#include "ray.h"
using namespace std;
vec3 color(const ray& r)
{
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);
}
int main()
{
int nx = 200;
int ny = 100;
ofstream outfile( ".\\results\\RaysBackgroundY.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
std::cout << "P3\n" << nx << " " << ny << "\n255\n";
vec3 lower_left_corner(-2.0, -1.0, -1.0);
vec3 horizontal(4.0, 0.0, 0.0);
vec3 vertical(0.0, 2.0, 0.0);
vec3 origin(0.0, 0.0, 0.0);
for (int j = ny-1; j >= 0; j--)
{
for (int i = 0; i < nx; i++)
{
float u = float(i) / float(nx);
float v = float(j) / float(ny);
ray r(origin, lower_left_corner + u*horizontal + v*vertical);
vec3 col = color(r);
int ir = int (255.99*col[0]);
int ig = int (255.99*col[1]);
int ib = int (255.99*col[2]);
outfile << ir << " " << ig << " " << ib << "\n";
std::cout << ir << " " << ig << " " << ib << "\n";
}
}
}
原理分析:
光线起点(也就是eye或者camera)固定的情况下,光线的方向向量的变动范围既形成光线束。光线束即是eye或者camera看到画面。
也就意味着:方向向量的变动范围决定着所能看到画面的范围。
另外,光线中每个光子的频率(颜色)决定这画面的内容。
所以,如果我们要通过光线追踪来画图的话,只需要做两件事情:
第一步,确定光线的方向向量的活动范围函数,从而确定画面的范围、大小(一条光线对应这画面上的一个像素点)。
第二步,对每一条光线(像素点)设置颜色,(高质量图的每个像素点上可能对应多个采样)从而确定画面上的内容。
如下图,光线的起点为(0,0,0),要求在黑框内作图(即光线和黑框平面的交点落在黑框内)
vec3 lower_left_corner(-2.0, -1.0, -1.0);
vec3 horizontal(4.0, 0.0, 0.0);
vec3 vertical(0.0, 2.0, 0.0);
所以,交点坐标可以表示为向量:lower_left_corner + u*horizontal + v*vertical
光线的方向向量 = 交点的向量 - 起点向量,由于起点为原点,所以方向向量=交点向量。
每个交点的u,v的值即为该像素点在整个画面中的位置。
int nx = 200;
int ny = 100;
for (int j = ny-1; j >= 0; j--)
{
for (int i = 0; i < nx; i++)
{
float u = float(i) / float(nx);
float v = float(j) / float(ny);
ray r(origin, lower_left_corner + u*horizontal + v*vertical);
/*由画面中每个像素点在画面中的相对位置每个像素点对应的光线的方向向量从而确定画面的范围/大小。(完成第一步)*/
vec3 col = color(r);
//根据光线对每一个像素点上色。(完成第二步)
}
}
vec3 color(const ray& r)
{
vec3 unit_direction = unit_vector(r.direction());
//对方向向量进行标准化。
float t = 0.5*(unit_direction.y() + 1.0);
//标准化之后的y值在[-1,1]中y+1在[0,2]中0.5*(y+1)在[0,1]中
return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);
//t=0时,color=vec3(1,1,1),乘以255后对应的RGB为(255,255,255)
//t=1时,color=vec3(0.5,0.7,1),乘以255后对应的RGB为(127.5,178.5,255)
//如上两个颜色分别对应着“白色”和“浅蓝色”。
/*画面颜色=(1-t)*“白色”+ t*“浅蓝色”,即画面颜色为“白色”和“浅蓝色”(沿着Y方向)的线性插值的结果。如果要换成X或者Z方向,将上面的.y()改成.x()或者.z()即可。
若要换其他颜色,设置对应的RGB值即可。RGB值和颜色的对应可参考word中“字体颜色设置”“ 其他颜色”“自定义”*/
}
通过“XnView”将结果的.txt转换成.jpg后的图片:
白色、浅蓝色沿着Y轴线性插值:
白色、浅蓝色沿着X轴线性插值:
白色、浅蓝色沿着Z轴线性插值:
白色、粉红色沿着Y轴线性插值: