https://www.luogu.com.cn/contest/70408

bmp

比较基础。

#include "LightImage.h"

namespace Light
{
    void Image::SaveBitmapToFile(const char *_path) const
    {
        //请在这里实现你的Bitmap输出

        FILE*outf=fopen(_path,"wb");
        if(outf==NULL) return;
        UINT ls=(width*3+3)/4*4;

        BITMAPFILEHEADER bfh;
        bfh.bfType=0x4d42;
        bfh.bfSize=54+height*ls;
        bfh.bfReserved1=bfh.bfReserved2=0;
        bfh.bfOffBits=0x36;
        fwrite(&bfh,sizeof bfh,1,outf);

        BITMAPINFOHEADER bih;
        bih.biSize=0x28;
        bih.biWidth=width;
        bih.biHeight=height;
        bih.biPlanes=1;
        bih.biBitCount=0x18;
        bih.biCompression=bih.biSizeImage=bih.biXPelsPerMeter
            =bih.biYPelsPerMeter=bih.biClrUsed=bih.biClrImportant=0;
        fwrite(&bih,sizeof bih,1,outf);

        BYTE blank=0;
        for(int i=height-1;~i;--i){
            fwrite(pixels+i*width,sizeof*pixels,width,outf);
            for(UINT b=width*3;b!=ls;++b)
                fwrite(&blank,sizeof blank,1,outf);
        }

        fclose(outf);
        return;
    }
}

triangle

实现一个射线与三角形求交的函数。

计算几何库中找到一个函数——planeIntersection,可以计算出一个平面和一个射线的交。利用平面的构造函数,加上交点在三角形内的判断即可。

展示成果前我想说说原理:

  • planeIntersection:用一个四维向量表示点或向量,第四维用 0/1 表示是向量/点,对于射线的原点 \(\vec o\) 与方向向量 \(\vec d\)planeIntersection 求的正是一个标量 \(l\) 和交点 \(\vec o+l\vec d\);设平面方程为 \(ax+by+cz+d=0\),把平面视为向量 \((a,b,c,d)\),那么点在平面上相当于 \((a,b,c,d)\cdot(\vec o+l\vec d)=0\)。(还记得吗?点的第四维为 1)。那么可推出 \(l=-\frac{\vec o\cdot(a,b,c,d)}{\vec d\cdot(a,b,c,d)}\)
  • 点 P 在三角形 ABC 内:\(S_{\triangle ABC}=S_{\triangle ABP}+S_{\triangle APC}+S_{\triangle PBC}\)
#include "include/LightGeometry.h"

namespace Light
{
    double triangleIntersection(const Vector4& v0, const Vector4& v1, const Vector4& v2, const Ray& ray, Vector4* point, Vector4* normal)
    {
        Vector4 p1;
        double dis=planeIntersection(Plane(v0,v1,v2),ray.o,ray.d,&p1,normal);

        if(std::isinf(dis)) return INFINITY;

        double SA=Triangle(v0,v1,v2).Area()
            -Triangle(v0,v1,p1).Area()
            -Triangle(v2,v1,p1).Area()
            -Triangle(v0,v2,p1).Area();

        if(point!=NULL) *point=p1;
        if(!isZero(SA)) return INFINITY;
        return dis;
    }
}

这里再分析一下给出的库。

关于 camera 和 triangle 的比较平凡,忽略。

重点看 Render 的实现过程。MSAA 代表多重采样抗锯齿。nthread 为 0 表示能开多少线程开多少。

多重采样,spp_per_round 与 MSAA 对齐。

首先要把任务加入任务队列,一共有像素个数个。

MSAA 理论中,若是邻近的 sum(rgb^2)-sum^2(rgb) 过大,则需要多次渲染,否则不需。

然后抓一个线程看。对于每一个任务,横纵坐标分别分成 MSAA 份,分别由摄像机在该区域内随机打出一道光线,并由 SampleRay 获取 RGB 贡献。

随机打出一道光线,其实就是以原点为光线端点,z=1 的平面为幕布,向幕布上的一个矩形打光线。

triangle.cpp 内定义的 SampleRay 其实就是以白色打底,按距离决定亮度。

完成后就直接输出图片。

cornell

实现一个函数,计算单个光源对理想漫反射表面上一点,在给出表面点,表面法向,环境模型以及光源信息的情况下,对该点的亮度贡献。不考虑漫反射表面反射率。

环境模型的作用就是,判断光是否被遮挡。

文档内容:对于一个总光通量为 F 的光源,设其与场景内一漫反射点的距离为 d。若该光源与漫反射表面点的连线无场景中其他物体遮挡,则该光源对该点的亮度贡献为 \(\frac{F\cos\theta_i}{4\pi^2d^2}\)\(\theta_i\) 依旧表示入射方向与法线方向的夹角),这样可以保证总的入射光通量等于出射光通量。多个光源贡献直接相加即可。

见下方代码 LightRayTrace.cpp。

继续分析一下库。

boundingbox 其实就是一个长方体。用于快速判断不交。

变化的只有 SampleRay 改成了一个光线追踪。

只有漫反射比较简单,求出交点后,枚举光源,计算当前光源对表面交点的亮度贡献,再乘上反光率。

ball

实现函数,在给出入射光方向和反射表面法向量的情况下,计算镜面反射的出射光方向。

我们可以发现一个非常有用的性质:入射方向和出射方向的平均值与法向量同向,且这个平均值也是入射方向和出射方向在法向量上的
分量。当给出的法向量长度为 1 时,我们可以得到一个非常简单的计算出射方向的公式:

\(\vec w_i\) 表示入射方向(注意,入射方向和入射光行进方向是相反的)\(\vec n\) 表示法向量,\(\vec w_o\) 表示需要
求的出射方向。有

\[\frac{\vec w_i+\vec w_o}2=(\vec w_i\cdot\vec n)\vec n \]

\[\vec w_o=2(\vec w_i\cdot\vec n)\vec n-\vec w_i \]

LightRayTrace.cpp

#include "include/LightRayTrace.h"

namespace Light
{
//LINE 5-73 省略
    Vector3 SingleSourceContribution(Vector4 vPoint, Vector4 vNorm, Model * pModel, const Source &source)
    {
        /*
vPoint: 表示表面交点,必定为坐标
vNorm: 表示表面法向量,必定为向量
pModel: 模型类指针,用于计算光线遮挡
source: 当前计算的光源信息

返回值: 返回光源 source 对表面点 vPoint 的亮度贡献

温馨提示:
1. 光源可能和模型表面重叠,为了避免碰撞检测误差,建议碰撞检测光线的阻挡距离大于光源和表面点距离的 1-eps 倍的时候即算光照未被阻挡
2. 表面背向光源时,光源不提供亮度贡献

你需要完成这个函数以通过 cornellbox
*/
        Vector4 ldv=source.pos-vPoint;

        if(dot(ldv,vNorm)<=EPS) return MakeVector3();

        double len=ldv.len();
        ldv=-ldv/len;

        Vector4 normal;
        Surface surface;
        double d=pModel->GetIntersection(Ray(source.pos+EPS*ldv,ldv),&normal,&surface);
        if(d<len*(1-EPS)) return MakeVector3();
        return source.intensity*(-dot(vNorm,ldv)/vNorm.len()/ldv.len())/PI/d/d;
    }

    Vector4 SpecularReflectRay(Vector4 vIn, Vector4 vNorm)
    {
        /*
vIn: 表示入射光方向,必定为向量
vNorm: 表示表面法向量方向,必定为向量,保证和入射光方向夹角小于等于 90 度

返回值: 你需要返回镜面反射条件下的出射方向,必须为向量,且模长必须为 1

你需要完成这个函数以通过 balls
*/

        Vector4 ans=2*dot(vIn,vNorm)*vNorm-vIn;
        return ans/ans.len();
    }
}

分析库,和上题有改变的只是加了镜面反射,求出方向后递归即可。

panorama

题意表明,以圆柱为基础展开得到全景视图。

计算偏角与俯仰角即可。

#include "include/LightCameraPanorama.h"

namespace Light
{

    CameraPanorama::CameraPanorama()
    {
        coord = Matrix34();
    }

    void CameraPanorama::SetCoordinateSystem(Matrix34 _coord)
    {
        coord = _coord;
    }

    Ray CameraPanorama::GenerateRay(double left, double top, double right, double bottom, Random::RAND_ENGINE *eng) const
    {
        double
        x = Random::randDouble(eng, left, right),
        y = Random::randDouble(eng, top, bottom);
        return GenerateRay(x, y);
    }

    Ray CameraPanorama::GenerateRay(double x, double y) const
    {
        double t1=(x-0.5)*2*PI,t2=(0.5-y)*PI;
        if(isZero(y))
            return Ray(coord*MakeVector4(0,0,0,1),
                    coord*MakeVector4(0,1,0,0));
        if(isZero(1-y))
            return Ray(coord*MakeVector4(0,0,0,1),
                    coord*MakeVector4(0,-1,0,0));
        Vector4 d=MakeVector4(sin(t1),tan(t2),cos(t1),0);
        d=d/d.len();
        return Ray(coord*MakeVector4(0,0,0,1),coord*d);
    }
}

库没有改。

texture

CamaraOutInside,没看懂。

球面贴图就是改了 GetColor。

posted on 2023-12-09 19:14  Zaunese  阅读(78)  评论(0编辑  收藏  举报