描述
渲染的大致过程
实现渲染的过程是从main开始,先导入物体和光源到scene里面,建立好scene的bvh树,这个树全部是对于兔子的三角形的树,然后调用r.Render(scene)开始渲染,渲染时,是对于每一个像素建立光线使用 scene.castRay( r, 0 );来得到这个光线打到的颜色,其中在castRay里面,我们为了得到交点,使用了 Scene::intersect(ray);来得到交点,而intersect函数就是调用当前场景的BVH的成员函数getIntersection来递归找到交点,最后利用交点的信息着色,绘制图片
从0开始的框架理解
想看作业的按照目录跳转!!我们先看看有什么东西,渲染逻辑在任务部分
global.hpp
辅助函数,有解方程的函数,实现进度条的函数,这些只要知道就行了
light.hpp
光线类,起点和强度,其实是点光源
AreaLight类
这个是继承自点光源的,是实现了面光源:下面详细介绍这个类
-
继承关系:
AreaLight
继承自Light
类,这意味着它继承了Light
类的属性方法,比如,点光的位置,强度
-
构造函数:
- 构造函数接受两个
Vector3f
参数:p
代表光源的位置,i
代表光源的强度。 - 构造函数中初始化了光源的一些默认属性:
normal
: 光源表面的法线向量,默认为(0, -1, 0)
,指向负 y 轴方向。u
和v
: 光源表面的两个正交向量,默认为(1, 0, 0)
和(0, 0, 1)
,分别指向 x 轴和 z 轴方向。length
: 光源的长度,默认为 100。
- 构造函数接受两个
-
成员变量:
normal
: 光源表面的法线向量。u
和v
: 光源表面的两个正交向量。length
: 光源的长度。
-
成员函数:
SamplePoint()
: 返回光源表面上随机选择的一个点。这个点是通过在光源的平面上随机采样得到的,具体来说,是在u
和v
向量方向上随机移动得到的
Ray.hpp类
光线类,就是光线的起点,方向和传播时间 t ,以及 t 时间对应的光的位置等等
Vector.hpp
手搓出向量,包括一些向量运算,还有对于两个向量线性插值函数lerp,单位化向量,两个向量最大化等等
Intersection.hpp
一个结构体,储存交点的信息,包括是否相交,交点坐标,交点的法向量,距离,相交的物体是什么,交点的材质是哪一个
struct Intersection
{
Intersection(){
happened=false;
coords=Vector3f();
normal=Vector3f();
distance= std::numeric_limits<double>::max();
obj =nullptr;
m=nullptr;
}
bool happened;
Vector3f coords;
Vector3f normal;
double distance;
Object* obj;
Material* m;
};
object.hpp
物体的父类,有很多虚函数,等着被子类实现的,包括两种判断是否有交点的函数,交点信息的成员变量,得到表面属性的函数,得到漫反射颜色的函数,得到这个物体的包围盒的函数
class Object
{
public:
Object() {}
virtual ~Object() {}
virtual bool intersect(const Ray& ray) = 0;
virtual bool intersect(const Ray& ray, float &, uint32_t &) const = 0;
virtual Intersection getIntersection(Ray _ray) = 0;
virtual void getSurfaceProperties(const Vector3f &, const Vector3f &, const uint32_t &, const Vector2f &, Vector3f &, Vector2f &) const = 0;
virtual Vector3f evalDiffuseColor(const Vector2f &) const =0;
virtual Bounds3 getBounds()=0;
};
Material.hpp
这是材质类,里面有材质的一些必要参数和方法函数,比如返回对应位置的颜色是什么,如果是纯色的材质还可以返回材质颜色,材质种类什么的
OBJ_Loader类
这个类很大,主要包括:
- 向量定义和运算
- 一个
Vertex
结构体,包含顶点的位置,法向量和材质uv坐标。表示网格或三角形的每一个顶点 - 材质结构体,有材质名字,反射指数Ns ,折射率Ni , 溶解率d , 光照模型illum以及后面的map什么什么的,都是文件地址,可以使用这些文件初始我们的材质信息
- 网格结构体Mesh , 包括网格的名字,顶点数组,顶点索引, 材质结构体定义的网格的材质
- 数学运算辅助函数math,比如求点乘,求模长 , 叉乘 , 求两个向量的角度,求投影长度
- 算法辅助函数algorithm,比如有判断是否在三角形里面,是否在一条直线一侧,计算一个三角形的法线
- 读取obj文件的类,不需要掌握
接下来看稍微变化一点的,也是核心部分,从主函数开始,我们遇到新的就介绍新的
Main函数
主函数,首先定义了一个场景,叫做scene,那这个场景里面有什么函数和属性或者变量呢?
Scene.hpp介绍(重要)
场景类是渲染器的核心组件之一,必须有这个场景才会在场景上进行渲染,主要作用负责管理场景中的所有对象、光源以及相关配置选项
成员变量:
- 渲染宽高,相机张角fov
- 背景色
- 渲染深度
- 所有物体的指针
- 所有光源
- 一整个加速结构BVH,是一个指针(毕竟是树结构嘛)
成员函数:
- 添加物体和光源到物体和光源列表
- 获取物体和光源的函数
- intersect(const Ray& ray) const 作用是求指定光线和场景类物体表面的交点
- buildBVH() 作用是构建BVH
- castRay(const Ray &ray, int depth) const,实现递归计算渲染,最后返回着色渲染的颜色
- trace()实现光线的传播,判断是否与物体相交,而且还告诉你相交的物体是什么
- HandleAreaLight()处理面光的光照过程
- 计算反射和折射和fresnel效应的函数,其中fresnel函数计算出来的kr是决定反射颜色和折射颜色最后混合时格各自的占比的
现在我们到cpp看看这些函数怎么实现的
Scene.cpp介绍
1.建立BVH
使用new BVHAccel()建立了BVH结构,BVH实现逻辑后面再看,其实要构建两个 BVH,一个是针对空间中物体的 BVH,另一个是针对单一物体的组成网格的 BVH,由于这次作业只有一个兔子,所以我们建立的其实是对于组成兔子的所有三角形的BVH
void Scene::buildBVH() {
printf(" - Generating BVH...\n\n");
this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}
2.相交函数
直接返回指定光线在bvh加速下的交点,这个交点是Intersection类型的,这个类型里面有交点的所有信息,包括是否相交,交点坐标,交点的法向量,距离,相交的物体是什么,交点的材质是哪一个
Intersection Scene::intersect(const Ray &ray) const
{
return this->bvh->Intersect(ray);
}
3.trace函数
这个bool函数的参数有:光线ray , 所有物体,当前最近的交点的t,以及交点所在的物体指针或者交点所在的三角形的索引(主要是区分光线和隐式表面的求交和三角形组成的物体的求交),这两个都是双向的,是会根据函数结果变化的
函数内容:一个一个物体看,如果和指定光线有交点同时交点还更近,就更新相交物体和t和物体的索引
最后这个函数:返回的是是否相交bool类型,同时得到的有最近相交的点对应的光传播的时间tNear和物体以及索引,有了tNear代入光线传播方程就可以计算出交点了
bool Scene::trace(
const Ray &ray,
const std::vector<Object*> &objects,
float &tNear, uint32_t &index, Object **hitObject)
{
*hitObject = nullptr;
for (uint32_t k = 0; k < objects.size(); ++k) {
float tNearK = kInfinity;
uint32_t indexK;
Vector2f uvK;
if (objects[k]->intersect(ray, tNearK, indexK) && tNearK < tNear) {
*hitObject = objects[k];
tNear = tNearK;
index = indexK;
}
}
return (*hitObject != nullptr);
}
4.castRay()
大致思路:这个函数是递归渲染函数,对于传进来光线ray,直接放入场景看看有无交点,这个求交过程使用了bvh加速的 ,最后直接得到物体交点,根据平面类型和交点携带的信息渲染着色,其中有反射折射就利用计算出来的反射折射方向重新建立一个新的ray,递归调用castRay(),最后hitColor要根据kr分配各自颜色占比。
来看注释:
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
if (depth > this->maxDepth) { //大于递归就结束
return Vector3f(0.0,0.0,0.0);
}
//求这根光线与场景的交点,这个求交过程使用了bvh加速的
Intersection intersection = Scene::intersect(ray);
Material *m = intersection.m;
Object *hitObject = intersection.obj;
Vector3f hitColor = this->backgroundColor;
// float tnear = kInfinity;
Vector2f uv;
uint32_t index = 0;
if(intersection.happened) {//有交点
Vector3f hitPoint = intersection.coords;//取出交点
Vector3f N = intersection.normal; // 取出法向量
Vector2f st; // st coordinates
hitObject->getSurfaceProperties(hitPoint, ray.direction, index, uv, N, st);
// Vector3f tmp = hitPoint;
switch (m->getType()) {
//根据表面类型区别着色
case REFLECTION_AND_REFRACTION:
{
Vector3f reflectionDirection = normalize(reflect(ray.direction, N));
Vector3f refractionDirection = normalize(refract(ray.direction, N, m->ior));
Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
hitPoint - N * EPSILON :
hitPoint + N * EPSILON;
Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ?
hitPoint - N * EPSILON :
hitPoint + N * EPSILON;
Vector3f reflectionColor = castRay(Ray(reflectionRayOrig, reflectionDirection), depth + 1);
Vector3f refractionColor = castRay(Ray(refractionRayOrig, refractionDirection), depth + 1);
float kr;
fresnel(ray.direction, N, m->ior, kr);//分配两种颜色
hitColor = reflectionColor * kr + refractionColor * (1 - kr);
break;
}
case REFLECTION://只有反射
{
float kr;
fresnel(ray.direction, N, m->ior, kr);
Vector3f reflectionDirection = reflect(ray.direction, N);
Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
hitPoint + N * EPSILON :
hitPoint - N * EPSILON;
//就全是反射光
hitColor = castRay(Ray(reflectionRayOrig, reflectionDirection),depth + 1) * kr;
break;
}
default://不然就是phong着色
{
Vector3f lightAmt = 0, specularColor = 0;
Vector3f shadowPointOrig = (dotProduct(ray.direction, N) < 0) ?
hitPoint + N * EPSILON :
hitPoint - N * EPSILON;
//phong着色,一个一个光线着色
for (uint32_t i = 0; i < get_lights().size(); ++i)
{
auto area_ptr = dynamic_cast<AreaLight*>(this->get_lights()[i].get());
if (area_ptr)
{
// Do nothing for this assignment
}
else
{
Vector3f lightDir = get_lights()[i]->position - hitPoint;
// square of the distance between hitPoint and the light
float lightDistance2 = dotProduct(lightDir, lightDir);
lightDir = normalize(lightDir);
float LdotN = std::max(0.f, dotProduct(lightDir, N));
Object *shadowHitObject = nullptr;
float tNearShadow = kInfinity;
// is the point in shadow, and is the nearest occluding object closer to the object than the light itself?
bool inShadow = bvh->Intersect(Ray(shadowPointOrig, lightDir)).happened;
lightAmt += (1 - inShadow) * get_lights()[i]->intensity * LdotN;
Vector3f reflectionDirection = reflect(-lightDir, N);
specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, ray.direction)),
m->specularExponent) * get_lights()[i]->intensity;
}
}
hitColor = lightAmt * (hitObject->evalDiffuseColor(st) * m->Kd + specularColor * m->Ks);
break;
}
}
}
return hitColor;
}
现在场景看完了,我们继续在main里面往下看
接下来使用了 MeshTriangle 类型定义了一个兔子网格,这个网格来自场外的兔子样子的文件,然后在场景加了光和建立了BVH结构
MeshTriangle bunny("../models/bunny/bunny.obj");
scene.Add(&bunny);
scene.Add(std::make_unique<Light>(Vector3f(-20, 70, 20), 1));
scene.Add(std::make_unique<Light>(Vector3f(20, 70, 20), 1));
scene.buildBVH();
我们一个一个看:
这个MeshTriangle类型是哪来的?是在三角形类.hpp里面
Triangle.hpp介绍(重要)
这是三角形大类,里面定义了两个类 Triangle
和 MeshTriangle
, 这两个类都是从 Object
类派生的,用于表示渲染场景中的三角形对象
最开始有一个函数rayTriangleIntersect
函数 , 此函数计算一条光线是否与给定的由三个顶点 (v0
, v1
, v2
) 定义的三角形相交。如果发生相交,则返回 true
并更新参数 tnear
、u
和 v
以表示相交距离和三角形上交点的双线性坐标
Triangle
此类表示三维空间中的单个三角形。它存储三角形的顶点、边、法向量以及关联的材质,其中材质包括了材质的各种属性。
- 构造函数只需要穿进去三个顶点和材质,就可以初始化这个三角形。
- 后面是判断是否与三角形有交点的虚函数和返回交点信息的虚函数,都只是声明没有实现
getSurfaceProperties
获得表面属性函数,这里只是单纯的将法向量返回。- evalDiffuseColor函数是返回对应点的漫反射颜色,但是没有实现
- getBounds获得这个三角形范围的函数,没有实现
MeshTriangle
这是网格三角形类,继承自 Object
类,并且用于表示由多个三角形构成的网格,这个类主要用于从 OBJ 文件加载模型,并且构建一个 BVH来加速光线追踪过程中的碰撞检测,(注意着是网格物体的BVH,就是物体是网格三角形组成的话,也要将这些网格建一个BVH)
成员变量:
Bounds3 bounding_box
:整个网格的边界框。std::unique_ptr<Vector3f[]> vertices
:网格的所有顶点。uint32_t numTriangles
:三角形的数量。std::unique_ptr<uint32_t[]> vertexIndex
:顶点索引数组,用于构建三角形。std::unique_ptr<Vector2f[]> stCoordinates
:纹理坐标数组。std::vector<Triangle> triangles
:网格中的所有三角形。BVHAccel* bvh
:指向 BVH 加速器的指针。
一句话概括这个类作用,根据你的obj文件里面的顶点和材质创建由三角形构成的网格,每一个三角形有材质什么的,还有整个网格的包围盒,并且为这个网格里的三角形建立了BVH,同时提供了利用BVH进行光线求交点 , 计算指定位置的颜色或者法向量,返回包围盒的一些成员函数
任务之一
计算得到交点的函数 getIntersection(传入的是光线)
inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;if (dotProduct(ray.direction, normal) > 0) //说明光从地下来,直接返回
return inter;
double u, v, t_tmp = 0;
Vector3f pvec = crossProduct(ray.direction, e2);
double det = dotProduct(e1, pvec);
if (fabs(det) < EPSILON) //det决定光是否与三角形平行
return inter; //几乎平行就返回double det_inv = 1. / det;
Vector3f tvec = ray.origin - v0;
u = dotProduct(tvec, pvec) * det_inv;
if (u < 0 || u > 1) /
return inter;
Vector3f qvec = crossProduct(tvec, e1);
v = dotProduct(ray.direction, qvec) * det_inv;
if (v < 0 || u + v > 1)
return inter;
t_tmp = dotProduct(e2, qvec) * det_inv;
if (t_tmp < 0)return inter; //时间小于0返回
inter.happened = true; //当所有都满足时,填充交点信息
inter.coords = ray(t_tmp);
inter.normal = normal;
inter.distance = t_tmp;
inter.obj = this; //物体就是调用这个函数的那一个对象
inter.m = m;return inter;
}
下面我们回到main函数,在给场景加完灯和建立了场景的BVH后, 定义了一个渲染器r,然后就渲染出图了!
作业任务(按逻辑顺序讲解)
Bounds3.hpp
大致介绍一下包围盒这个类,完成这个类里面的一个任务
包围盒是由两个对角的顶点围起来组成的
Vector3f pMin, pMax;
默认的构造函数是初始包围盒的,这个包围盒前提是不知道范围的
Bounds3()
{
double minNum = std::numeric_limits<double>::lowest();
double maxNum = std::numeric_limits<double>::max();
pMax = Vector3f(minNum, minNum, minNum);
pMin = Vector3f(maxNum, maxNum, maxNum);
}
还有一种初始包围盒的方法,一个点作为aabb
Bounds3(const Vector3f p) : pMin(p), pMax(p) {}
还有一种,使用两个点初始aabb,
Bounds3(const Vector3f p1, const Vector3f p2)
{
pMin = Vector3f(fmin(p1.x, p2.x), fmin(p1.y, p2.y), fmin(p1.z, p2.z));
pMax = Vector3f(fmax(p1.x, p2.x), fmax(p1.y, p2.y), fmax(p1.z, p2.z));
}
成员函数Diagonal()返回对角线向量 , SurfaceArea计算表面积,Centroid计算中心,Intersect计算两个aabb交集等等一堆函数
接下来就是实现函数inline bool Bounds3::IntersectP了,利用这个aabb类的各种成员变量和属性,判断光是否和aabb相交,做法就是计算时间,其中需要根据光线方向正负调整时间最大最小值
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& dirIsNeg) const
{float t_Min_x = (pMin.x - ray.origin.x) * invDir[0];
float t_Min_y = (pMin.y - ray.origin.y) * invDir[1];
float t_Min_z = (pMin.z - ray.origin.z) * invDir[2];
float t_Max_x = (pMax.x - ray.origin.x) * invDir[0];
float t_Max_y = (pMax.y - ray.origin.y) * invDir[1];
float t_Max_y = (pMax.y - ray.origin.y) * invDir[1];
float t_Max_z = (pMax.z - ray.origin.z) * invDir[2];if (dirIsNeg[0] == 0)
{
float t = t_Min_x;
t_Min_x = t_Max_x;
t_Max_x = t;
}
if (dirIsNeg[1] == 0)
{
float t = t_Min_y;
t_Min_y = t_Max_y;
t_Max_y = t;
}
if (dirIsNeg[2] == 0)
{
float t = t_Min_z;
t_Min_z = t_Max_z;
t_Max_z = t;
}float t_in = std::max(t_Min_x, std::max(t_Min_y, t_Min_z));
float t_out = std::min(t_Max_x, std::min(t_Max_y, t_Max_z));
if (t_in < t_out && t_out >= 0)
return true;
else
return false;
}
渲染过程是什么样的呢?我们来看渲染类
Renderer.hpp
渲染类,有一个装交点信息的包裹hit_payload,和构造函数
Renderer.cpp
开始定义了一个角度转化器和微小的值,然后就来到Render函数,接受一个场景,开始渲染:
定义framebuffer存颜色用于最后绘图,然后一个一个像素看,按照上一次作业的方法计算出x,y,计算出这个像素发出的光线的方向向量,建立好光线Ray , 使用Scene::castRay开始着色
void Renderer::Render(const Scene& scene)
{
std::vector<Vector3f> framebuffer(scene.width * scene.height);
float scale = tan(deg2rad(scene.fov * 0.5));
float imageAspectRatio = scene.width / (float)scene.height;
Vector3f eye_pos(-1, 5, 10);
int m = 0;
for (uint32_t j = 0; j < scene.height; ++j) {
for (uint32_t i = 0; i < scene.width; ++i) {
// generate primary ray direction
float x = (2 * (i + 0.5) / (float)scene.width - 1) *
imageAspectRatio * scale;
float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
Vector3f dir = Vector3f(x, y, -1);
dir = normalize(dir);
Ray r( eye_pos, dir, 0);
framebuffer[m++] = scene.castRay( r, 0 );
}
UpdateProgress(j / (float)scene.height);
}
UpdateProgress(1.f);
}
来到scene类里面的castRay,这是递归渲染函数,对于传进来光线ray,直接放入场景求交点,这个求交过程使用了bvh加速的 ,最后直接得到物体交点,根据平面类型和交点携带的信息渲染着色,最后返回这个着色的值,存进framebuffer
那在castRay里面是怎么利用BVH求交点的呢?这里使用了获取交点
Intersection intersection = Scene::intersect(ray);
来看Scene::intersect(ray)函数,直接利用这个场景的bvh里面的成员函数求交,最后返回交点
Intersection Scene::intersect(const Ray &ray) const
{
return this->bvh->Intersect(ray);
}
那bvh是怎么建立的?
在Scene.cpp里面实现了建立BVH的函数,具体实现过程在bvh类里
void Scene::buildBVH() {
printf(" - Generating BVH...\n\n");
this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}
接下来来看BVH.hpp 和 .cpp
BVH.hpp(重要)
是用于构建和查询 BVH的数据结构
bvh节点定义
struct BVHBuildNode {
Bounds3 bounds; //这个bvh的包围盒,就是范围
BVHBuildNode *left; //左bvh
BVHBuildNode *right;//右bvh
Object* object; //当是叶子节点时,才有这个bvh里面物体public:
int splitAxis=0, firstPrimOffset=0, nPrimitives=0;//分割轴,对象偏移,当前节点树包含的对象数量
BVHBuildNode(){ //初始一下这些量
bounds = Bounds3();
left = nullptr;right = nullptr;
object = nullptr;
}
};
BVHAccel类:
开始定义了两种 BVH 构建策略:NAIVE
和 SAH
。NAIVE
表示简单的均匀分割,而 SAH
表示基于表面积的分割方法,这是一种更高效的构建策略
enum class SplitMethod { NAIVE, SAH };
BVHAccel类的成员变量:
- 树根
- 最多的对象数
- 分割法
- 物体
BVHAccel类的成员函数:
- Intersect和getIntersection就是利用BVH结构查找光线ray在这个bvh结构里面的最近交点,其中getIntersection需要我们实现,该过程递归进行,你将在其中调
用你实现的 Bounds3::IntersectP .
实现过程逻辑伪代码:
Node BVH_Intersect(Type_of_light light, BVH* tree)
{
//先看看光线是否和盒子有相交
if (Is_Intersect(light, tree->aabb));
{
//有交点先判断是不是叶子
if (tree->lchild == nullptr && tree->rchild == nullptr)
//是叶子直接计算这个叶子的aabb里面所有物体的最近交点并返回
return NObjIntsection(light, tree->aabb);
else
{
//递归看左右bvh树,收集最近节点
Node l = NIntsection(light, tree->lchild);
Node r = NIntsection(light, tree->rchild);
return Min(l, r);//返回左右中近的那一个
}
}
}
根据这个很快可以写出代码:
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
// TODO Traverse the BVH to find intersection
std::array<int, 3> dirIsNeg;
if (ray.direction[0] > 0)dirIsNeg[0] = 1; else dirIsNeg[0] = 0;
if (ray.direction[1] > 0)dirIsNeg[1] = 1; else dirIsNeg[1] = 0;
if (ray.direction[2] > 0)dirIsNeg[2] = 1; else dirIsNeg[2] = 0;
Intersection inter;
if (node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg))
{
if (node->left == nullptr && node->right == nullptr)
return node->object->getIntersection(ray);
Intersection l = getIntersection(node->left, ray);
Intersection r = getIntersection(node->right, ray);
if (l.distance < r.distance)return l;
else return r;
}
else return inter;
}
BVHAccel类的构造函数:
构造函数实现过程,在BVH.cpp里,这里只留下代码核心的部分
参数:p表示物体后面变成了primitives传入了递归函数,maxPrimsInNode表示停止建立bvh树的递归终止条件,就是bvh里面只有一个物体时,splitMethod表示全部是NAIVE分割方法
BVHAccel::BVHAccel(vector<Object*> p, int maxPrimsInNode, SplitMethod splitMethod)
{
if (primitives.empty())
return;root = recursiveBuild(primitives); //开始递归建树
}
递归函数recursiveBuild的原理:
- 如果
objects
向量中只有一个对象,则创建一个叶节点,该节点包含该对象的边界盒和指向该对象的指针。 - 如果
objects
向量中有两个对象,则创建一个内部节点,该节点有两个子节点,每个子节点分别包含一个对象。 - 如果
objects
向量中有两个以上的对象,则进行以下操作:- 计算所有对象的中心点的边界盒
centroidBounds
。 - 确定扩展最大的维度
dim
(x, y, 或 z)作为分割轴。 - 根据中心点的坐标,沿着扩展最大的维度对原始对象进行排序。
- 将排序后的对象向量分为两半,创建两个子向量
leftshapes
和rightshapes
。 - 递归地构建左子树和右子树。
- 当前节点的边界盒为左子节点和右子节点的边界盒的并集。
- 计算所有对象的中心点的边界盒
最后返回树根到root,完成建树,最后得到图片:
总结
就这样我们分析完了所有的代码,知道了实现渲染的过程是从main开始,先导入obj光源到scene里面,scene再建立好bvh树,这个数全部是对于兔子的三角形的树,然后调用r.Render(scene)开始渲染,渲染时,是对于每一个像素建立光线使用 scene.castRay( r, 0 );来得到这个光线打到的颜色,其中在castRay里面,我们为了得到交点,使用了 Scene::intersect(ray);来得到交点,而intersect函数就是调用当前场景的BVH的成员函数getIntersection来递归找到交点,最后利用交点的信息着色,绘制图片!!!