Code Analysis:
friend:
C++中友元(友元函数和友元类)的用法和功能_friend class a<b>-CSDN博客
[C++:不如Coding](11):友元函数与友元类_哔哩哔哩_bilibili
Here is a simple explanation:
By using the mechanism of classes, data hiding and encapsulation are achieved. Data members of a class are usually defined as private, while member functions are generally defined as public, thus providing an interface for communication between the class and the outside world. However, sometimes it is necessary to define functions that are not part of the class but need frequent access to the class's data members. In such cases, these functions can be defined as friend functions of the class. In addition to friend functions, there are also friend classes, both of which are collectively referred to as friends. The purpose of friends is to improve the efficiency of the program (by reducing the time overhead of type checking and security checks, etc.). However, they undermine the encapsulation and hiding of the class, allowing non-member functions to access the class's private members.
Why use friends: You don't have to write a public method for a special case separately (for example, for a man and a woman, there is a marry method that is generally called only once during the lifecycle of these two classes. If you give the man or woman class a public method, marry, it is unnecessary and would make the class overly complex).
Operator overloading:
C++运算符重载_哔哩哔哩_bilibili
std::Move:
The functionality of std::move
is to convert a left-value into an r-value reference, and return that r-value reference, allowing the value to be used via the r-value reference for move semantics (move constructor or move assignment operator).
std::move()与移动构造函数_std::move的作用-CSDN博客
Architecture:
Let's look at the picture first:
The logical architecture is exceptionally clear now, and I've placed the source code (with comments) in another blog.
Games101Homework【6】Code Part-CSDN博客
Principle:
Checking if a ray intersects with a plane.:
Determine the entry point and exit point:
BVH Core Algorithm (Recursive):
Code:
Render():
Transform the manually added vectors into a new Ray object:
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;
// 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*
Ray ray(eye_pos, normalize(Vector3f(x, y, -1)));
// Don't forget to normalize this direction!
framebuffer[m++] =scene.castRay(ray,scene.maxDepth);
}
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::getIntersection():
Calculate intersection information:
/**
* 获取射线与三角形的交点信息
*
* @param ray 射线对象,包含射线的起点和方向
* @return Intersection 结构体,包含交点是否发生、交点坐标、交点到射线起点的距离、
* 对应的物体指针、交点处的法向量和材质信息
*/
inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter; // 初始化交点信息结构体
// 如果射线方向与三角形法向量的点积大于0,则射线方向与三角形面向反,不产生交点
if (dotProduct(ray.direction, normal) > 0)
return inter;
double u, v, t_tmp = 0;
// 计算pvec,这是进行交点计算的一部分
Vector3f pvec = crossProduct(ray.direction, e2);
// 计算行列式值det,用于后续交点计算
double det = dotProduct(e1, pvec);
// 如果行列式的绝对值小于EPSILON,表示方向向量几乎平行,不产生交点
if (fabs(det) < EPSILON)
return inter;
// 计算行列式的倒数,用于简化后续计算
double det_inv = 1. / det;
// 计算tvec,这是计算u参数的一部分
Vector3f tvec = ray.origin - v0;
// 计算参数u,如果u不在0到1之间,则交点不在三角形上
u = dotProduct(tvec, pvec) * det_inv;
if (u < 0 || u > 1)
return inter;
// 计算qvec,用于计算v参数
Vector3f qvec = crossProduct(tvec, e1);
// 计算参数v,如果v不在0到1之间,或u+v不在0到1之间,则交点不在三角形上
v = dotProduct(ray.direction, qvec) * det_inv;
if (v < 0 || u + v > 1)
return inter;
// 计算临时距离参数t_tmp,用于进一步判断交点是否成立
t_tmp = dotProduct(e2, qvec) * det_inv;
// 判断t_tmp是否小于0,若小于0,则交点在射线的起点之后,不成立
if(t_tmp<0){
return inter;
}
// 如果以上所有条件都满足,则更新交点信息为有效交点
inter.happened = true;
inter.coords = ray(t_tmp); // 计算交点坐标
inter.distance = t_tmp; // 保存交点到射线起点的距离
inter.obj = this; // 保存对应的三角形对象指针
inter.normal = normal; // 保存交点处的法向量
inter.m = m; // 保存材质信息
return inter;
}
IntersectP():
"Solve equations to calculate tEnter and tExit. Based on the direction of the ray, if it is emitted in the non-positive direction, then the smaller negative t is the exit point, and the larger one is the entry point (dirIsNeg).
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& 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 tEnter,tExit;
tEnter= (pMin-ray.origin)*invDir;
tExit = (pMax-ray.origin)*invDir;
if(!dirIsNeg[0]){
std::swap(tEnter.x,tExit.x);
}
if(!dirIsNeg[1]){
std::swap(tEnter.y,tExit.y);
}
if(!dirIsNeg[2]){
std::swap(tEnter.z,tExit.z);
}
float tenter,texit;
tenter=std::max(tEnter.x,std::max(tEnter.y,tEnter.z));
texit=std::min(tExit.x,std::min(tExit.y,tExit.z));
return tenter<=texit&&texit>=0;
}
getIntersection():
Implementing the core algorithm:
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
Intersection isect;
// TODO Traverse the BVH to find intersection
if(!node->bounds.IntersectP(ray,ray.direction_inv,std::array<int,3>{ray.direction.x>0,ray.direction.y>0,ray.direction.z>0})) return isect;
if((!node->left)&&(!node->right)){
return node->object->getIntersection(ray);
}
auto hit1=getIntersection(node->left,ray);
auto hit2=getIntersection(node->right,ray);
return hit1.distance<hit2.distance?hit1:hit2;
}
Result:
cool!