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\) 表示需要
求的出射方向。有
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。