RayTracingonOneWeek 目录
文章目录
前言
- 本书适合从零接触去学习
- 简单容易上手
推荐
- 《Ray Tracing in One Weekend》
- 《An Introduction to Ray Tracing》- blog
- 《Ray Tracing from the Group Up》- blog
- 推荐博客-图形学跟班
- 购买原书:亚马逊美国
- 官方代码
0. 概述
1. CodeBlocks
环境搭建C++
程序
- 官网下载
安装后的编译设置:
Debugger
设置:
运行:
2. 目标
- 本书目标就是创建出此图:
1. 输出图像
实现一个ppm
例子:
- 像素行以行从左到右列出
- 行从头到尾列出
- 按照惯例来看,每个
R/G/B
的范围是从 0.0 0.0 0.0 到 1.0 1.0 1.0 ,稍后将会内部应用高动态范围,将色调图映射到 0 0 0到 1 1 1的范围
- 红色从左到右由黑变红
- 绿色从底向上,由黑变绿
- 红绿相加为黄色,右上角是黄色
#include <iostream>
#include <fstream>
using namespace std;
int main(void) {
// 输出文件
ofstream outfile("./result/toneMap.txt", ios_base::out);
int nx = 200;
int ny = 100;
outfile << "P3\n" << nx << " " << ny << "\n255\n";
for (int j = ny - 1; j >= 0; j --){
for(int i = 0; i < nx; i ++) {
float r = float(i) / float(nx);
float g = float(j) / float(ny);
float b = 0.2;
int ir = int(255.99 * r);
int ig = int(255.99 * g);
int ib = int(255.99 * b);
outfile << ir << " " << ig << " " << ib << "\n";
}
}
}
输出结果:
注意
- 需要将代码输出结果放到文件中
- 用
PPM Viewer
文件打开源代码文件,这里推荐XnView
2. The vec3 class
- 大部分的图像程序会建立储存几何向量和颜色的类
- 几何向量为是4D的:
3D
坐标加上几何体的其次坐标,颜色加上透明度 - 本书只需要表示颜色、位置、方向、偏移即可
头文件
#ifdef VEC3H
#define VEC3H
#include <math.h>
#include <stdlib.h>
#include <iostream>
class vec3
{
public:
vec3(/* args */) {};
vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; }
inline float x() const { return e[0]; }
inline float y() const { return e[1]; }
inline float z() const { return e[2]; }
inline float r() const { return e[0]; }
inline float g() const { return e[1]; }
inline float b() const { return e[2]; }
inline const vec3& operator+() const { return *this; }
inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
inline float operator[](int i) const { return e[i]; }
inline float& operator[](int i) { return e[i]; };
inline vec3& operator+=(const vec3 &v2);
inline vec3& operator-=(const vec3 &v2);
inline vec3& operator*=(const vec3 &v2);
inline vec3& operator/=(const vec3 &v2);
inline vec3& operator*=(const float t);
inline vec3& operator/=(const float t);
inline float length() const { return sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]); }
inline float squared_length() const { return e[0] * e[0] + e[1] * e[1] + e[2] * e[2]; }
inline void make_unit_vector();
float e[3];
};
#endif
operator
重写
- 针对颜色的
inline vec3 operator+(const vec3 &v1, const vec3 &v2){
return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]);
}
inline vec3 operator-(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]);
}
inline vec3 operator*(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] * ve2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]);
}
inline vec3 operator/(const vec3 &v1, connst vec3 &v2) {
return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]);
}
针对位置的
inline float dot(const vec3 &v1, const vec3 &v2) {
return v1.e[0] * v2.e[0] + v1.e[1] * v2.e[1] + v1.e[2] * v2.e[2];
}
inline vec3 cross(const vec3 &v1, const vec3 &v2) {
return vec3( (v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1]),
(-(v1.e[0] * v2.e[2] - v1.e[2] * v2.e[0])),
(v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]));
}
使单位向量与输入向量的方向相同
inline vec3 unit_vector(vec3 v) {
return v / v.length();
}
更改头文件去使用
#include <iostream>
#include "vec3.h"
using namespace std;
int main() {
int nx = 200;
int ny = 100;
cout << "P3\n" << nx << " " << ny << "\n255\n";
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(float(i) / float(nx), float(j) / float(ny), 0.2);
int ir = int(255.99 * col[0]);
int ig = int(255.99 * col[1]);
int ib = int(255.99 * col[2]);
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
全部代码
vec3.h
#ifndef VEC3H
#define VEC3H
#include <math.h>
#include <stdlib.h>
#include <iostream>
class vec3
{
public:
vec3() {};
vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; }
inline float x() const { return e[0]; }
inline float y() const { return e[1]; }
inline float z() const { return e[2]; }
inline float r() const { return e[0]; }
inline float g() const { return e[1]; }
inline float b() const { return e[2]; }
inline const vec3& operator+() const { return *this; }
inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
inline float operator[](int i) const { return e[i]; }
inline float& operator[](int i) { return e[i]; };
inline vec3& operator+=(const vec3 &v2);
inline vec3& operator-=(const vec3 &v2);
inline vec3& operator*=(const vec3 &v2);
inline vec3& operator/=(const vec3 &v2);
inline vec3& operator*=(const float t);
inline vec3& operator/=(const float t);
// 新增声明
inline float dot(const vec3 &v1, const vec3 &v2);
inline vec3 cross(const vec3 &v1, const vec3 &v2);
inline float length() const { return sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]); }
inline float squared_length() const { return e[0] * e[0] + e[1] * e[1] + e[2] * e[2]; }
inline void make_unit_vector();
float e[3];
};
inline vec3 operator+(const vec3 &v1, const vec3 &v2){
return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]);
}
inline vec3 operator-(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]);
}
inline vec3 operator*(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]);
}
inline vec3 operator/(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]);
}
// 数量积
inline float dot(const vec3 &v1, const vec3 &v2) {
return v1.e[0] * v2.e[0] + v1.e[1] * v2.e[1] + v1.e[2] * v2.e[2];
}
// 叉乘
inline vec3 cross(const vec3 &v1, const vec3 &v2) {
return vec3( (v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1]),
(-(v1.e[0] * v2.e[2] - v1.e[2] * v2.e[0])),
(v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]));
}
// 新增
inline vec3 operator*(float t, const vec3 &v){
return vec3(t * v.e[0], t * v.e[1], t * v.e[2]);
}
inline vec3 operator/(vec3 v, float t){
return vec3(v.e[0] / t, v.e[1] / t, v.e[2] / t);
}
inline vec3 operator*(const vec3 &v, float t) {
return vec3(t * v.e[0], t * v.e[1], t * v.e[2]);
}
inline vec3 unit_vector(vec3 v) {
return v / v.length();
}
#endif
vec3.cpp
#include <iostream>
#include <fstream>
#include "vec3.h"
using namespace std;
int main() {
int nx = 200;
int ny = 100;
ofstream outfile(".\\image2.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
cout << "P3\n" << nx << " " << ny << "\n255\n";
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(float(i) / float(nx), float(j) / float(ny), 0.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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
3. 光线,简单相机和背景
- 所有光线跟踪器都有一个光线类,一个计算沿着光线看到什么颜色的类
- 光线公式:
p
(
t
)
=
A
+
t
×
B
p(t) = A + t \times B
p(t)=A+t×B,
p
是3D
图的位置,A
是光源,B
是光线的方向,t
是个浮点数
ray.h
#ifndef RAYH_H
#define RAYH_H
#include "vec3.h"
class ray
{
public:
ray() {}
ray(const vec3& a, const vec3& b) { A = a; B = b; }
vec3 origin() const { return A; }
vec3 direction() const { return B; }
vec3 point_at_parameter(float t) const { return A + t * B; }
vec3 A;
vec3 B;
};
#endif // RAYH_H
- 光线跟踪器的核心是通过像素发送光线,并计算在这些光线的方向上看到的颜色
- 它的形式是计算哪一条光线从眼睛到一个像素,计算那一条光线的相交点,然后计算相交点的颜色
- 在第一次开发光线跟踪器时,我总是用一个简单的摄像头来启动和运行代码。我还制作了一个简单的color(ray)函数,返回背景的颜色(一个简单的渐变色)
- 我经常在使用正方形图像进行调试时遇到麻烦,因为我太频繁地转置x和y,所以我将坚持使用200x100图像。我会把“眼睛”(或相机中心,如果你想到一个相机)在(0,0,0)。我让y轴向上,x轴向右。为了尊重右手坐标系的对流,进入屏幕的是负z轴。我将从左下角遍历屏幕,并沿屏幕两侧使用两个偏移向量在屏幕上移动光线端点。请注意,我没有将光线方向设置为单位长度向量,因为我认为不这样做会使代码更简单、速度稍快。
- 稍后增加抗锯齿
#include <iostream>
#include <fstream>
#include "RAYH.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(".\\image.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";
}
}
}
4. 添加球体
- 常在光线中用球体,因为计算是否落到球体的计算直接简单
( x − c x ) ∗ ( x − c x ) + ( y − c y ) ∗ ( y − c y ) + ( z − c z ) ∗ ( z − c z ) = R ∗ R (x-cx)*(x-cx) + (y-cy)*(y-cy) + (z-cz)*(z-cz)= R*R (x−cx)∗(x−cx)+(y−cy)∗(y−cy)+(z−cz)∗(z−cz)=R∗R
- 在图形学中,你几乎总是希望你的公式是向量的形式,所以所有的x/y/z都在vec3类中。您可能会注意到,从中心C=(cx,cy,cz)到点p=(x,y,z)的向量是(p-C)。点((p-C),(p-C))=(y-cy)(y-cy)+(z-cz)(z-cz)。所以向量形式的球面方程是:
d o t ( ( p − c ) , ( p − c ) ) = R ∗ R dot((p - c),(p - c)) = R*R dot((p−c),(p−c))=R∗R
- 如果考虑到光线:
d o t ( ( p ( t ) − c ) , ( p ( t ) − c ) ) = R ∗ R dot((p(t) - c),(p(t) - c)) = R*R dot((p(t)−c),(p(t)−c))=R∗R
即为:
-
d o t ( ( A + t ∗ B − C ) , ( A + t ∗ B − C ) ) = R ∗ R dot((A + t*B - C),(A + t*B - C)) = R*R dot((A+t∗B−C),(A+t∗B−C))=R∗R
-
t ∗ t ∗ d o t ( B , B ) + 2 ∗ t ∗ d o t ( A − C , A − C ) + d o t ( C , C ) − R ∗ R = 0 t*t*dot(B,B) + 2*t*dot(A-C,A-C) + dot(C,C) - R*R = 0 t∗t∗dot(B,B)+2∗t∗dot(A−C,A−C)+dot(C,C)−R∗R=0
所以:
- a = d o t ( B , B ) a = dot(B, B) a=dot(B,B)
- b = 2 × d o t ( B , ( A − C ) ) b = 2 \times dot(B, (A-C)) b=2×dot(B,(A−C))
- c = d o t ( A − C , A − C ) − R ∗ R c = dot(A-C, A-C) - R * R c=dot(A−C,A−C)−R∗R
#include <iostream>
#include <fstream>
#include "RAYH.h"
using namespace std;
bool 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.0 * dot(oc, r.direction());
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
return (discriminant > 0);
}
vec3 color(const ray& r){
if(hit_sphere(vec3(0, 0, -1), 0.5, r))
return vec3(1, 0, 0);
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(".\\image.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";
}
}
}
5. 曲面法线和多个对象
首先,让我们得到一个曲面法线,这样我们就可以着色。这是一个垂直于表面的向量,按照惯例,它指出。一个设计决策是这些法线(同样按惯例)是否为单位长度。这是方便着色,所以我会说是的,但我不会强制在代码中。这可能会导致一些细微的错误,所以请注意这是个人喜好,就像大多数这样的设计决策一样。对于球体,法线位于命中点减去中心的方向上:
在地球上,这意味着从地球中心到你的向量指向正上方。现在让我们把它加入到代码中,并对其进行着色。我们还没有任何灯光或其他东西,所以让我们用颜色贴图来可视化法线。用于可视化法线的一个常见技巧(因为假设N是单位长度向量很容易而且有些直观,所以每个分量都在-1和1之间)是将每个分量映射到0到1的间隔,然后将x/y/z映射到r/g/b。对于法线,我们需要命中点,而不仅仅是命中与否。假设最近的命中点(最小的t)。代码中的这些变化让我们计算并可视化N:
#include <iostream>
#include <fstream>
#include "RAYH.h"
using namespace std;
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.0 * dot(oc, r.direction());
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
if(discriminant < 0){
return -1.0;
}
else{
return (-b - sqrt(discriminant)) / (2.0 * a);
}
}
vec3 color(const ray& r){
float t = hit_sphere(vec3(0, 0, -1), 0.5, r);
if(t > 0.0){
vec3 N = unit_vector(r.point_at_parameter(t) - vec3(0, 0, -1));
return 0.5 * vec3(N.x() + 1, N.y() + 1, N.z() + 1);
}
vec3 unit_direction = unit_vector(r.direction());
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(".\\image2.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";
}
}
}
- 不修改
bool
,并且将溢出值(-2147483648 -2147483648 -2147483648)
修改为(0 127 127)
,得到此图
- 如果想要创建多个球体呢?这时候需要创建
abstract class
- 这个可命中的抽象类将有一个接受光线的命中函数。大多数射线跟踪器都发现,在tmin到tmax之间添加一个有效的命中间隔是很方便的,因此只有当tmin<t<tmax时,命中才会“计数”。对于初始光线,这是正t,但正如我们将看到的,它可以帮助代码中的一些细节,使时间间隔tmin到tmax。一个设计问题是,如果我们碰到某个东西,是否要计算法线;我们在搜索时可能会碰到更近的东西,我们只需要最接近的东西的法线。我将使用简单的解决方案,并计算一束我将存储在某种结构中的东西。我知道在某个时刻我们需要运动模糊,所以我会添加一个时间输入变量。下面是抽象类:
hitable.h
#ifndef HITABLE
#define HITABLE
#include "RAYH.h"
struct hit_record {
float t;
vec3 p;
vec3 normal;
};
class hitable
{
public:
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) : center(cen), radius(r) {};
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
};
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(b * b - a * c)) / 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;
return true;
}
temp = (-b + sqrt(b * b - a * c)) / 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;
return true;
}
}
return false;
}
#endif
- 对象列表 -
hitablelist.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 // HITABLELISTH
main
#include <iostream>
#include <limits>
#include <fstream>
#include "sphere.h"
#include "hitablelist.h"
#include "float.h"
using namespace std;
vec3 color(const ray& r, hitable *world) {
hit_record rec;
if(world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
return 0.5 * vec3(rec.normal.x() + 1, rec.normal.y() + 1, rec.normal.z() + 1);
}else {
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(".\\image3.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
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);
hitable *list[2];
list[0] = new sphere(vec3(0,0,-1), 0.5);
list[1] = new sphere(vec3(0, -100.5, -1), 100);
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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
6. 抗锯齿
- 当真正的相机拍照时,边缘通常没有锯齿,因为边缘像素是一些前景和一些背景的混合。我们可以通过平均每个像素内的一组样本来获得相同的效果。我们将不打扰分层,这是有争议的,但通常是我的节目。对于某些光线跟踪器来说,这是非常关键的,但是我们正在编写的那种通用的光线跟踪器并没有从中获得太多的好处,它会使代码更加难看。我们对camera类进行了一些抽象,以便稍后制作更酷的camera。
- 我们需要的是一个随机数生成器,它返回真实的随机数。传统上,C++没有标准的随机数生成器,但是大多数系统都有DRAND48(),它是我在这里使用的。但是,C++的更新版本已经解决了这个问题。无论您的基础结构是什么,都可以找到一个函数,该函数返回一个规范随机数,按照惯例,该随机数返回范围为0<=ran<1的随机实数。1前面的“小于”很重要,因为我们有时会利用它。对于一个给定的像素,我们在该像素内有几个样本,并通过每个样本发送光线。然后对这些光线的颜色进行平均:
camera.h
#ifndef CAMERAH
#define CAMERAH
#include "RAYH.h"
class camera {
public:
camera() {
lower_left_corner = vec3(-2.0, -1.0, -1.0);
horizontal = vec3(4.0, 0.0, 0.0);
vertical = vec3(0.0, 2.0, 0.0);
origin = vec3(0.0, 0.0, 0.0);
}
ray get_ray(float u, float v) { return ray(origin, lower_left_corner + u * horizontal + v * vertical - origin); }
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
};
#endif // CAMERAH
main.cpp
#include <iostream>
#include <limits>
#include <fstream>
#include "sphere.h"
#include "hitablelist.h"
#include "float.h"
#include "camera.h"
using namespace std;
vec3 color(const ray& r, hitable *world) {
hit_record rec;
if(world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
return 0.5 * vec3(rec.normal.x() + 1, rec.normal.y() + 1, rec.normal.z() + 1);
}else {
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;
int ns = 100;
ofstream outfile(".\\image3.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
cout << "P3\n" << nx << " " << ny << "\n255\n";
hitable *list[2];
list[0] = new sphere(vec3(0,0,-1), 0.5);
list[1] = new sphere(vec3(0, -100.5, -1), 100);
hitable *world = new hitable_list(list, 2);
camera cam;
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(0,0,0);
for(int s = 0; s < ns; s++){
float random = rand() % (100) / (float)(100);
float u = float(i + random) / float(nx);
float v = float(j + random) / float(ny);
ray r = cam.get_ray(u, v);
vec3 p = r.point_at_parameter(2.0);
col += color(r, world);
}
col /= float(ns);
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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
- 此处使用了
operator/=,+=(float)
,会报错,因此需要在前面的vec3.h
中新增此函数的定义
// charpter 6.
inline vec3& vec3::operator+=(const vec3 &v){
e[0] += v.e[0];
e[1] += v.e[1];
e[2] += v.e[2];
return *this;
}
inline vec3& vec3::operator/=(const vec3 &v){
e[0] /= v.e[0];
e[1] /= v.e[1];
e[2] /= v.e[2];
return *this;
}
inline vec3& vec3::operator*=(const float t) {
e[0] *= t;
e[1] *= t;
e[2] *= t;
return *this;
}
inline vec3& vec3::operator/=(const float t){
float k = 1.0 / t;
e[0] *= k;
e[1] *= k;
e[2] *= k;
return *this;
}
7. 漫反射材质
现在我们有了每个像素的对象和多条光线,我们可以制作一些逼真的材质。我们将从漫反射(无光)材质开始。一个问题是,我们是否可以混合和匹配形状和材质(因此我们将球体指定为材质),或者是否将其组合在一起,以便几何体和材质紧密绑定(这对于几何体和材质链接的程序对象可能很有用)。我们将使用单独的-这在大多数渲染器中是常见的-但一定要注意其局限性。不发光的漫反射物体只是呈现出周围环境的颜色,但它们用自己的本色来调节这种颜色。从漫反射表面反射的光的方向是随机的。因此,如果我们将三条射线送入两个漫反射表面之间的裂缝中,它们将具有不同的随机行为:
它们也可能被吸收而不是反映出来。表面越暗,吸收的可能性就越大。(所以天黑了!)实际上,任何随机化方向的算法都会生成看起来无光的曲面。最简单的方法之一就是对理想的漫反射曲面完全正确。(我以前是作为一个懒惰的黑客来做的,但是我博客上的一个评论显示它实际上是数学上理想的朗伯函数。)从单位半径球体中选取一个与命中点相切的随机点s,然后从命中点p向随机点s发送一条光线。这个球体有中心(p+N):
我们还需要一种方法,在以原点为中心的单位半径球体中选取一个随机点。我们将使用通常最简单的算法:拒绝方法。首先,我们在单位立方体中选取一个随机点,其中x、y和z都在-1到+1之间。我们拒绝此点,如果该点在球体之外,请重试。do/while结构非常适合:
main.cpp
#include <iostream>
#include <limits>
#include <fstream>
#include "sphere.h"
#include "hitablelist.h"
#include "float.h"
#include "camera.h"
using namespace std;
vec3 random_in_unit_sphere() {
vec3 p;
float random = rand() % (100) / (float)(100);
do{
p = 2.0 * vec3(random, random, random) - vec3(1,1,1);
}while(dot(p,p) >= 1.0);
return p;
}
vec3 color(const ray& r, hitable *world) {
hit_record rec;
if(world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
vec3 traget = rec.p + rec.normal + random_in_unit_sphere();
return 0.5 * color( ray(rec.p, target - rec.p), world);
}else {
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;
int ns = 100;
ofstream outfile(".\\image3.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
cout << "P3\n" << nx << " " << ny << "\n255\n";
hitable *list[2];
list[0] = new sphere(vec3(0,0,-1), 0.5);
list[1] = new sphere(vec3(0, -100.5, -1), 100);
hitable *world = new hitable_list(list, 2);
camera cam;
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(0,0,0);
for(int s = 0; s < ns; s++){
float random = rand() % (100) / (float)(100);
float u = float(i + random) / float(nx);
float v = float(j + random) / float(ny);
ray r = cam.get_ray(u, v);
vec3 p = r.point_at_parameter(2.0);
col += color(r, world);
}
col /= float(ns);
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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
main.cpp
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";
cout << ir << " " << ig << " " << ib << "\n";
- 使用以上会出现卡顿,没有结果
- 因为
drand48()
只出现在linux
中,windows
中不存在,因此需要自己写drand48.h
drand.h
#ifndef DRAND48_H
#define DRAND48_H
#include <stdlib.h>
#define m 0x100000000LL
#define c 0xB16
#define a 0x5DEECE66DLL
static unsigned long long seed = 1;
double drand48(void)
{
seed = (a * seed + c) & 0xFFFFFFFFFFFFLL;
unsigned int x = seed >> 16;
return ((double)x / (double)m);
}
void srand48(unsigned int i)
{
seed = (((long long int)i) << 16) | rand();
}
#endif
main.cpp
#include <iostream>
#include <limits>
#include <fstream>
#include "sphere.h"
#include "hitablelist.h"
#include "float.h"
#include "camera.h"
#include "drand.h"
using namespace std;
vec3 random_in_unit_sphere() {
vec3 p;
/*float random = rand() % (100) / (float)(100);*/
do{
p = 2.0 * vec3(drand48(), drand48(), drand48()) - vec3(1,1,1);
}while(dot(p,p) >= 1.0);
return p;
}
vec3 color(const ray& r, hitable *world) {
hit_record rec;
if(world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
return 0.5 * color( ray(rec.p, target - rec.p), world);
}else {
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;
int ns = 100;
ofstream outfile(".\\image3.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
cout << "P3\n" << nx << " " << ny << "\n255\n";
hitable *list[2];
list[0] = new sphere(vec3(0,0,-1), 0.5);
list[1] = new sphere(vec3(0, -100.5, -1), 100);
hitable *world = new hitable_list(list, 2);
camera cam;
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(0,0,0);
for(int s = 0; s < ns; s++){
float random = rand() % (100) / (float)(100);
float u = float(i + random) / float(nx);
float v = float(j + random) / float(ny);
ray r = cam.get_ray(u, v);
vec3 p = r.point_at_parameter(2.0);
col += color(r, world);
}
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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
8. 金属
如果我们希望不同的物体有不同的材料,我们就有一个设计决定。我们可以得到一种具有许多参数的通用材料,不同的材料类型只需将其中的一些参数归零。这是一个不错的方法。或者我们可以有一个抽象的material类来封装行为。我是后一种方法的粉丝。对于我们的节目材料需要做两件事:
- 产生散射光线(或者说它吸收了入射光线)
- 如果是散射的,说明光线应该衰减多少这表明了抽象类:
material.h
class material {
public:
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};
成功的记录是避免一堆争论,这样我们就可以把我们想要的任何信息都塞进里面。你可以用论点来代替,这是品味的问题。Hitables和materials需要相互了解,所以有一些循环引用。在C++中,你只需要提醒编译器指针是一个类,下面的hitable
类中的“类材料”是这样的:
hitable.h
#ifndef HITABLEH
#define HITABLEH
#include "RAYH.h"
class material;
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
- 漫反射是会衰弱的
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;/*scatter()获取反射光线和衰减系数*/
vec3 albedo;/*保存衰减系数*/
};
#endif // LAMBERTIAN_H
metal.h
- 再增加对光线的反射公式:
#ifndef METAL_H
#define METAL_H
#include <material.h>
class metal : public material
{
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_H
sphere.h
#ifndef SPHEREH
#define SPHEREH
#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 tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
material *ma;
};
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(b * b - a * c)) / 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;
return true;
}
temp = (-b + sqrt(b * b - a * c)) / 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;
return true;
}
}
return false;
}
#endif
main.cpp
- 回到
main.cpp
修改color
vec3 color(const ray& r, hitable *world) {
hit_record rec;
if(world->hit(r, 0.0, (numeric_limits<float>::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.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;
int ns = 100;
ofstream outfile(".\\image3.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
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, 2);
camera cam;
for(int j = ny - 1; j >= 0; j--){
for(int i = 0; i < nx; i++) {
vec3 col(0,0,0);
for(int s = 0; s < ns; s++){
float u = float(i + drand48()) / float(nx);
float v = float(j + drand48()) / float(ny);
ray r = cam.get_ray(u, v);
vec3 p = r.point_at_parameter(2.0);
col += color(r, world);
}
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";
cout << ir << " " << ig << " " << ib << "\n";
}
}
}
Final
- 最终光追代码
vec3.h
#ifndef VEC3H
#define VEC3H
#include <math.h>
#include <stdlib.h>
#include <iostream>
class vec3 {
public:
vec3() {}
vec3(float e0, float e1, float e2) { e[0] = e0; e[1] = e1; e[2] = e2; }
inline float x() const { return e[0]; }
inline float y() const { return e[1]; }
inline float z() const { return e[2]; }
inline float r() const { return e[0]; }
inline float g() const { return e[1]; }
inline float b() const { return e[2]; }
inline const vec3& operator+() const { return *this; }
inline vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
inline float operator[](int i) const { return e[i]; }
inline float& operator[](int i) { return e[i]; }
inline vec3& operator+=(const vec3 &v2);
inline vec3& operator-=(const vec3 &v2);
inline vec3& operator*=(const vec3 &v2);
inline vec3& operator/=(const vec3 &v2);
inline vec3& operator*=(const float t);
inline vec3& operator/=(const float t);
inline float length() const { return sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]); }
inline float squared_length() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; }
inline void make_unit_vector();
float e[3];
};
inline std::istream& operator>>(std::istream &is, vec3 &t) {
is >> t.e[0] >> t.e[1] >> t.e[2];
return is;
}
inline std::ostream& operator<<(std::ostream &os, const vec3 &t) {
os << t.e[0] << " " << t.e[1] << " " << t.e[2];
return os;
}
inline void vec3::make_unit_vector() {
float k = 1.0 / sqrt(e[0]*e[0] + e[1]*e[1] + e[2]*e[2]);
e[0] *= k; e[1] *= k; e[2] *= k;
}
inline vec3 operator+(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] + v2.e[0], v1.e[1] + v2.e[1], v1.e[2] + v2.e[2]);
}
inline vec3 operator-(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] - v2.e[0], v1.e[1] - v2.e[1], v1.e[2] - v2.e[2]);
}
inline vec3 operator*(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] * v2.e[0], v1.e[1] * v2.e[1], v1.e[2] * v2.e[2]);
}
inline vec3 operator/(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[0] / v2.e[0], v1.e[1] / v2.e[1], v1.e[2] / v2.e[2]);
}
inline vec3 operator*(float t, const vec3 &v) {
return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}
inline vec3 operator/(vec3 v, float t) {
return vec3(v.e[0]/t, v.e[1]/t, v.e[2]/t);
}
inline vec3 operator*(const vec3 &v, float t) {
return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}
inline float dot(const vec3 &v1, const vec3 &v2) {
return v1.e[0] * v2.e[0]
+ v1.e[1] * v2.e[1]
+ v1.e[2] * v2.e[2];
}
inline vec3 cross(const vec3 &v1, const vec3 &v2) {
return vec3(v1.e[1] * v2.e[2] - v1.e[2] * v2.e[1],
v1.e[2] * v2.e[0] - v1.e[0] * v2.e[2],
v1.e[0] * v2.e[1] - v1.e[1] * v2.e[0]);
}
inline vec3& vec3::operator+=(const vec3 &v){
e[0] += v.e[0];
e[1] += v.e[1];
e[2] += v.e[2];
return *this;
}
inline vec3& vec3::operator*=(const vec3 &v){
e[0] *= v.e[0];
e[1] *= v.e[1];
e[2] *= v.e[2];
return *this;
}
inline vec3& vec3::operator/=(const vec3 &v){
e[0] /= v.e[0];
e[1] /= v.e[1];
e[2] /= v.e[2];
return *this;
}
inline vec3& vec3::operator-=(const vec3& v) {
e[0] -= v.e[0];
e[1] -= v.e[1];
e[2] -= v.e[2];
return *this;
}
inline vec3& vec3::operator*=(const float t) {
e[0] *= t;
e[1] *= t;
e[2] *= t;
return *this;
}
inline vec3& vec3::operator/=(const float t) {
float k = 1.0f/t;
e[0] *= k;
e[1] *= k;
e[2] *= k;
return *this;
}
inline vec3 unit_vector(vec3 v) {
return v / v.length();
}
#endif
ray.h
#ifndef RAYH
#define RAYH
#include "vec3.h"
class ray
{
public:
ray() {}
ray(const vec3& a, const vec3& b) { A = a; B = b; }
vec3 origin() const { return A; }
vec3 direction() const { return B; }
vec3 point_at_parameter(float t) const { return A + t*B; }
vec3 A;
vec3 B;
};
#endif
hitable.h
#ifndef HITABLEH
#define HITABLEH
#include "ray.h"
class material;
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
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
random.h
#ifndef RANDOMH
#define RANDOMH
#include <cstdlib>
inline double random_double() {
return rand() / (RAND_MAX + 1.0);
}
#endif
camera.h
#ifndef CAMERAH
#define CAMERAH
#include "ray.h"
#include "random.h"
vec3 random_in_unit_disk() {
vec3 p;
do {
p = 2.0*vec3(random_double(),random_double(),0) - vec3(1,1,0);
} while (dot(p,p) >= 1.0);
return p;
}
class camera {
public:
camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist) {
// vfov is top to bottom in degrees
lens_radius = aperture / 2;
float theta = vfov*M_PI/180;
float half_height = tan(theta/2);
float half_width = aspect * half_height;
origin = lookfrom;
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup, w));
v = cross(w, u);
lower_left_corner = origin - half_width*focus_dist*u -half_height*focus_dist*v - focus_dist*w;
horizontal = 2*half_width*focus_dist*u;
vertical = 2*half_height*focus_dist*v;
}
ray get_ray(float s, float t) {
vec3 rd = lens_radius*random_in_unit_disk();
vec3 offset = u * rd.x() + v * rd.y();
return ray(origin + offset, lower_left_corner + s*horizontal + t*vertical - origin - offset);
}
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
vec3 u, v, w;
float lens_radius;
};
#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) {};
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
material.h
#ifndef MATERIALH
#define MATERIALH
#include "ray.h"
#include "hitable.h"
#include "random.h"
struct hit_record;
float schlick(float cosine, float ref_idx) {
float r0 = (1-ref_idx) / (1+ref_idx);
r0 = r0*r0;
return r0 + (1-r0)*pow((1 - cosine),5);
}
bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) {
vec3 uv = unit_vector(v);
float dt = dot(uv, n);
float discriminant = 1.0 - ni_over_nt*ni_over_nt*(1-dt*dt);
if (discriminant > 0) {
refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminant);
return true;
}
else
return false;
}
vec3 reflect(const vec3& v, const vec3& n) {
return v - 2*dot(v,n)*n;
}
vec3 random_in_unit_sphere() {
vec3 p;
do {
p = 2.0*vec3(random_double(),random_double(),random_double()) - vec3(1,1,1);
} while (p.squared_length() >= 1.0);
return p;
}
class material {
public:
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};
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 target = rec.p + rec.normal + random_in_unit_sphere();
scattered = ray(rec.p, target-rec.p);
attenuation = albedo;
return true;
}
vec3 albedo;
};
class metal : public material {
public:
metal(const vec3& a, float f) : albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; }
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
attenuation = albedo;
return (dot(scattered.direction(), rec.normal) > 0);
}
vec3 albedo;
float fuzz;
};
class dielectric : public material {
public:
dielectric(float ri) : ref_idx(ri) {}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
vec3 outward_normal;
vec3 reflected = reflect(r_in.direction(), rec.normal);
float ni_over_nt;
attenuation = vec3(1.0, 1.0, 1.0);
vec3 refracted;
float reflect_prob;
float cosine;
if (dot(r_in.direction(), rec.normal) > 0) {
outward_normal = -rec.normal;
ni_over_nt = ref_idx;
// cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();
cosine = dot(r_in.direction(), rec.normal) / r_in.direction().length();
cosine = sqrt(1 - ref_idx*ref_idx*(1-cosine*cosine));
}
else {
outward_normal = rec.normal;
ni_over_nt = 1.0 / ref_idx;
cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();
}
if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted))
reflect_prob = schlick(cosine, ref_idx);
else
reflect_prob = 1.0;
if (random_double() < reflect_prob)
scattered = ray(rec.p, reflected);
else
scattered = ray(rec.p, refracted);
return true;
}
float ref_idx;
};
#endif
main.cc
//==================================================================================================
// Written in 2016 by Peter Shirley <ptrshrl@gmail.com>
//
// To the extent possible under law, the author(s) have dedicated all copyright and related and
// neighboring rights to this software to the public domain worldwide. This software is distributed
// without any warranty.
//
// You should have received a copy (see file COPYING.txt) of the CC0 Public Domain Dedication along
// with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//==================================================================================================
#include <iostream>
#include <limits>
#include <fstream>
#include "sphere.h"
#include "hitable_list.h"
#include "float.h"
#include "camera.h"
#include "material.h"
#include "random.h"
using namespace std;
vec3 color(const ray& r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r, 0.001, (numeric_limits<float>::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.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);
}
}
hitable *random_scene() {
int n = 500;
hitable **list = new hitable*[n+1];
list[0] = new sphere(vec3(0,-1000,0), 1000, new lambertian(vec3(0.5, 0.5, 0.5)));
int i = 1;
for (int a = -11; a < 11; a++) {
for (int b = -11; b < 11; b++) {
float choose_mat = random_double();
vec3 center(a+0.9*random_double(),0.2,b+0.9*random_double());
if ((center-vec3(4,0.2,0)).length() > 0.9) {
if (choose_mat < 0.8) { // diffuse
list[i++] = new sphere(
center, 0.2,
new lambertian(vec3(random_double()*random_double(),
random_double()*random_double(),
random_double()*random_double()))
);
}
else if (choose_mat < 0.95) { // metal
list[i++] = new sphere(
center, 0.2,
new metal(vec3(0.5*(1 + random_double()),
0.5*(1 + random_double()),
0.5*(1 + random_double())),
0.5*random_double())
);
}
else { // glass
list[i++] = new sphere(center, 0.2, new dielectric(1.5));
}
}
}
}
list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));
return new hitable_list(list,i);
}
int main() {
int nx = 1200;
int ny = 800;
int ns = 10;
ofstream outfile(".\\image.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
std::cout << "P3\n" << nx << " " << ny << "\n255\n";
hitable *world = random_scene();
vec3 lookfrom(13,2,3);
vec3 lookat(0,0,0);
float dist_to_focus = 10.0;
float aperture = 0.1;
camera cam(lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny), aperture, dist_to_focus);
for (int j = ny-1; j >= 0; j--) {
for (int i = 0; i < nx; i++) {
vec3 col(0, 0, 0);
for (int s=0; s < ns; s++) {
float u = float(i + random_double()) / float(nx);
float v = float(j + random_double()) / float(ny);
ray r = cam.get_ray(u, v);
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";
}
}
}