PBRT笔记(1)——主循环、浮点误差
PBRT2与3之间的改动
- 增加了一个功能完备的BRDF模型,支持体积光照与重要性多重路径采样。
- 次表面散射,基于光线追踪技术,无需预处理。
- 解决浮点数四折五入的问题
- 光子映射
- 样本生成
- 第一章多了讲并行的东西
看到第2页
渲染分块问题
对这个渲染任务过多的分块会影响性能。
场景的复杂性会对不同CPU核心的渲染速度产生影响。所以如果是分块数等于核心数,渲染完的核心会等待没渲染完的核心。
过小分块也是不科学的,因为处理核心问题有一定的开销。
pbrt里用的是16*16的方案
浮点类型
pbrt采用了FLOAT,这样浮点格式会根据宏进行调整
主循环
void SamplerIntegrator::Render(const Scene &scene) {
Preprocess(scene, *sampler);
//分块并行渲染图片
//计算tiles, _nTiles_,用于并行运算
Bounds2i sampleBounds = camera->film->GetSampleBounds();
Vector2i sampleExtent = sampleBounds.Diagonal();
//书中说16*16分块可以解决大部分情况
const int tileSize = 16;
//sampleExtent.x + tileSize - 1 整除考虑
Point2i nTiles((sampleExtent.x + tileSize - 1) / tileSize,
(sampleExtent.y + tileSize - 1) / tileSize);
ProgressReporter reporter(nTiles.x * nTiles.y, "Rendering");
{
ParallelFor2D([&](Point2i tile) {
// Render section of image corresponding to _tile_
//内存池,之后会给Li函数
MemoryArena arena;
//给每个块分配一个Samper实例
int seed = tile.y * nTiles.x + tile.x;
std::unique_ptr<Sampler> tileSampler = sampler->Clone(seed);
//计算块的采样边界
int x0 = sampleBounds.pMin.x + tile.x * tileSize;
int x1 = std::min(x0 + tileSize, sampleBounds.pMax.x);
int y0 = sampleBounds.pMin.y + tile.y * tileSize;
int y1 = std::min(y0 + tileSize, sampleBounds.pMax.y);
Bounds2i tileBounds(Point2i(x0, y0), Point2i(x1, y1));
LOG(INFO) << "Starting image tile " << tileBounds;
//取得指定范围的图片块
std::unique_ptr<FilmTile> filmTile =
camera->film->GetFilmTile(tileBounds);
//开始指定区域的渲染循环
for (Point2i pixel : tileBounds) {
{
ProfilePhase pp(Prof::StartPixel);
tileSampler->StartPixel(pixel);
}
// Do this check after the StartPixel() call; this keeps
// the usage of RNG values from (most) Samplers that use
// RNGs consistent, which improves reproducability /
// debugging.
if (!InsideExclusive(pixel, pixelBounds))
continue;
do {
//初始化相机采样,存储了时间以及镜头位置的采样值
CameraSample cameraSample =
tileSampler->GetCameraSample(pixel);
//生成相机光线
//以及光线的pdf
RayDifferential ray;
Float rayWeight =
camera->GenerateRayDifferential(cameraSample, &ray);
ray.ScaleDifferentials(
1 / std::sqrt((Float)tileSampler->samplesPerPixel));
++nCameraRays;
//估算当前相机光线辐射度
Spectrum L(0.f);
if (rayWeight > 0) L = Li(ray, scene, *tileSampler, arena);
//对渲染出错误结果的处理,log相应信息
if (L.HasNaNs()) {
LOG(ERROR) << StringPrintf(
"Not-a-number radiance value returned "
"for pixel (%d, %d), sample %d. Setting to black.",
pixel.x, pixel.y,
(int)tileSampler->CurrentSampleNumber());
L = Spectrum(0.f);
} else if (L.y() < -1e-5) {
LOG(ERROR) << StringPrintf(
"Negative luminance value, %f, returned "
"for pixel (%d, %d), sample %d. Setting to black.",
L.y(), pixel.x, pixel.y,
(int)tileSampler->CurrentSampleNumber());
L = Spectrum(0.f);
} else if (std::isinf(L.y())) {
LOG(ERROR) << StringPrintf(
"Infinite luminance value returned "
"for pixel (%d, %d), sample %d. Setting to black.",
pixel.x, pixel.y,
(int)tileSampler->CurrentSampleNumber());
L = Spectrum(0.f);
}
VLOG(1) << "Camera sample: " << cameraSample << " -> ray: " <<
ray << " -> L = " << L;
// Add camera ray's contribution to image
filmTile->AddSample(cameraSample.pFilm, L, rayWeight);
//释放内存池
arena.Reset();
} while (tileSampler->StartNextSample());
}
LOG(INFO) << "Finished image tile " << tileBounds;
//将图片块合并到图片上去
camera->film->MergeFilmTile(std::move(filmTile));
reporter.Update();
}, nTiles);
reporter.Done();
}
LOG(INFO) << "Rendering finished";
//保存图片到文件
camera->film->WriteImage();
}
并行相关问题
- 读取场景文件以及创建场景都是单线程的,获取场景信息因为不涉及到修改数据,所以可以无视。我们只需要关注修改内存数据的情况。
- 请不要在不同步的情况下修改数据
- 对应初始化可以考虑std::call_once函数
- 实用程序类MemoryArena(用于高性能临时内存分配)和RNG(伪随机数生成)也不适合多线程使用; 这些类存储在调用其方法时被修改的状态,并且相互排除的保护修改到其状态的开销相对于它们执行的计算量而言过多。 因此,在上面的SamplerIntegrator :: Render()方法的代码中,实现在堆栈上分配这些类的perthread实例。
- 每个线程需要各复制一个Sampler的实例,以保证线程安全
- 目前的计算机架构运算除法、平方根和三角函数是最慢的。加法与乘法相比之下要快10~50倍。所以我们可以减少这种数学运算数量来提高性能。例如我们可以提前计算1/v,之后再乘。而不是重复除以v。
Shape类
- Shape存储了Transform(正变换、逆变换)指针指针,这些指针都指向了一个智能指针,通过Transform池来进行内存空间管理。 (3.1.1)
- 全部Shape对象都采用了独立int32进行id管理(3.1.1)
- 在PBRT中的一些Shape支持通过texture的alpha通道颜色进行cutting away。(3.1.1)
- IntersectP()返回相交结果而不返回相交表面数据,Intersect()则会返回相交表面数据。(3.1.3)
- 为了使用面光源,所以会计算表面面积(面积模型)(3.1.4)
- PBRT不支持单面渲染(RayTracig from the ground 支持这个)(3.1.5)
sphere
使用一元二次方程求根很可能会因为浮点数舍入误差从而得到错误结果,一般来说,(-b)、sqrtdis 都是正数的话,-b+sqrtdis所得的正根一般都会是正确的,此时我们可以使用“维达定理”求得负根。
$ x_1x_2=\dfrac{c}{a} \Leftrightarrow x_2= \dfrac{c}{ax_1}$
关于舍入误差
static constexpr Float MaxFloat = std::numeric_limits<Float>::max();
static constexpr Float Infinity = std::numeric_limits<Float>::infinity();
std::numeric_limits<Float>::epsilon() * 0.5;
我们可以通过断言 !(x==x)来判断是否是NaN,也可以使用std::isnan。
光线追踪中的光线与物体求交函数的tmin值,可以帮助抵消因为浮点数舍入误差而造成的在物体内部相交的问题。较大的tmin值可以起到更好的效果,但是过大的tmin值,会使靠的比较近的面丢失部分反射与阴影效果。
不过PBRT告诉我们,可以通过一定的系统设计,让我们不再需要ray epsilon。(使用EFLoat类)
EFloat类通过重载计算符的方式,使用误差计算公式。使得EFLoat类之间的计算可以正确地积累或者抵消之前浮点运算的误差。
low = NextFloatDown(v - err);
high = NextFloatUp(v + err);
最新的代码直接计算出误差边界,而不是存储在float err变量。
PBRT笔记(1)——主循环、浮点误差的更多相关文章
- Cocos2d-x 3.2 学习笔记(十六)保卫萝卜 游戏主循环与定时器
保卫萝卜~想法一直存在于想法,实战才是硬道理!有想法就去实现,眼高手低都是空谈. 一.游戏主循环GameSchedule 主循环是游戏处理逻辑,控制游戏进度的地方,处理好主循环是很重要的 ...
- PBRT笔记(6)——采样和重构
前言 本文仅作为个人笔记分享,又因为本章涉及多个专业领域而本人皆未接触过,所以难免出错,请各位读者注意. 对于数字图像需要区分image pixels(特定采样处的函数值)和display pixel ...
- 游戏主循环(Game Loop)
游戏主循环是游戏的心跳,一般使用while循环进行主动刷新. 一次循环由获取用户输入.更新游戏状态.处理AI.播放音乐和绘制画面组成. 这些行为可以分成两类: update_game(); // 更新 ...
- 辛巴学院-Unity-剑英的c#提高篇(一)主循环
这是测试版 辛巴学院:正大光明的不务正业. 最近刚刚离开了我服务了三年多的公司,因为一个无数次碰到的老问题,没钱了. 之前不知道做什么好的时候,机缘巧合之下和哒嗒网络的吴总聊了一下,发现了vr gam ...
- 用WP_Query自定义WordPress 主循环
我们知道操作 WordPress 主循环(WordPress Loop)最容易的方法是使用 query_posts 函数. 但是使用 query_posts 直接修改 WordPress 默认的主循环 ...
- Update主循环、状态机的实现
从写一段程序,到写一个app,写一个游戏,到底其中有什么不同呢?一段程序的执行时间很短,一个应用的执行时间很长,仅此而已. 游戏中存在一个帧的概念. 这个概念大家都知道,类比的话,它就是电影胶卷的 ...
- POJ1064 Cable master(二分 浮点误差)
题目链接:传送门 题目大意: 给出n根长度为1-1e5的电线,想要从中切割出k段等长的部分(不可拼接),问这个k段等长的电线最长可以是多长(保留两位小数向下取整). 思路: 很裸的题意,二分答案即可. ...
- [UE4]游戏主循环
游戏的运行模型 理解游戏的运行模型,对处理很多游戏错误有非常大的帮助. 游戏是有一个主循环的.那么游戏主循环做了什么事情呢? 游戏主循环一次就表示一帧,游戏主循环包括:接受输入.处理游戏逻辑.渲染.S ...
- MYSQL 的 MASTER到MASTER的主主循环同步
MYSQL 的 MASTER到MASTER的主主循环同步 刚刚抽空做了一下MYSQL的主主同步.把步骤写下来,至于会出现的什么问题,以后随时更新.这里我同步的数据库是TEST1.环境描述. 主 ...
随机推荐
- luoguP4705 玩游戏
好好玩 即对于k∈[1,t] 求(ax+by)^k 以下图片均来自于: 在Ta的博客查看 一 二项式展开: 设: 那么: 可以卷积了 二 求: (PS:随机序列的0~k次方和,这是一个经典问题.) 我 ...
- 洛谷P5289 皮配
解:观察一波部分分. 首先小数据直接暴力4n,然后考虑背包.设f[i][a][b][c]表示前i个学校中前三位导师分别有多少人,第四位导师可以直接推出来. 然后暴力枚举每一个人放在哪进行背包. 进一步 ...
- ubuntu16.04连接wifi
前提:实验室里没有网线,也没有无线网络,只能用个人手机开热点上网! Then~~ 首先参考了这两篇博文: https://blog.csdn.net/weixin_41762173/article/d ...
- sublime text3格式化html,css,js代码
需要安装HTML/CSS/JS prettify插件. 安装步骤:首选项 -> Package Control -> Install Package -> HTML-CSS-JS P ...
- SpringBoot之解决云服务器VPS在所处云端集群的内网不能解析域名的问题:java.net.UnknownHostException:abc.cn: Temporary failure in name resolution
一.起因与原因分析过程 前端小伙伴儿告诉我,说服务器崩了. 请求数据接口,接口有响应,但报的json提示指向:数据库异常错误. 遂登陆云主机查看日志,核心记录显示如下: 2018-11-09 22:1 ...
- Linux文件权限命令及配置
http://www.cnblogs.com/CgenJ/archive/2011/07/28/2119454.html
- How to avoid the 0-byte file on Webclient download error
_client.DownloadDataAsync(new Uri(url)); _client.DownloadDataCompleted += (sender, e) => { try { ...
- 本地图片上传与H5适配知识
最近用到本地图片上传作为API的参数,在网上看了许多,记录一下,以后可能用的着(仅自己记录用,看不清请绕路) function getObjectURL(file) { var url = null ...
- es6编程建议和技巧点汇总
大括号 特点:大括号(单独的大括号或者if等后的大括号)内是一个单独的作用域 注意点:在块级作用域内声明的函数,类似var,会被提升到大括号外,应避免在块级作用域内声明函数.如果确实需要,写成函数表达 ...
- C#任务同步
using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using ...