Assignment 6¶
这次作业分为两个部分,一部分是利用网格加速光线追踪,另一部分是添加程序化纹理。
Tasks¶
(1)题目提供了 RayTracingStats
类,用于统计一些数据,方便我们得到利用网格加速效果。在命令行中提供 -stats
用来选择是否统计。
(2)利用上一次作业实现光线追踪的加速,新写一个函数 RayCastFast
用来完成加速的光线相交。
(3)因为一些无限大的 primitives
(在这里单指 plane
) 我们需要在 grid
类中新加入一个指针指向 Object3DVector
用于存储这些 primitives
在之前的光线相交中,需要将 primitives
分为两部分,一部分是在网格中的,另一部分是存储在 infinitePrimitives
中的,分别求交,然后得到最近的那一个。
(4)增加几种新的材质:
CheckerBoard
棋盘格Noise
类似于棉絮状的纹理Marble
大理石Wood
木头材质
具体的实现见 Implementation
Implementation¶
traceRayFast
¶
实现网格化加速光线与物体相交,我们可以更改 Grid::interscet
,上一次作业该函数的内容仅仅只是找到最终相交的格子,在本次作业中继续在格子中寻找交点,然后将 traceRay
函数的求交部分 group->intersect
改为 grid->intersect
即可。
bool Grid::intersect(const Ray &r, Hit &h, float tMin)
{
static Material* m = new PhongMaterial(
{1,1,1},{0,0,0},1,
{0,0,0},{0,0,0},1
);
MarchingInfo mi;
Vec3f p1, p2, p3, p4, normal;
bool hSet = false;
Hit hTmp;
int ret = initializeRayMarch(mi, r, tMin);
//ret返回-1,说明没有交点
if (!visualizeGrid && ret != -1)
{
while (mi.valid)
{
RayTracingStats::IncrementNumGridCellsTraversed();
//ret 为3 说明光线不是从内部发出的,画出enterface
if (ret != 3)
{
Vec3f p = r.pointAtParameter(mi.tMin);
hitFace(boundingBox, mi, ret, p1, p2, p3, p4, normal);
if (normal.Dot3(r.getDirection()) > 0) normal.Negate();
RayTree::AddEnteredFace(p1, p2, p4, p3, normal, m);
}
//如果打到的格子是有物体的,那么就要一一与内部的物体进行检测
if (occupied(mi.i,mi.j,mi.k))
{
auto objects = getObjects(mi.i, mi.j, mi.k);
for (int i = 0; i < objects->getNumObjects(); i++)
{
auto obj = objects->getObject(i);
bool hasInter = obj->intersect(r, hTmp, tMin);
//如果与这个物体有交点,并且这个交点还在这个格子里面
if (hasInter && isInGrid(hTmp.getIntersectionPoint(), mi.i, mi.j, mi.k))
{
//如果这个交点是这个格子中当前最近的位置,那么更新h
if (!hSet || hTmp.getT() < h.getT())
{
h = hTmp;
hSet = true;
}
}
}
if (hSet) break;
}
ret = mi.nextCell();
}
if (hSet && ret !=3)
{
auto p = h.getIntersectionPoint();
hitFace(boundingBox, mi, ret, p1, p2, p3, p4, normal);
if (normal.Dot3(r.getDirection()) > 0) normal.Negate();
RayTree::AddHitCellFace(p1, p2, p3, p4, normal, m);
}
}
//如果visualizeGrid为真,那么需要看到的是格子的样子,直接将作业五的部分嫁接到这里
if(visualizeGrid)
{
while (mi.valid && !occupied(mi.i, mi.j, mi.k))
{
Vec3f p1, p2, p3, p4, normal;
hitFace(boundingBox, mi, ret, p1, p2, p3, p4, normal);
if (r.getDirection().Dot3(normal) > 0) normal.Negate();
RayTree::AddHitCellFace(p1, p2, p3, p4, normal, m);
ret = mi.nextCell();
}
if (mi.valid)
{
Vec3f p1, p2, p3, p4, normal;
hitFace(boundingBox, mi, ret, p1, p2, p3, p4, normal);
if (r.getDirection().Dot3(normal) > 0) normal.Negate();
m->setDiffuseColor(getColor(mi.i, mi.j, mi.k));
RayTree::AddEnteredFace(p1, p2, p3, p4, normal, m);
h.set(mi.tMin, m, normal,r);
return true;
}
return false;
}
//最后是与 infinitePrimitives 的相交结果,与现有的h作比较
for (int i = 0; i< infinitePrimitives->getNumObjects();i++)
{
auto obj = infinitePrimitives->getObject(i);
if (obj->intersect(r, hTmp, tMin))
{
if (!hSet || hTmp.getT() < h.getT())
{
h = hTmp;
hSet = true;
}
}
}
//最后返回是否相交的结果
return hSet;
}
Other Materials¶
Checker board¶
棋盘格材质其实就是两者材质交替排列,在这个派生类中,需要包含的成员变量为两种材质,以及一个矩阵,这个矩阵的作用是将世界坐标转换到材质坐标,然后判断材质的种类。
我们可以写一个辅助函数用于判断世界坐标下的点是属于什么材质
Material *getMaterialByPoint(const Vec3f &_p) const {
Vec3f p = _p;
m->Transform(p);
auto px = (int) floor(p.x()), py = (int) floor(p.y()), pz = (int) floor(p.z());
auto oddCount = (px % 2) + (py % 2) + (pz % 2);
if (oddCount % 2 == 0) {
return mat1;
} else {
return mat2;
}
}
然后在 shade
函数中返回相应的结果
Vec3f Checkerboard::Shade(const Ray &ray, const Hit &hit, const Vec3f &dirToLight, const Vec3f &lightColor) const
{
Vec3f p = hit.getIntersectionPoint();
return getMaterialByPoint(p)->Shade(ray, hit, dirToLight, lightColor);
}
Noise¶
这种材质是程序化生成的材质,使用到了噪声函数 noise
,然后得到一个插值参数,将两种材质的结果进行插值。
Vec3f Noise::Shade(const Ray &ray, const Hit &hit, const Vec3f &dirToLight, const Vec3f &lightColor) const
{
Vec3f p = hit.getIntersectionPoint();
m->Transform(p);
float N =0;
for (int i = 0; i < octaves; i++)
{
float oct = pow(2, i);
N += PerlinNoise::noise(p.x() * oct, p.y() * oct, p.z() * oct) / oct;
}
return mat1->Shade(ray, hit, dirToLight, lightColor) * (N) + mat2->Shade(ray, hit, dirToLight, lightColor) * (1-N);
}
Marble¶
这个材质是模拟了大理石材质,相比于上一个材质,大理石材质在x方向上有一些条纹,所以在插值参数的计算上有 x 的坐标。
Vec3f Marble::Shade(const Ray &ray, const Hit &hit, const Vec3f &dirToLight, const Vec3f &lightColor) const
{
Vec3f p = hit.getIntersectionPoint();
m->Transform(p);
float N;
for (int i = 0; i < octaves; i++)
{
float oct = pow(2, i);
N += PerlinNoise::noise(p.x() * oct, p.y() * oct, p.z() * oct) / oct;
}
float M = sin(frequency * p.x() + amplitude * N) ;
M = (M+1)/2;
return mat1->Shade(ray, hit, dirToLight, lightColor) * M + mat2->Shade(ray, hit, dirToLight, lightColor) * (1-M);
}
Wood¶
这个材质模拟了木头的材质,这个材质则是绕着某个轴有一圈一圈的条纹,在插值参数的计算上有到 y 轴的距离。
Vec3f Wood::Shade(const Ray &ray, const Hit &hit, const Vec3f &dirToLight, const Vec3f &lightColor) const
{
Vec3f p = hit.getIntersectionPoint();
m->Transform(p);
float N;
for (int i = 0; i < octaves; i++)
{
float oct = pow(2, i);
N += PerlinNoise::noise(p.x() * oct, p.y() * oct, p.z() * oct) / oct;
}
float l = sqrt(p.x() * p.x() + p.z() * p.z());
float M = sin(frequency * l + amplitude * N) ;
M = (M+1)/2;
return mat1->Shade(ray, hit, dirToLight, lightColor) * M + mat2->Shade(ray, hit, dirToLight, lightColor) * (1-M);
}
Summary¶
这次作业整体难度没有上次那么大,难的部分只有写 Grid::intersect
的部分,材质的部分基本上题目都给了相应的思路。但是依然在写的时候出现了很多 bug
- Transform 的部分,上一次作业并未全部完成,插入到网格中的物体并不是直接插入原物体,而是应该插入相应的 transform
- camera 的生成光线部分,之前几次作业都默认图片的长宽比是一样的,题目中的 angle 应该指的是横向的角度
- 还有 tmin 的理解,在写
Grid::intersect
时,参数给的不太对,导致查了非常久
每次 IDE 总是会直接帮我补充代码,一些细节有时候会写错,好几次作业的一些函数的参数都出现了问题。
还有三次作业,继续加油!!!