跳转至

Assignment 6

文本统计:约 821 个字 • 169 行代码

这次作业分为两个部分,一部分是利用网格加速光线追踪,另一部分是添加程序化纹理。

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 总是会直接帮我补充代码,一些细节有时候会写错,好几次作业的一些函数的参数都出现了问题。

还有三次作业,继续加油!!!

评论区

对你有帮助的话请给我个赞和 star => GitHub stars
欢迎跟我探讨!!!