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.环境描述. 主 ...
随机推荐
- 洛谷 P1411 树
最近在做些树形DP练练手 原题链接 大意就是给你一棵树,你可以断开任意数量的边,使得剩下的联通块大小乘积最大. 样例 8 1 2 1 3 2 4 2 5 3 6 3 7 6 8 输出 18 我首先想的 ...
- 20175221 实验一《Java开发环境的熟悉》实验报告
20175221 实验一<Java开发环境的熟悉>实验报告 (一)Linux运行结果 (二)IDEA下Java程序开发.调试:学会通过调试(Debug)来定位逻辑错误 试验IDEA是否 ...
- 金融量化分析【day110】:Pandas-DataFrame索引和切片
一.实验文档准备 1.安装 tushare pip install tushare 2.启动ipython C:\Users\Administrator>ipython Python 3.7.0 ...
- solr的域
solrhom相当于存放数据的地方,里面一个个文件相当于数据库,每个数据库里面有两个文件夹,一个data,一个conf,conf下有一个schema.xml文件,配置域,相当于表的字段. solr中内 ...
- Linux查看系统信息的命令及已安装软件包的命令
系统 uname -a查看内核/操作系统/CPU信息head -n 1 /etc/issue查看操作系统版本cat /proc/cpuinfo查看CPU信息hostname查看计算机名lspci -t ...
- JavaScript 日期和时间基础知识
前言 学习Date对象之前,首先要先了解关于日期和时间的一些知识.比如,闰年.UTC等等.深入了解这些,有助于更好地理解javascript中的Date对象. 标准时间 一般而言的标准时间是指GMT和 ...
- logging模块简单使用
日志配置 #!/usr/bin/python2.7 import os import logging def get_logger(path='./', filename='access.log', ...
- 调用腾讯、百度翻译API,实现游戏机翻通用程序
最近玩了款steam独立游戏,没中文,只能自己汉化了,用腾讯跟百度的API实现了一个通用的机翻程序(只需要导入JSON文本), 同样,比较懒,还没写,先占坑
- JS实现定时任务,每隔N秒请求后台——setInterval定时和ajax请求
DiGui = function (param) { $.ajax({ success: function (returnValue) { window.setInterval("fnSet ...
- Git入门——本地版本库操作
作为一个一直用SVN的家伙,深深地感到了自己在版本控制工具上的落伍.... 首先必须强调的是: Git与Github不是一回事. Git是目前世界上最先进的分布式版本控制系统,于2005年被linux ...