前言
本节中尝试将材质抽象为一个抽象类,而后在次基础上新增镜面反射材质,继而在场景中再添加两个不同材质的小球。
正文
1. 定义一个 material抽象类
// ------------ material.h ------------ //
#ifndef MATERIAL_H
#define MATERIAL_H
#include "hitable.h"
class material
{
public:
// 定义材质抽象方法:作用: 1.确定反射光线的方向 2.确定反射系数
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};
#endif // !MATERIAL_H
并对hitable.h 做相应修改,在hit_record结构体中记录材质的类型。这里涉及两个头文件相互引用的问题。(所以material 在这里采用了指针类型?)
// ------------ material.cpp ------------ //
#ifndef HITABLEH
#define HITABLEH
#include "ray.h"
class material;
// 使用结构体记录交点信息 与光源的距离 t,坐标向量p, 交点表mian法向量
struct hit_record {
float t;
vec3 p;
vec3 normal;
material* mat_ptr;
};
class hitable {
public:
virtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const = 0;
};
#endif
2. 定义一个镜面反射材质类(metal)与一个漫射材质类(lambertian),继承于材质类
(1)漫反射的类参考前面在main函数中的实现
// -------- lambertian.h -------- //
#ifndef _LAMBERTIAN_H_
#define _LAMBERTIAN_H_
#include "material.h"
class lambertian :public material {
public:
lambertian(const vec3& a) : albedo(a) {}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const;
vec3 albedo; // 衰减系数
};
#endif // !_LAMBERTIAN_H_
// -------- lambertian.cpp -------- //
#include "lambertian.h"
float random01() {
return (rand() % 101) / 100.0;
}
vec3 random_in_unit_sphere() {
vec3 p;
do {
p = 2.0 * vec3(random01(), random01(), random01()) - vec3(1, 1, 1);
} while (p.squared_length() >= 1.0);
return p;
}
bool lambertian::scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered)const {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
scattered = ray(rec.p, target-rec.p);
attenuation = albedo;
return true;
}
(2)镜面反射材质类中
结构大体与漫反射类相似,不同之处在于光线在镜面反射材质表面,反射方向由入射方向与发现方向唯一确定。
// ------------ metal.h ------------ //
#ifndef METAL_H
#define METAL_H
#include "material.h"
class metal {
public:
metal(const vec3& a) :albedo(a) {}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered)const;
vec3 albedo;
};
#endif
知入射光线与法线方向的范围向量,求反射方向
// ------------ metal.cpp ------------ //
#include "metal.h"
vec3 reflect(const vec3& v, const vec3& n) {
return v - 2 * dot(v, n) * n;
}
bool metal::scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered)const{
vec3 reflected = reflect(unit_vector(r_in.getDirection()), rec.normal);
scattered = ray(rec.p, reflected);
attenuation = albedo;
return (dot(scattered.getDirection(), rec.normal) > 0);
}
小球类也需要做相应修改,加入被抽象出的材质属性
// ------------ sphere.h ------------ //
#ifndef SPHERE
#define SPHERE
#include "hitable.h"
#include "material.h"
class sphere :public hitable {
public:
sphere() {}
sphere(vec3 cen, float r, material *m) : center(cen), radius(r), ma(m) {};
virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
vec3 center;
float radius;
material *ma;
};
#endif // !SPHERE
// ------------ sphere.cpp ------------ //
#include "sphere.h"
bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
vec3 oc = r.getOrigin() - center;
float a = dot(r.getDirection(), r.getDirection());
float b = 2 * dot(oc, r.getDirection());
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
if (discriminant > 0) {
// -------- 光线与小球有两个交点 -------- //
float t = (-b - sqrt(discriminant)) / (2.0 * a);
// 1. 先判断小 t, 若满足条件,则交点信息存储到rec中
if (t < t_max && t > t_min) {
rec.t = t;
rec.p = r.getPointAtParameter(t);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = ma;
return true;
}
// 2. 再判断大 t
t = (-b + sqrt(discriminant)) / (2.0 * a);
if (t < t_max && t > t_min) {
rec.t = t;
rec.p = r.getPointAtParameter(t);
rec.normal = (rec.p - center) / radius;
rec.mat_ptr = ma;
return true;
}
}
return false;
}
main.cpp中修改color 函数,并在main函数中添加新的小球
// ------------ main.cpp ------------- //
#include <iostream>
#include <fstream>
#include "vec3.h"
#include "ray.h"
#include "hitable_list.h"
#include "sphere.h"
#include <float.h>
#include "camera.h"
#include "lambertian.h"
#include "metal.h"
float random01();
vec3 random_in_unit_sphere();
// -------- color 函数用于返回背景的颜色 -------- //
vec3 color(const ray& r, hitable *world, int depth) {
hit_record rec;
// 判断光线是否与圆小交
if (world -> hit(r, 0.001, FLT_MAX, rec)) {
// 渲染漫反射效果
ray scattered;
vec3 attenuation;
if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered)) {
return attenuation * color(scattered, world, depth + 1);
}
else {
return vec3(0,0,0);
}
}
else {
vec3 unit_direction = unit_vector(r.getDirection());
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);//white, light blue
}
}
void main() {
int nx = 200;
int ny = 100;
int ns = 100; // 每个像素点的随机采样次数
std::ofstream outfile("PPMTest.txt", std::ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
//std::cout << "P3\n" << nx << " " << ny << "\n255\n";
// 添加几个新的材质球
hitable *list[4];
list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.8, 0.3, 0.3)));
list[1] = new sphere(vec3(0, -100.5, -1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); // 在半部分放一个大球作为地面
list[2] = new sphere(vec3(1, 0, -1), 0.5, new metal(vec3(0.8, 0.6, 0.2)));
list[3] = new sphere(vec3(-1, 0, -1), 0.5, new metal(vec3(0.8, 0.8, 0.8)));
hitable *world = new hitable_list(list, 4);
camera cam;
bool randSample = true;
for (int j = ny - 1; j >= 0; --j) {
for (int i = 0; i < nx; ++i) {
vec3 col = vec3(0,0,0);
if (randSample) {
// 随机采样
for (int s = 0; s < ns; ++s) {
// 对每一个像素点随机采样100次
float u = float(i + (rand() % 101) / 100.0) / float(nx);
float v = float(j + (rand() % 101) / 100.0) / float(ny);
ray r = cam.get_ray(u, v);
vec3 p = r.getPointAtParameter(2.0);
col += color(r, world, 0);
}
col /= float(ns);
}
col = vec3(sqrt(col[0]), sqrt(col[1]), sqrt(col[2]));
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";
}
}
}