法向量,垂直于表面,方向为相交点p减去球心点c的方向向量。 用色彩表可视化单位法向量,让法向量的XYZ值映射到RGB值,使得球有一个渐变的效果,具体做法:考虑x/y/z在[-1,1],r/g/b在[0,1],怎么映射?x/y/z分别+1然后/2 [ cpp:
#include
#include
#include “ray.h”
using namespace std;
//此时该函数的返回值从ch4的bool变为float
float hit_sphere(const vec3& center, float radius, const ray& r)
{
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = 2.0f * dot(oc, r.direction());
float c = dot(oc, oc) - radius * radius;
//判断这个方程有没有根,如果有2个根就是击中
float discrimiant = b * b - 4.0f * a * c;
if (discrimiant < 0.0f)
{
return -1.0f;
}
else
{
return (-b - sqrt(discrimiant)) / (2.0f * a);
}
}
vec3 Color(const ray& r)
{
float t = hit_sphere(vec3(0.0f, 0.0f, -1.0f), 0.5, r);
if (t > 0.0f)
{
//法向量
vec3 N = unit_vector(r.point_at_parameter(t) - vec3(0.0f, 0.0f, -1.0f));
return 0.5f * vec3(N.x() + 1.0f, N.y() + 1.0f, N.z() + 1.0f);
}
//绘制背景
vec3 unit_direction = unit_vector(r.direction());
//t从0到1
t = 0.5f * (unit_direction.y() + 1.0f);
//blended\_value = (1-t)\*start\_value + t\*end\_value
//线性混合,t=1时蓝色,t=0时白色,t介于中间时是混合颜色
return (1.0f - t) \* vec3(1.0f, 1.0f, 1.0f) + t \* vec3(0.5f, 0.7f, 1.0f);
}
int main()
{
ofstream outfile;
outfile.open(“IMG.ppm”);
int nx = 800;
int ny = 400;
outfile << "P3\\n" << nx << " " << ny << "\\n255\\n";
//视锥体左下角
vec3 lower\_left\_corner(-2.0f, -1.0f, -1.0f);
//距离左下角的水平距离
vec3 horizontal(4.0f, 0.0f, 0.0f);
//距离左下角的垂直距离
vec3 vertical(0.0f, 2.0f, 0.0f);
//起始点
vec3 origin(0.0f, 0.0f, 0.0f);
for (int j = ny - 1; j >= 0; j--)
{
for (int i = 0; i < nx; i++)
{
//u从0开始越来越接近1;v从无限接近1开始,越来越接近0
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";
}
}
outfile.close();
return 0;
}
运行效果: [ 下面是多个球的出现: hitable.h 抽象类:
#ifndef hitableH
#define hitableH
#include “ray.h”
class material;
//撞击点信息
struct hit_record
{
//射线参数t
float t;
//撞击点位置向量p
vec3 p;
//撞击点位置向量N
vec3 normal;
material* mat_ptr;
};
//所有能被射线撞击的物体的父类
class hitable
{
public:
//hit()虚函数
virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const = 0;
};
#endif
sphere.h:
#ifndef sphereH
#define sphereH
#include “hitable.h”
class sphere : public hitable {
public:
sphere(){ }
//sphere(vec3 cen, float r, material *m) : center(cen), radius(r), mat_ptr(m) {};
sphere(vec3 cen, float r) : center(cen), radius(r) { };
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
material* mat_ptr;
};
bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = dot(oc, r.direction());
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - a * c;
if (discriminant > 0) {
float temp = (-b - sqrt(discriminant)) / a;
if (temp<t_max && temp> t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
temp = (-b + sqrt(discriminant)) / a;
if (temp<t_max && temp> t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
}
return false;
}
#endif
hitable_list.h:
#ifndef hitableLISTH
#define hitableLISTH
#include “hitable.h”
class hitable_list : public hitable {
public:
hitable_list(){ }
hitable_list(hitable** l, int n){ list = l; list_size = n; }
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
hitable** list;
int list_size;
};
bool hitable_list::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
hit_record temp_rec;
bool hit_anything = false;
double closest_so_far = t_max;
//依次判断列表中所有物体是否被光线撞到
for (int i = 0; i<list_size; i++) {
if (list[i]->hit(r, t_min, closest_so_far, temp_rec)) {
hit_anything = true;
closest_so_far = temp_rec.t;
rec = temp_rec;
}
}
return hit_anything;
}
#endif
cpp:
#include
#include
#include “sphere.h”
#include “hitable_list.h”
using namespace std;
vec3 Color(const ray& r, hitable* world)
{
hit_record rec;
if (world->hit(r, 0.0, FLT_MAX, rec))
{
return 0.5f * vec3(rec.normal.x() + 1.0f, rec.normal.y() + 1.0f, rec.normal.z() + 1.0f);
}
else
{
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5f * (unit_direction.y() + 1.0f);
return (1.0f - t) * vec3(1.0f, 1.0f, 1.0f) + t * vec3(0.5f, 0.7f, 1.0f);
}
}
int main()
{
ofstream outfile;
outfile.open(“IMG2.ppm”);
int nx = 800;
int ny = 400;
outfile << "P3\\n" << nx << " " << ny << "\\n255\\n";
vec3 lower\_left\_corner(-2.0f, -1.0f, -1.0f);
vec3 horizontal(4.0f, 0.0f, 0.0f);
vec3 vertical(0.0f, 2.0f, 0.0f);
vec3 origin(0.0f, 0.0f, 0.0f);
hitable\* list\[2\];
list\[0\] = new sphere(vec3(0.0f, 0.0f, -1.0f), 0.5f);
list\[1\] = new sphere(vec3(0.0f, -100.5f, -1.0f), 100.0f);
hitable\* world = new hitable\_list(list, 2);
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 p = r.point\_at\_parameter(2.0);
vec3 col = Color(r, world);
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";
}
}
outfile.close();
return 0;
}
最终效果: 参考书籍:《Ray Tracing in One Weekend》 RTIOW系列项目地址:GitHub RTIOW系列笔记: RTIOW-ch1:Output an image RTIOW-ch2:The vec3 class RTIOW-ch3:Rays, a simple camera, and background RTIOW-ch4:Adding a sphere RTIOW-ch5:Surface normals and multiple objects RTIOW-ch6:Antialiasing RTIOW-ch7:Diffuse Materials RTIOW-ch8:Metal RTIOW-ch9:Dielectrics RTIOW-ch10:Positionable camera RTIOW-ch11:Defocus Blur RTIOW-ch12:Where next