光线追踪基础

前面的文章,介绍了如何产生一个像素。实际上当你拥有了一个像素之后,你便拥有了整个世界,中间不过是差点想象力而已。

现在我们使用光线追踪的渲染技术产生一张图片。

光线追踪的原理如上图所示,首先我们分别产生上述元素。

1 image

为了产生一张图,首先我们得有一张image。一张image有哪些元素?长和宽。

#include<fstream>
void main()
{

	ofstream ppmstream("C:/Users/Desktop/mypic_1.ppm");
	ppmstream << "P3" << endl;
	ppmstream << img_x << " " << img_y << endl;
	ppmstream << 255<<endl;

float img_x = 200;
float img_y = 100;
int r=255;
int g=r;
int b=r;


for (int y = img_y - 1; y >= 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<iostream>
//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 (t<t_max&&t>t_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 (t<t_max&&t>t_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<<endl;
	hitable* hitable_array[2]; 
	hitable_array[0] = & sphere(0.5, Vec3(0, 0, -1));
	hitable_array[1] = & sphere(100, Vec3(0, -100.5, -1));
	hitable_list* Myworld = & hitable_list(hitable_array,0);
	for (int y = img_y - 1; y >= 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校正,让图像亮一点,

终于,我们踏出了光线追踪的第一步。

 

 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值