简介
这是一篇零基础的图形爱好者实现一个简易光线追踪DEMO的记录,最终设想是
1.可以实现读入OBJ等模型数据并渲染。
2.实现简单的反射(reflection),折射(refraction),效果。
3.简易光照计算(phong)-复杂着色(brdf)
4.待扩充
PS:该文章处于更新阶段,代码只贴出了比较重要的部分
光线追踪
光线追踪是图形学的另一种常用渲染方式,区别于光栅化。
光栅化现在已经广泛应用于各种渲染情景。简单而言,光栅化在渲染一个像素时,他在考虑所有投影在这一像素的三角面片的颜色,并根据深度,选取最靠前的那一部分。
而光线追踪则是考虑从视角出发透过一个像素发出射线,去寻找这个射线与场景的交,并由此确定这一像素的颜色。
光栅化在很早以前其部分工作已经被固化在硬件中,实现了很高的可并行性,以及极快的渲染速度,同时也开放了很多可编程接口,也是目前游戏实时渲染的基础,但目前的光栅化有一个极大的缺点。
在渲染某一frame时,光栅化整个渲染过程会不断地抛弃部分全局信息,如果用过OPENGL的朋友应该了解,在确定一个三角面片最终位置时(位于vertex shader),我们需要对它坐标进行model,camera_view,projection,3个矩阵连乘,最终取坐标(x,y)落在(-1.0,1.0)区间内的点,之外的点均被抛弃,然后进入geometry shader,fragment shader,最终和深度buffer内的数据作比较,保留最前方(z值最小)的点。在这个过程中,抛弃了大量的信息。这就导致如果想要实现镜面反射等特效需要从不同角度多次渲染整个场景,以及被抛弃部分与存在的部分进行互动很困难。而光线追踪在实现这种效果时却十分轻松,只需生成一条新的光线并返回其颜色结果即可,因为其在计算时总是保留全局信息的,光线追踪的特点有:
- 高并行性 :单个像素的颜色计算与其余像素无关;
- 符合物理规律:其计算过程更加符合现实生活中人们观察物体时发生的物理现象;
- 保留了全局信息:在计算时无需丢弃部分信息;
- 计算速度较慢 :由于每一像素的颜色计算都需要用到全局信息,这一方面需要占用大量的资源,也需要很长的时间进行计算;
- 待扩充
参考书目
具体原理性的知识大多来源于百度,代码部分参考了《一周学会光线追踪》的实现内容。
实验环境
WIN10 VS2017
语言:C++
依赖库:glm(基本数据结构以及矩阵变换) stb_image_write(保存图片) stb_image(图片读入)
工具人:本人
目前进度
CODE
Ray:类
class Ray {
public:
Ray(vec3 o,vec3 d,float p) {
this->o = o;
this->d = d;
this->power = p;
}
vec3 getDir()
{
return this->d;
}
vec3 getOrg()
{
return this->o;
}
float getPower()
{
return this->power;
}
private:
vec3 o;
vec3 d;
float power;
};
Hit_Able:基类,所有可与ray相交的类均需要继承该类,并重写getIntersection以及getColor函数,其中getIntersection返回交点据ray出发点的距离t(hit_position = ray.o+tray.d),getColor()获取颜色*
class Hit_Able{
public:
virtual float getIntersection(Ray &ray)
{
cout << "base" << endl;
return 0.0;
}
virtual vec3 getColor(Ray &ray, float &t, vector<Hit_Able*> &world, Scene_Inf &scene_inf)
{
return vec3(0.0);
}
};
Floor:类,花纹地板
class Floor : public Hit_Able {
public:
Floor(vec3 top_l, vec3 top_r, vec3 buttom_l, vec3 buttom_r, Material material)
{
this->top_l = top_l;
this->top_r = top_r;
this->buttom_l = buttom_l;
this->buttom_r = buttom_r;
this->material;
}
Material getMaterial()
{
return this->material;
}
vec3 getTL()
{
return this->top_l;
}
vec3 getTR()
{
return this->top_r;
}
vec3 getBL()
{
return this->buttom_l;
}
vec3 getBR()
{
return this->buttom_r;
}
float getIntersection(Ray &ray)
{
vec3 normal = glm::cross(this->buttom_r - this->buttom_l, this->top_l - this->buttom_l);
vec3 d_vector = normal * buttom_l;
float d = -(getSum(d_vector));
vec3 n_o = normal * ray.getOrg();
vec3 n_d = normal * ray.getDir();
float t = -(getSum(n_o) + d) /getSum(n_d);
if (t < 0.0)
{
t = 0.0;
}
return t;
}
vec3 getColor(Ray &ray, float &t, vector<Hit_Able*> &world, Scene_Inf &scene_inf)
{
vec3 res = vec3(0.0);
vec3 hit_position = ray.getOrg() + ray.getDir()*t;
float width = (this->buttom_r - this->buttom_l)[0];
float height = ((this->top_r - this->buttom_r)[2]);
float x = (hit_position-buttom_l)[0];
float y = (hit_position-buttom_l)[2];
float r_x = x / width;
float r_y = y / (height);
r_x = r_x * 10.0;
r_y = r_y * 10.0;
int i_x = (int)r_x;
int i_y = (int)r_y;
if (i_x % 2 == 0)
{
if (i_y % 2 == 0)
{
res = vec3(0.0, 0.0, 0.0);
}
else
{
res = vec3(1.0, 1.0, 1.0);
}
}
else
{
if (i_y % 2 == 0)
{
res = vec3(1.0, 1.0, 1.0);
}
else
{
res = vec3(0.0, 0.0, 0.0);
}
}
return res;
}
private:
vec3 top_l;
vec3 top_r;
vec3 buttom_l;
vec3 buttom_r;
Material material;
};
Sphere:类,是个球
class Sphere :public Hit_Able {
public:
Sphere(vec3 position, float radius, Material material)
{
this->position = position;
this->radius = radius;
this->material = material;
}
vec3 getPosition()
{
return this->position;
}
float getRadius()
{
return this->radius;
}
Material getMaterial()
{
return this->material;
}
float getIntersection(Ray &ray)
{
float t = 0.0;
vec3 o = ray.getOrg();
vec3 d = ray.getDir();
vec3 center = this->getPosition();
float r = this->getRadius();
float a = glm::length(d);
a = a * a;
float two = 2.0;
vec3 b_v = (o*d - d * center);
float b = 2.0*(b_v[0] + b_v[1] + b_v[2]);
vec3 o_c = (o*center);
float c = glm::length(o)*glm::length(o) + glm::length(center)*glm::length(center) - 2.0*(o_c[0] + o_c[1] + o_c[2]) - r * r;
float det = (b*b - 4.0*a*c);
if (det <= 0)
{
t = 0.0;
}
else
{
float x1 = (-b + sqrt(det)) / (2.0*a);
float x2 = (-b - sqrt(det)) / (2.0*a);
t = x1 > x2 ? x2 : x1;
}
return t;
}
vec3 getColor(Ray &ray, float &t, vector<Hit_Able*> &world, Scene_Inf &scene_inf)
{
vec3 hit_position = ray.getOrg() + t * ray.getDir();
vec3 normal = normalize(hit_position - this->getPosition());
return normal;
}
private:
vec3 position;
float radius;
Material material;
};
Scene_Render:类,负责进行一帧的渲染,包含相机参数,负责生成Ray,并交由rayTrace类进行颜色计算
//渲染类 负责管理相机参数 实现所有RAY的初始化并调用TRACE类进行光线投射
//在此相机总是位于垂直屏幕中心后一个单位(0.0,0.0,1.0),朝向为(0.0,0.0,0.0),垂直向量为(0.0,1.0,0.0)
//左上角(0,0)
class Scene_Render {
public:
Scene_Render()
{
cout << "init scene render" << endl;
}
unsigned int screen_width = SCREEN_WIDTH;
unsigned int screen_height = SCREEN_HEIGHT;
float wh = ((float)(SCREEN_HEIGHT))/ (float(SCREEN_WIDTH));
unsigned char* image = new unsigned char[screen_height*screen_width*3];
float delta = 1.0 / SCREEN_WIDTH;
vec3 view_position = vec3(0.0, 0.0, 1.0);
mat4 tran_mat = mat4(1.0);//用于对相机进行位移旋转等,需要作用于ray的o与d
vec3 color_top_left = vec3(255.99, 255.99, 255.99);
vec3 color_buttom_left = vec3(255.99, 255.99, 0.0);
vec3 color_top_right = vec3(0.0, 255.99, 255.99);
vec3 color_buttom_right = vec3(255.99, 0.0, 255.99);
int ray_num = RAYNUM;
void setTranMat(mat4 mat)
{
this->tran_mat = mat;
}
void Render(vector<Hit_Able*> &world,Scene_Inf &scene_inf,string save_path = "..\\image\\sceen.jpg")
{
for (unsigned int i = 0; i < screen_height; i++)
{
for (unsigned int j = 0; j < screen_width; j++)
{
float x = (((float)(j)) / ((float)(screen_width)) - 0.5)*2.0;
float y = ((1.0-((float)(i)) / ((float)(screen_height))) - 0.5)*2.0*wh;
float z = 0.0;//ray 初始位置,并由此的到发射方向
vec3 pixel_color = vec3(0.0, 0.0, 0.0);
for (int k = 0; k < ray_num; k++)
{
vec3 o = vec3(x+delta*random(), y+delta*random(), 0.0);
vec3 d = normalize(o - view_position);
Ray ray = Ray(o, d, 1.0);
pixel_color = pixel_color + rayTrace(ray, world, scene_inf);
}
pixel_color = pixel_color/((float)(ray_num));
for (int k = 0; k < 3; k++)
{
if (pixel_color[k] > 1.0)
pixel_color[k] = 1.0;
if (pixel_color[k] < 0.0)
pixel_color[k] = 0.0;
image[i*screen_width * 3 + j * 3 + k] = (unsigned char)(pixel_color[k]*M);
}
}
}
int image_write = stbi_write_jpg(save_path.c_str(), screen_width, screen_height, 3, (void*)image, 100);
}
};
vec3 rayTrace():函数,接受ray,world,scene_inf,并返回该ray所交场景颜色值,其中world为vector<Hit_Abel*>类型,(由基类函数指针调用子类的同名函数)为所有可与光线求交的物体,scene_inf为包含场景信息的类,暂时为空
vec3 rayTrace(Ray &ray, vector<Hit_Able*> &world, Scene_Inf &scene_inf)
{
vec3 last_color = vec3(0.0);
float last_t = 0.0;
bool is_intersection = false;
for (int i = 0; i < world.size(); i++)
{
Hit_Able* object = world[i];
float t = object->getIntersection(ray);
if (t != 0.0)
{
if (is_intersection == false)
{
last_t = t;
last_color = object->getColor(ray, t, world, scene_inf);
is_intersection = true;
}
else
{
if (t < last_t)
{
last_t = t;
last_color = object->getColor(ray, t, world, scene_inf);
}
}
}
}
if (is_intersection == false)
{
return vec3(0.0);
}
return last_color;
}
历史记录:
1.0.每一像素点射出一条射线,存在边缘不平滑的问题:
2.0 每一像素随机方向(在一定范围内),射出十条射线,取均值,改善边缘问题: