前面的文章,介绍了如何产生一个像素。实际上当你拥有了一个像素之后,你便拥有了整个世界,中间不过是差点想象力而已。
现在我们使用光线追踪的渲染技术产生一张图片。
光线追踪的原理如上图所示,首先我们分别产生上述元素。
1 image
为了产生一张图,首先我们得有一张image。一张image有哪些元素?长和宽。
#include
void main()
{
ofstream ppmstream("C:/Users/Desktop/mypic_1.ppm");
ppmstream << "P3" << endl;
ppmstream << img_x << " " << img_y << endl;
ppmstream << 255<= 0; y--)
{
for (int x = 0; x < img_x; x++)
{
ppmstream << r << " " << g << " " << b << endl;
}
}
}
将每个像素的rgb都设为(255,255,255)
现在我们有了一张image,接下来,我们要创造一个camera
2 vec3 自定义类
但是在此之前,先写一个vec3的类型,方便后面的计算。
#ifndef _VEC3_H_
#define _VEC3_H_
#include
//let the code explains itself;
using namespace std;
class Vec3
{
public:
Vec3() {};
Vec3(float x, float y, float z) { e[0] = x; e[1] = y; e[2] = z;};
float operator[](int i) { return e[i]; };
float operator[](int i)const { return e[i]; };
float length() { return sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]); };
Vec3 unit() { return Vec3(e[0] / length(), e[1] / length(), e[2] / length()); };
float e[3];
Vec3 cross(const Vec3 &a) { return Vec3((e[1]*a[2]-e[2]*a[1]),-(e[0]*a[2]-e[2]*a[0]),(e[0]*a[1]-e[1]*a[0])); };
};
inline Vec3 operator+(const Vec3& a, const Vec3& b) { Vec3 c(a[0] + b[0], a[1] + b[1], a[2] + b[2]); return c; };
inline Vec3 operator-(const Vec3& a, const Vec3& b) { Vec3 c(a[0] - b[0], a[1] - b[1], a[2] - b[2]); return c; };
inline Vec3 operator*(float b,const Vec3& a) { return Vec3(b*a[0], b*a[1], b*a[2]); };
inline float operator*(const Vec3& a, const Vec3& b) { return a[0] * b[0]+a[1] *b[1]+a[2] *b[2]; };
ostream& operator<<(ostream &os, const Vec3 a) { os << a[0] << " " << a[1] << " " << a[2]; return os; };
#endif // !VEC3_H
3 ray类
我们的主题是光线追踪,所以我们先要产生一根光线,一根光线实际上是一根射线,先看一下光线传播方程ray function:
p(t)=A+B*t
A是光线的原点,B是光线传播的方向,t是时间
#ifndef _RAY_H_
#define _RAY_H_
#include"Vec3.h"
//let the code explains itself;
class ray
{
public:
ray() {};
ray(const Vec3& v1, const Vec3& v2) { A = v1; B = v2; };
Vec3 A;
Vec3 B;
inline Vec3 origin() { return A; };
inline Vec3 origin() const{ return A; };
inline Vec3 direction() { return B; };
inline Vec3 direction()const { return B; };
inline Vec3 point_at_param(float t) { return A+t*B; };
inline Vec3 point_at_param(float t)const{ return A + t * B; };
};
4 camera
一个相机应该具有以下属性,光心的位置,底片的位置和底片的尺寸,这里,我们假设底片就是之前的image
class camera
{
public:
camera() {};
camera(Vec3 pos_eye, Vec3 pos_img_origin, float w, float h)
{
eye = pos_eye;
img_origin = pos_img_origin;
vertical = h;
horozon = w;
};
Vec3 eye;
Vec3 img_origin;
float vertical;
float horozon;
ray get_a_ray(float u, float v)
{
u *= horozon;
v *= vertical;
Vec3 direction(img_origin[0] + u, img_origin[1] + v, img_origin[2]);
return ray(eye, direction);
};
};
5 世界
我要让创造一个世界让相机来现实,为了描述这个复杂的世界,我们用类与继承来完成。
首先要有一个抽象类
#ifndef _HITABLE_h_
#define _HITABLE_h_
#include "ray.h"
struct record
{
float t;
Vec3 p;
Vec3 normal;
};
class hitable
{
public:
virtual bool hit(const ray& aray, record& hitrecord, float t_min, float t_max)const {
return 0;
};
};
#endif // !_HITABLE_h_
这里需要注意的是,这个抽象类的纯虚函数hit,是用来检测,一个物体线的交点。需要子类来重载。c++多态的美妙就在于此。
接下来,我们要创造一个列表类,用来管理世界上的物体
#ifndef _HITABLE_LIST_H_
#define _HITABLE_LIST_H_
#include"hitable.h"
//let the code explains itself;
class hitable_list:public hitable
{
public:
hitable_list() { };
hitable_list(hitable ** l, int n) {
p = l; size = n;
};
virtual bool hit(const ray& aray, record& hitrecord, float t_min, float t_max)const;
hitable ** p;
int size;
};
bool hitable_list:: hit(const ray& aray, record& hitrecord, float t_min, float t_max)const
{
float closest_so_far_t= t_max;
record record_temp;
bool result = 0;
for (int i = 0; i < size; i++)
{
if (p[i]->hit(aray, record_temp, t_min, closest_so_far_t))//这里有问题
{
result = 1;
hitrecord = record_temp;
closest_so_far_t = record_temp.t;
}
}
return result;
};
#endif // !_HITABLE_LIST_H_
现在我们来创造点实际的东西,比如一个球:
#ifndef _SPHERE_H
#define _SPHERE_H
#include"hitable.h"
//let the code explains itself;
class sphere :public hitable
{
public:
sphere() {};
sphere(float r, Vec3 cen): R(r),Center(cen){ };
virtual bool hit(const ray& aray, record& hitrecord, float t_min, float t_max)const;
Vec3 Center;
float R;
};
bool sphere:: hit(const ray& aray, record& hitrecord, float t_min, float t_max)const
{
float a = aray.B*aray.B;
float b = 2.0 * aray.B*(aray.A - Center);
float c = (aray.A - Center) * (aray.A - Center) - R * R;
float delta = b * b - 4 * a*c;
if (delta >= 0)
{
float t = (-b - sqrt(delta)) / (2 * a);
if (tt_min)
{
hitrecord.t = t;
hitrecord.p = aray.point_at_param(t);
hitrecord.normal = ( hitrecord.p- Center).unit();
return 1;
}
t = (-b + sqrt(delta)) / (2 * a);
if (tt_min)
{
hitrecord.t = t;
hitrecord.p = aray.point_at_param(t);
hitrecord.normal = ( hitrecord.p- Center).unit();
return 1;
}
return 0;
}
else return 0;
}
#endif // !_SPHERE_H
需要注意的是它的重载hit函数,它设计到光线与球的交点求解
光线的方程:
P=A+B*t
球的方程是
(P-C)*(P-C)=R
联立两个方程,求一个一元二次方程即可求得问题的解
最后,我们我们创建一个color函数,对与碰撞到的物体,现白色,否则则现实背景颜色
Vec3 color(const ray &a_ray, hitable* world)
{
record rec;
if (world->hit(a_ray, rec, 0, FLT_MAX))
{
return Vec3(1, 1, 1);
}
else
{
Vec3 unit_direction = a_ray.direction().unit();
float seita = 0.5*(unit_direction[1] + 1);
return (1 - seita)*Vec3(1, 1, 1) + (seita)*Vec3(0.5, 0.7, 1)
}
这里背景色做了一个蓝色与白色的线性插值,然后将每个像素的返回颜色写如文件
main函数如下
void main()
{
camera cam(Vec3(0.3,0.25,0),Vec3(-2,-1,-1),4,2);
float img_x = 200;
float img_y = 100;
int simple_times = 100;
Vec3 img_origin(-2,-1,-1);
ofstream ppmstream("C:/Users/sl136/Desktop/mypic_1.ppm");
ppmstream << "P3" << endl;
ppmstream << img_x << " " << img_y << endl;
ppmstream << 255<= 0; y--)
{
for (int x = 0; x < img_x; x++)
{
Vec3 col(0, 0, 0);
float ui = rand() / (double)RAND_MAX;
float vi = rand() / (double)RAND_MAX;
float u = ((float)x+ui) / img_x;
float v = ((float)y+vi) / img_y;
ray a_ray = cam.get_a_ray(u, v);
col = col+ color(a_ray, Myworld);
int r = (int)255 * col[0];
int g = (int)255 * col[1];
int b = (int)255 * col[2];
ppmstream << r << " " << g << " " << b << endl;
}
}
}
我们得到了:
这样还有不够cool,我们加上法线渲染,以点的单位法线映射为颜色值,我们重写color函数
Vec3 color(const ray &a_ray, hitable* world)
{
record rec;
if (world->hit(a_ray, rec, 0, FLT_MAX))
{
return 0.5*(rec.normal+vec3(1,1,1))
}
else
{
Vec3 unit_direction = a_ray.direction().unit();
float seita = 0.5*(unit_direction[1] + 1);
return (1 - seita)*Vec3(1, 1, 1) + (seita)*Vec3(0.5, 0.7, 1);
}
}
现在的效果是这样的
这样一来就有点意思了,但还不够
现在我们加点阴影,在多加一个物体进来
Vec3 rand_point_in_unit_sphere()
{
float result;
Vec3 point(0,0,0);
do {
float x = rand() / (float)RAND_MAX;
float y = rand() / (float)RAND_MAX;
float z = rand() / (float)RAND_MAX;
point = 2 * Vec3(x, y, z) - Vec3(1, 1, 1);
result = point * point - 1;
} while (result >= 0);
return point;
}
Vec3 color(const ray &a_ray, hitable* world)
{
record rec;
if (world->hit(a_ray, rec, 0, FLT_MAX))
{
Vec3 randpoint = rand_point_in_unit_sphere();
Vec3 target = rec.p + rec.normal + randpoint;
ray new_ray(rec.p, rec.normal + randpoint);
return 0.5*color(new_ray,world);
}
else
{
Vec3 unit_direction = a_ray.direction().unit();
float seita = 0.5*(unit_direction[1] + 1);
return (1 - seita)*Vec3(1, 1, 1) + (seita)*Vec3(0.5, 0.7, 1);
}
}
现在我们的效果是这样的:
进行gama校正,让图像亮一点,
终于,我们踏出了光线追踪的第一步。