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 表示是向量/点,对于射线的原点 o 与方向向量 dplaneIntersection 求的正是一个标量 l 和交点 o+ld;设平面方程为 ax+by+cz+d=0,把平面视为向量 (a,b,c,d),那么点在平面上相当于 (a,b,c,d)(o+ld)=0。(还记得吗?点的第四维为 1)。那么可推出 l=o(a,b,c,d)d(a,b,c,d)
  • 点 P 在三角形 ABC 内:SABC=SABP+SAPC+SPBC
#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。若该光源与漫反射表面点的连线无场景中其他物体遮挡,则该光源对该点的亮度贡献为 Fcosθi4π2d2θi 依旧表示入射方向与法线方向的夹角),这样可以保证总的入射光通量等于出射光通量。多个光源贡献直接相加即可。

见下方代码 LightRayTrace.cpp。

继续分析一下库。

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

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

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

ball

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

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

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

wi+wo2=(win)n

wo=2(win)nwi

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   Zaunese  阅读(112)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?



点击右上角即可分享
微信分享提示