该博客只分析较难理解的函数和用途,以及程序运行方式,简单的函数不再赘述。
- 首先,我们将从程序运行的流程来理解代码框架。
- 之后再按照代码文件来分析整个框架的构造思路。
程序流程(main函数进入)
构造Scene类(构造场景)
Scene scene(784, 784);
Scene
参数:
int width = 1280;
int height = 960;
double fov = 40;
Vector3f backgroundColor = Vector3f(0.235294, 0.67451, 0.843137);
int maxDepth = 1;
float RussianRoulette = 0.8;
BVHAccel *bvh;
std::vector<Object* > objects; //模型指针集合
std::vector<std::unique_ptr<Light> > lights; //光源指针集合
构造函数:
Scene(int w, int h) : width(w), height(h)
{}
设置材质(Material类)
Material* red = new Material(DIFFUSE, Vector3f(0.0f));
red->Kd = Vector3f(0.63f, 0.065f, 0.05f);
参数:
MaterialType m_type;//只有diffuse材质(漫反射材质)
Vector3f m_emission;//辐射量
float ior;
Vector3f Kd, Ks;//Kd漫反射系数,Ks高光镜面反射系数
float specularExponent;
构造函数
Material::Material(MaterialType t, Vector3f e){
m_type = t;//材质类型
m_emission = e;//不知道是什么,构造时都为0.0f
}
使用:在main函数中构造了red,green,white,light 四种材质,设置了材质的漫反射系数。
加载模型(MeshTriangle类,三角形网格类)
MeshTriangle floor("../models/cornellbox/floor.obj", white);
参数
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树的根指针
float area; //表面积之和
Material* m; //材质指针
构造函数
- 调用obj1::Loader载入模型(Loader类这里便不再介绍,这个类主要用于读取obj格式的文件)
objl::Loader loader;
loader.LoadFile(filename);
- 将模型中的所有三角形保存到三角形集合中,并且计算出整个模型的包围盒保存到bounding_box中
- 创建三角形指针集合ptrs,用三角形指针集合构建BVH树
std::vector<Object*> ptrs;
for (auto& tri : triangles){
ptrs.push_back(&tri);
area += tri.area;
}
bvh = new BVHAccel(ptrs);
对模型构建BVH树
BVHAccel 类(BVH加速器)
参数:
BVHBuildNode* root; //BVH树根节点。
// BVHAccel Private Data
const int maxPrimsInNode; //默认值为1,在代码中没有被使用
const SplitMethod splitMethod; //拆分方法,默认为SplitMethod::NAIVE(原始的)
std::vector<Object*> primitives;//三角形指针集合
构造函数:
调用recursiveBuild(递归构造)
root = recursiveBuild(primitives);
BVHBuildNode结构体
struct BVHBuildNode {
Bounds3 bounds; //包围盒
BVHBuildNode *left; //左指针
BVHBuildNode *right; //右指针
Object* object; //物体指针
float area; //面积
...
}
recursiveBuild函数
构建BVH树:
- 对于每一个结点都有该结点包含三角形的包围盒,左右指针指向子结点。
- 每次对包围盒的最长边进行分隔。
- 求每个三角形的重心,以重心坐标包围盒最长的方向进行排序,再二分递归建立子节点。
- 直到结点中只剩下1个三角形,结束递归(两个三角形建立左右结点递归)。
- 返回根结点
将模型添加到场景
scene.Add(&floor);
将模型指针添加到<object*>容器中。
void Add(Object *object) { objects.push_back(object); }
对场景构建BVH树
scene.buildBVH();
对object容器构建BVH树,将结果保存在Scene的bvh中。
this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
对每一个模型看作一个整体进行树的构建。
整个BVH树的构建
先以模型为基本元素进行树的构建,再对每一个模型内部进行树的构建。
渲染
Renderer r;
r.Render(scene);
对场景中的每一个像素生成一道从视点发出的光线
framebuffer[m++] = scene.castRay(Ray(eye_pos, dir), 0);
调用castRay
函数进行光线追踪。
这块内容详见 作业7,作业代码分析。
存储到文件
存储为ppm文件格式。
// save framebuffer to file
FILE* fp = fopen("binary.ppm", "wb");
(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
for (auto i = 0; i < scene.height * scene.width; ++i) {
static unsigned char color[3];
color[0] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].x), 0.6f));
color[1] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].y), 0.6f));
color[2] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].z), 0.6f));
fwrite(color, 1, 3, fp);
}
fclose(fp);
到此为止,程序便完成了最终图像的生成
下面我们对文件中各功能进行分析
对代码文件分析
代码文件中有较多并未使用的函数和许多冗杂参数,使得阅读代码时“杂音”太多。下面将只介绍光线追踪中使用到的函数。
Main.cpp
进行场景的构建,模型的添加,参数的设置,渲染的调用。
Scene类
参数
int width = 1280;
int height = 960;
double fov = 40;
Vector3f backgroundColor = Vector3f(0.235294, 0.67451, 0.843137);
int maxDepth = 1;
float RussianRoulette = 0.8;//俄罗斯轮盘赌
std::vector<Object* > objects;//模型的容器(存储的是模型指针,不是三角形)
std::vector<std::unique_ptr<Light> > lights;
BVHAccel *bvh;
函数
向场景中添加物体
void Add(Object *object) { objects.push_back(object); }
void Add(std::unique_ptr<Light> light) { lights.push_back(std::move(light)); }
获取object和light的引用
const std::vector<Object*>& get_objects() const { return objects; }
const std::vector<std::unique_ptr<Light> >& get_lights() const { return lights; }
光线与场景求交
Intersection intersect(const Ray& ray) const;
对object容器构造BVH树
void buildBVH();
光线追踪
Vector3f castRay(const Ray &ray, int depth) const;
光源采样,在光源中按面积均匀采样,返回pos(采样点信息)和pfd(采样点密度)
void sampleLight(Intersection &pos, float &pdf) const;
未使用的函数
bool trace(const Ray &ray, const std::vector<Object*> &objects, float &tNear, uint32_t &index, Object **hitObject);
std::tuple<Vector3f, Vector3f> HandleAreaLight(const AreaLight &light, const Vector3f &hitPoint, const Vector3f &N,
const Vector3f &shadowPointOrig,
const std::vector<Object *> &objects, uint32_t &index,
const Vector3f &dir, float specularExponent);
Vector3f reflect(const Vector3f &I, const Vector3f &N) const;
Vector3f refract(const Vector3f &I, const Vector3f &N, const float &ior) const;
void fresnel(const Vector3f &I, const Vector3f &N, const float &ior, float &kr) const
//由菲涅耳公式可以求出一定入射角下反射和透射的振幅、强度等
Triangle类
class Triangle : public Object
参数
Vector3f v0, v1, v2; // vertices A, B ,C , counter-clockwise order
Vector3f e1, e2; // 2 edges v1-v0, v2-v0;
Vector3f t0, t1, t2; // texture coords
Vector3f normal;
float area;
Material* m;
函数
返回光线ray与该三角形的交点信息
Intersection Triangle::getIntersection(Ray ray)
随机得到三角形内一点,并得到该点pdf(该点的概率密度)为1/三角形面积。
void Sample(Intersection &pos, float &pdf)
得到三角形面积
float getArea();
判断该物体是否为光源
bool hasEmit();
class MeshTriangle : public Object
参数
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;
float area;
Material* m;
函数
构造函数:加载模型,并将模型中的三角形加载入std::vector<Triangle> triangles
中,并对该模型构造BVH树
MeshTriangle(const std::string& filename, Material *mt = new Material())
光线与该模型求交
Intersection getIntersection(Ray ray)
下面函数功能与Triangle
类相同
void Sample(Intersection &pos, float &pdf)
float getArea()
bool hasEmit()
其他未提及的函数都是没有被使用的。
BVHAccel类
struct BVHBuildNode
Bounds3 bounds;
BVHBuildNode *left;
BVHBuildNode *right;
Object* object;
float area;
参数
enum class SplitMethod { NAIVE, SAH };//分隔方法
BVHBuildNode* root;
const int maxPrimsInNode;
const SplitMethod splitMethod;
std::vector<Object*> primitives;
函数
构造函数
对容器p
构造BVH树,返回根节点。
BVHAccel(std::vector<Object*> p, int maxPrimsInNode = 1, SplitMethod splitMethod = SplitMethod::NAIVE);
内部函数:递归构造BVH树函数(设为private更好)
BVHBuildNode* BVHAccel::recursiveBuild(std::vector<Object*> objects)
光线与root根节点下的三角形求交。
Intersection Intersect(const Ray &ray) const;
内部函数:递归搜索查找光线与node根节点下的三角形求交,返回最先打到的三角形交点。(设为private更好)
Intersection getIntersection(BVHBuildNode* node, const Ray& ray)const;
获取根节点(root)下的某个点,且返回该点密度函数(1/该根节点包含的面积)
void Sample(Intersection &pos, float &pdf);
根据p
值得到某个三角形上的某一点pos
,pdf
恒为1返回。
void getSample(BVHBuildNode* node, float p, Intersection &pos, float &pdf);