目录
作业要求
Render.cpp
TODO:需要的补充内容
Triangle.hpp
框架
Ray.hpp -> struct Ray
Intersection.hpp -> struct Intersection
判断有无交点
TODO:需要的补充内容
Bounds3.hpp
TODO:需要的补充内容
BVH.cpp
框架
BVHAccel::BVHAccel()
recursiveBuild()
框架
分类讨论&递归
TODO:getIntersection()
遇到的问题汇总
报错C4996: 'fopen'不安全
解决方法
报错:Assertion failed
解决方法
结果展示
渲染时间过长的原因及解决方法
作业要求一共有四个内容需要我们补充,其中前面的两个过程与作业5类似。
Render.cpp光线生成,这里跟作业5的基本相同,就不做过多的注释。
TODO:需要的补充内容void Renderer::Render(const Scene& scene)
{
std::vector 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;
// TODO: Find the x and y positions of the current pixel to get the direction
// vector that passes through it.
// Also, don't forget to multiply both of them with the variable
// *scale*, and x (horizontal) variable with the *imageAspectRatio*
// Don't forget to normalize this direction!
Vector3f dir = normalize(Vector3f(x, y, -1));
//castRay(const Ray ray, int depth)
//Ray(ori, dir, _t=0)
Ray ray(eye_pos, dir);
framebuffer[m++] = scene.castRay(ray, 0);
}
UpdateProgress(j / (float)scene.height);
}
UpdateProgress(1.f);
// 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 * clamp(0, 1, framebuffer[i].x));
color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y));
color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z));
fwrite(color, 1, 3, fp);
}
fclose(fp);
}
Triangle.hpp
补充光线与三角形求交的getIntersection()函数.
框架inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;
...
return inter;
}
其中包含的两个结构 Ray 和 Intersection:
Ray.hpp -> struct Raystruct Ray{
//Destination = origin + t*direction
Vector3f origin;
Vector3f direction, direction_inv;
double t;//transportation time,
double t_min, t_max;
Ray(const Vector3f& ori, const Vector3f& dir, const double _t = 0.0): origin(ori), direction(dir),t(_t) {
direction_inv = Vector3f(1./direction.x, 1./direction.y, 1./direction.z);
t_min = 0.0;
t_max = std::numeric_limits::max();
}
Vector3f operator()(double t) const{return origin+direction*t;}
friend std::ostream &operatorleft = recursiveBuild(std::vector{objects[0]});
node->right = recursiveBuild(std::vector{objects[1]});
node->bounds = Union(node->left->bounds, node->right->bounds);
return node;
}
...
②只有两个物体:
...
//如果只有两个物体,则把两个物体分别作为左右子节点进行遍历,下一个recur这两个物体就可以分别作为size==1的叶节点了
else if (objects.size() == 2) {
node->left = recursiveBuild(std::vector{objects[0]});
node->right = recursiveBuild(std::vector{objects[1]});
node->bounds = Union(node->left->bounds, node->right->bounds);
return node;
}
...
③物体>2个:
//>2个物体
else {
Bounds3 centroidBounds;
for (int i = 0; i < objects.size(); ++i)
//得到所有box的中心覆盖的范围AABB
centroidBounds =
Union(centroidBounds, objects[i]->getBounds().Centroid());//centroid()得到box的中心
//得到覆盖范围最大的轴(x->0 y->1 z->2)
int dim = centroidBounds.maxExtent();
//在该轴进行排序,升序排列
switch (dim) {
case 0://x
std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
return f1->getBounds().Centroid().x <
f2->getBounds().Centroid().x;
});
break;
case 1://y
std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
return f1->getBounds().Centroid().y <
f2->getBounds().Centroid().y;
});
break;
case 2://z
std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
return f1->getBounds().Centroid().z <
f2->getBounds().Centroid().z;
});
break;
}
//对半分
auto beginning = objects.begin();
auto middling = objects.begin() + (objects.size() / 2);
auto ending = objects.end();
//分成左右两份,分别进行
auto leftshapes = std::vector(beginning, middling);
auto rightshapes = std::vector(middling, ending);
assert(objects.size() == (leftshapes.size() + rightshapes.size()));
//开始递归了,把叶节点不断分成左右两个子节点,直到到了最后只剩一个物体
node->left = recursiveBuild(leftshapes);
node->right = recursiveBuild(rightshapes);
node->bounds = Union(node->left->bounds, node->right->bounds);
}
TODO:getIntersection()
这里主要是要有个分类的思路,我在代码注释里展示出来了,具体看代码就好:
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
// TODO Traverse the BVH to find intersection
//递归调用Bounds3::intersectP
Intersection res;
// 首先判断这个ray与当前box是否有交点:
// 1.如果没有交点 -> 那就不用继续了,因为再进行细分也没有意义,直接返回当前的intersection
std::array dirIsNeg = { int(ray.direction.x>0),int(ray.direction.y > 0),int(ray.direction.z > 0) };
if (!node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg)) {
return res;
}
// 2.如果有交点 -> 2.1若该点无子节点,则返回该交点,该节点是没有子节点,可以进行BVN判断是否相交
if (node->left == nullptr && node->right == nullptr) {
res = node->object->getIntersection(ray);
return res;
}
//2.1该点有子节点,则左右子节点分别判断,继续递归
Intersection resleft, resright;
resleft = getIntersection(node->left, ray);
resright = getIntersection(node->right, ray);
return resleft.distance < resright.distance ? resleft : resright;
}
遇到的问题汇总
报错C4996: 'fopen'不安全
属性 -> C/C++ -> 预处理器 -> 定义 ->加上以下定义即可:
_CRT_SECURE_NO_WARNINGS
检查main.cpp里obj模型的路径,如果是两个点,需要将model文件夹放在VS项目文件夹下面才行。
注意,跟作业5一样,输出的是ppm文件,可以用PS等软件打开再保存成png等图片格式即可。
吐槽:用时80min我真的会谢。。。电脑太拉了TAT,迟早给他换掉!!
渲染时间过长的原因及解决方法这里是做完作业7后的我,又回来复盘了!
作业6渲染时长竟然有快80min,后来我发现大家大部分都是用了几秒就完成了,开始回来找问题,后来看到了这个问题:
作业6渲染时间过长是为什么 – 计算机图形学与混合现实在线平台 (games-cn.org)
其中,有两个人给了问题的解决方法:
我再回去看了一眼自己IntersectP函数:
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array& dirIsNeg) const
{
//use this because Multiply is faster that Division
// dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
// TODO test if ray bound intersects
//判断包围盒Bounding Box与光线是否相交
//tenter = max{tmin} texit = min{tmax}
//先给个无穷初值
double tenter = -std::numeric_limits::infinity();
double texit = std::numeric_limits::infinity();
for (int i = 0; i < 3; i++) {
//求三个轴的tmin,tmax
// invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z),
double tmin = (pMin[i] - ray.origin[i]) * invDir[i];
double tmax = (pMax[i] - ray.origin[i]) * invDir[i];
//用dirIsNeg判断光线方向
if (!dirIsNeg[i])//如果i= 0;
}
竟然用了一个循环!!!
先甩一下把循环消除后的解决办法,用时11s
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array& dirIsNeg) const
{
// invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
// dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
// TODO test if ray bound intersects
Vector3f tmin = (pMin - ray.origin) * invDir;
Vector3f tmax = (pMax - ray.origin) * invDir;
Vector3f dir = ray.direction;
if (dir.x < 0)
std::swap(tmin.x, tmax.x);
if (dir.y < 0)
std::swap(tmin.y, tmax.y);
if (dir.z < 0)
std::swap(tmin.z, tmax.z);
float texit = std::min(tmax.x, std::min(tmax.y, tmax.z));
float tenter = std::max(tmin.x, std::max(tmin.y, tmin.z));
return tenter < texit&& tenter > 0;
}
11s VS 80min 很多了!那么为什么会这样呢?道理其实很简单,递归如果有循环相当于指数增长了,用时也会爆炸增长,因此以后如果遇到这种需要取值的可以选择其他的方法,尽量避免循环的使用~【X】
【2023.6.13补充】其实时间差别不是循环的影响,我后来发现,循环用不用只是20s和10s的差别,真正原因是在tenter和texit那里max和min用错了导致的问题!