跳转至

Assignment 7

文本统计:约 647 个字 • 179 行代码

这次的作业使用超采样改善锯齿以及摩尔纹,整体难度不是特别大。

Tasks

使用 sample 类来存储一个采样点的位置\((0,0)\rightarrow (1,1)\) 以及相应的颜色,然后使用一个新的类 film 用来存储所有像素的采样结果,成员变量为:

int width;
int height;
int num_samples;
Sample *samples;

其中 samples 是成员为 Sample 的数组,存储 width*height*num_samples 这么多的样本

(1)然后实现不同的采样器 (Sampler),相应的实现一个基类 sampler 以及各个不同采样器的派生类。同时题目还给你了一个函数 RandomSampler 用于生成采样的点彩图。

(2)接着实现 filter ,用于使用采样点以生成超采样后的图像,这个类中最为重要的函数为

 Vec3f Filter::getColor(int i, int j, Film *film);

利用采样后的结果 film ,返回像素坐标为 \((i,j)\) 的像素的颜色。

要实现这个函数还需要几个辅助函数

virtual float Filter::getWeight(float x, float y) = 0;

这个函数用于返回某个点的权重,不同的 filter 的权重函数不一样

virtual int Filter::getSupportRadius() = 0;

Filter 使用的采样点的范围由 supportRaidus 来确定,这个范围题目中已经说明的很清楚了,这里不再赘述。

(3)更改 render 函数,利用 samplerfilter 得到抗锯齿的结果

Implementation

sampler

采样器的核心函数为

virtual Vec2f getSamplePosition (int n) = 0;

用于生成第 n 个采样点的采样点位置,分别要实现的采样器为

  • RandomSampler 随机采样
  • UniformSampler 均匀采样
  • JitteredSampler 抖动采样(均匀加上小范围抖动)
Vec2f RandomSampler::getSamplePosition (int n) {
    float x = (float)rand() / (float)RAND_MAX;
    float y = (float)rand() / (float)RAND_MAX;
    return Vec2f(x, y);
}

Vec2f UniformSampler::getSamplePosition (int n) {
    int i = n / nPerSide;
    int j = n % nPerSide;
    return {(i + 0.5f)*gap, (j + 0.5f)*gap};
}

Vec2f JitteredSampler::getSamplePosition (int n) {
    int i = n / nPerSide;
    int j = n % nPerSide;
    return {(i + rand() / (float)RAND_MAX)*gap, (j + rand() / (float)RAND_MAX)*gap};
}

Warning

注意一下这里返回的点的范围是 \((0,0)\rightarrow(1,1)\)

filter

filter 类中主要实现的三个函数

Vec3f getColor(int i, int j, Film *film);

virtual float getWeight(float x, float y) = 0;

virtual int getSupportRadius() = 0;

后面两个函数分别在派生类中有不同的实现方式

Vec3f Filter::getColor(int i, int j, Film *film)
{
    int r = getSupportRadius();
    int width = film->getWidth();
    int height = film->getHeight();
    int n = film->getNumSamples();

    Vec3f color(0,0,0);
    float weight = 0;

    for (int x = std::max(0, i - r); x <= std::min(width - 1, i + r); x++)
    {
        for (int y = std::max(0, j - r); y <= std::min(height - 1, j + r); y++)
        {
            for (int k = 0; k < n; k++)
            {
                //得到该像素点的第k个采样点的结果
                Sample sample = film->getSample(x, y, k);
                //计算相对于像素中心点的偏移量
                Vec2f samplePosition = Vec2f(sample.getPosition().x()+x, 
                                             sample.getPosition().y()+y);
                Vec2f offset = Vec2f(samplePosition.x()-i-0.5, 
                                     samplePosition.y()-j-0.5);
                //得到该点的权重,并计算加权后的颜色
                float w = getWeight(offset.x(), offset.y());
                weight += w;
                color += w * sample.getColor();
            }
        }
    }

    return {color.x()/weight, color.y()/weight, color.z()/weight};
}

然后不同的 filter 的权重是不一样的

  • BoxFilter 是均匀的权重
  • TentFilter 是从像素中心到距离为 radius 的位置权重是线性变化的
  • GaussianFilter 权重函数是高斯函数
float BoxFilter::getWeight(float x, float y) {
    float dist = std::max(std::abs(x), std::abs(y));
    if (dist <= radius)
        return 1;
    else return 0;
}

float TentFilter::getWeight(float x, float y) {
    float length = std::sqrt(x*x + y*y);
    float weight = 1 - length/radius;
    if (weight < 0)
        return 0;
    else return weight;
}

float GaussianFilter::getWeight(float x, float y) {
    float length = std::sqrt(x*x + y*y);
    return exp( -length*length/(2*sigma*sigma));
}

Warning

注意 getWeight 函数的参数输入的是相对于像素中心点的偏差,同时这个像素中心点指的是像素坐标为 (i,j) 的像素中心点

render 函数

更改相应的渲染函数,加入采样以及filter的过程

void RayTracerRenderer::Render() {
    Renderer::Render(); // preparations
    auto tracer = new RayTracer(scene, maxBounces, cutoffWeight);

    //采样器
    Sampler* sampler;
    if (randomSampler) {
        sampler = new RandomSampler(numSamples);
    }  else if (jitteredSampler){
        sampler = new JitteredSampler(numSamples);
    } else  {
        //如果没有说明采样器,那么就默认为采样数为1的UniformSampler
        sampler = new UniformSampler(numSamples);
    }

    //过滤器
    Filter* filter = nullptr;
    if (boxFilter){
        filter = new BoxFilter(radius);
    } else if (tentFilter) {
        filter = new TentFilter(radius);
    } else if (gaussianFilter) {
        filter = new GaussianFilter(sigma);
    }

    if (nx*ny*nz != 0)
    {
        Grid* grid = tracer->getGrid();
        BoundingBox* bb = grid->getBoundingBox();
        int nx,ny,nz;
        std::tie(nx, ny, nz) = grid->getGridSize();
        RayTracingStats::Initialize(width, height, bb, nx, ny, nz);
    }
    else 
    {
        RayTracingStats::Initialize(width, height, group->getBoundingBox(), 1, 1, 1);
    }

    //初始化film,用于存储sample
    auto film = new Film(image->Width(), image->Height(), numSamples);

    for (int i = 0; i < image->Width(); i++) {
        for (int j = 0; j < image->Height(); j++) {
            for (int k = 0; k < numSamples; k++)
            {
                Vec2f offset = sampler->getSamplePosition(k);
                auto ray = camera->generateRay(Vec2f(
                    (offset.x() + i - image->Width() / 2.0f) / image->Width(),
                    (offset.y() + j - image->Height() / 2.0f) / image->Width()));
                Hit hit;
                Vec3f color;
                if (nx*ny*nz == 0)
                {
                    color = tracer->traceRay(ray, camera->getTMin(), 0, 1, 1, hit);
                }
                else
                {
                    color = tracer->traceRayFast(ray, camera->getTMin(), 0, 1, 1, hit);
                }

                //设置采样点的信息
                film->setSample(i, j, k, offset, color);
            }

        }
    }

    //输出采样的结果
    if (sampleFile != nullptr) 
    {
        film->renderSamples(sampleFile, sampleZoom);
    }

    //输出权重函数函数
    if (filterFile != nullptr)
    {
        film->renderFilter(filterFile, filterZoom, filter);
    }

    for (int i = 0; i < image->Width(); i++)
    {
        for (int j = 0; j < image->Height(); j++)
        {
            Vec3f color = film->getSample(i, j, 0).getColor();
            //根据采样信息 film,利用filter输出渲染结果
            if (filter!=nullptr) color = filter->getColor(i, j, film);
            image->SetPixel(i, j, color);
        }
    }

    if(stats)
        RayTracingStats::PrintStatistics();

    delete sampler;
    delete filter;
    delete tracer;
}

Summary

这次作业相对前几次作业比较简单,应该也是最后一个光线追踪的作业了。这次作业做的快的主要原因是没怎么出现 bug,同时作业也巩固并加深了我对supersampling的理解。

还剩下两次作业,争取在寒假前再做一个!

评论区

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