Physically Based Rendering From Theory To Implementation
该系列属于系统源代码解读系列,根据源码程序,分析程序处理路径,从系统层面探索基于物理的渲染的奥秘。
该系列为译者原创,转载请说明原文出处。
作者:Elsa的迷弟
个人blog网址:https://blog.csdn.net/weixin_44518102
文章目录
2. 场景描述文件解析
接上文,当main函数处理完命令行项后,接下来需要处理的就是命令行中输入的场景文件了。
// 如果只是修改格式内容,不渲染
if (format || toPly || options.upgrade) {
FormattingParserTarget formattingTarget(toPly, options.upgrade);
ParseFiles(&formattingTarget, filenames);
}
如果命令行中有format || toPly || options.upgrade
三种参数,则不渲染图形,只做格式转化。
// 在解析和验证了参数之后,ParseFiles()函数接管处理前面描述的三个执行阶段中的第一个阶段。
// 在两个类的帮助下,BasicSceneBuilder和BasicScene(分别在第C.2节和C.3节中描述),
// 它循环遍历提供的文件名,依次解析每个文件。
else {
// Parse provided scene description files
// 解析提供的场景描述文件
BasicScene scene;
BasicSceneBuilder builder(&scene);
ParseFiles(&builder, filenames);
其中 BasicScene 为最终需要获得的数据;
BasicSceneBuilder 为解析数据使用的中间类,用于设置BasicScene的默认值;
ParseFiles(&builder, filenames);
执行解析工作。
在ParseFiles(&builder, filenames);
函数中,
void ParseFiles(ParserTarget *target, pstd::span<const std::string> filenames)
{
auto tokError = [](const char *msg, const FileLoc *loc)
{
ErrorExit(loc, "%s", msg);
};
// Process scene description
if (filenames.empty()) {
// Parse scene from standard input
// 解析标准输入场景
std::unique_ptr<Tokenizer> t = Tokenizer::CreateFromFile("-", tokError);
if (t)
parse(target, std::move(t));
}
else {
// Parse scene from input files
// 从输入场景文件中解析
for (const std::string &fn : filenames) {
if (fn != "-")
SetSearchDirectory(fn);
std::unique_ptr<Tokenizer> t = Tokenizer::CreateFromFile(fn, tokError);
if (t)
parse(target, std::move(t));
}
}
target->EndOfFiles();
}
CreateFromFile
返回一个std::unique_ptr<Tokenizer>
指针,内部函数使用了WINDOWS API,暂时不进行分析。Tokenizer为编译器,分析器的意思。此处使用该类代替了flex和bison作为文本处理类。
之后解析场景parse(target, std::move(t));
。
进入parse(target, std::move(t));
函数:
void parse(ParserTarget *target, std::unique_ptr<Tokenizer> t)
{
//---------------------------------------------------------------
// 初始化声明变量
//---------------------------------------------------------------
FormattingParserTarget *formattingTarget = dynamic_cast<FormattingParserTarget *>(target);
bool formatting = formattingTarget;
static std::atomic<bool> warnedTransformBeginEndDeprecated{false};
std::vector<std::pair<AsyncJob<int> *, BasicSceneBuilder *>> imports;
LOG_VERBOSE("Started parsing %s",
std::string(t->loc.filename.begin(), t->loc.filename.end()));
std::vector<std::unique_ptr<Tokenizer>> fileStack;
fileStack.push_back(std::move(t));
pstd::optional<Token> ungetToken;
auto parseError = [&](const char *msg, const FileLoc *loc) {
ErrorExit(loc, "%s", msg);
};
首先是一些局部变量的声明,具体是何意义,在使用时解释。
// nextToken is a little helper function that handles the file stack,
// returning the next token from the current file until reaching EOF,
// at which point it switches to the next file (if any).
// nextToken是一个小的辅助函数,它处理文件堆栈,
// 从当前文件返回下一个令牌,直到到达EOF,
// 此时它切换到下一个文件(如果有的话)。
std::function<pstd::optional<Token>(int)> nextToken;
nextToken = [&](int flags) -> pstd::optional<Token>
{
// 如果ungetToken有值,则返回ungetToken,并将ungetToken置为空{}
if (ungetToken.has_value())
return std::exchange(ungetToken, {});
// 需要数据,但文件为空时,抛出异常
if (fileStack.empty()) {
if ((flags & TokenRequired) != 0) {
ErrorExit("premature end of file");
}
return {};
}
// 获取新令牌
pstd::optional<Token> tok = fileStack.back()->Next();
if (!tok)//如果tok为EOF
{
// We've reached EOF in the current file. Anything more to parse?
// 我们已经到达当前文件中的EOF。还有什么要解析的吗?
LOG_VERBOSE("Finished parsing %s",
std::string(fileStack.back()->loc.filename.begin(),
fileStack.back()->loc.filename.end()));
// 将当前文件退出栈,遍历之后的文件,递归返回最终结果
fileStack.pop_back();
return nextToken(flags);
}
else if (tok->token[0] == '#') {
// Swallow comments, unless --format or --toply was given, in
// which case they're printed to stdout.
// 跳过注释#,除非指定了--format或--toply
// 在这种情况下,他们会打印到stdout
if (formatting)
printf("%s%s\n",
dynamic_cast<FormattingParserTarget *>(target)->indent().c_str(),
toString(tok->token).c_str());
return nextToken(flags);
}
else
// Regular token; success.
// 通常情况直接返回令牌本身
return tok;
};
之后是一个nextToken辅助函数,作用就是获取下一个token,并处理各种情况。
还有三个函数变量,暂不解释。
auto unget = [&](Token t) {...};
// Helper function for pbrt API entrypoints that take a single string
// parameter and a ParameterVector (e.g. pbrtShape()).
// pbrt API入口点的辅助函数,它接受一个字符串参数和一个参数向量(例如pbrtShape())。
auto basicParamListEntrypoint = [&](
void (ParserTarget::*apiFunc)(const std::string &, ParsedParameterVector,FileLoc),
FileLoc loc)
{...};
auto syntaxError = [&](const Token &t) {...};
之后开始解析处理
pstd::optional<Token> tok;
long tokenNumber = 0;
//---------------------------------------------------------------
// 开始解析处理
//---------------------------------------------------------------
printf("\n");
while (true) {
tok = nextToken(TokenOptional);
if (!tok.has_value())
break;
switch (tok->token[0]) {
case 'A':
...
case 'C':
...
因为解析内容过于庞大,我们从小场景文件入手。
路径:https://github.com/mmp/pbrt-v4-scenes/blob/master/killeroos/killeroo-simple.pbrt
2.1 全局渲染选项解析分析
LookAt 400 20 30
0 63 -110
0 0 1
Rotate -5 0 0 1
Camera "perspective"
"float fov" [ 39 ]
# zoom in by feet
# "integer xresolution" [1500] "integer yresolution" [1500]
# "float cropwindow" [ .34 .49 .67 .8 ]
Film "rgb"
"string filename" [ "killeroo-simple.exr" ]
"integer yresolution" [ 700 ]
"integer xresolution" [ 700 ]
Sampler "halton"
"integer pixelsamples" [ 256 ]
(1)LookAt
LookAt 400 20 30
0 63 -110
0 0 1
case 'L':
if (tok->token == "LightSource")
basicParamListEntrypoint(&ParserTarget::LightSource, tok->loc);
else if (tok->token == "LookAt") {
Float v[9];
for (int i = 0; i < 9; ++i)
v[i] = parseFloat(*nextToken(TokenRequired));
// 记录摄像机LookAt矩阵[eye,lookat,up]
target->LookAt(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8],
tok->loc);
}
else
syntaxError(*tok);
break;
v[i] = parseFloat(*nextToken(TokenRequired));
获取文件中的string类型数据,并转化为Float类型,之后保存矩阵数据。
void BasicSceneBuilder::LookAt(Float ex, Float ey, Float ez, Float lx, Float ly, Float lz,
Float ux, Float uy, Float uz, FileLoc loc) {
class Transform lookAt =
pbrt::LookAt(Point3f(ex, ey, ez), Point3f(lx, ly, lz), Vector3f(ux, uy, uz));
graphicsState.ForActiveTransforms([=](auto t) { return t * lookAt; });
}
graphicsState.ForActiveTransforms([=](auto t) { return t * lookAt; });
保存层级图形状态
对于如下场景,graphicsState保存了两组矩阵到TransformSet ctm;
中,一组矩阵包含两个矩阵,一个转化矩阵m和该转化矩阵的逆矩阵mInv。
调试输出:
ActiveTransforms0 =
[ [ -0.106884174, -0.99427146, -1.3623458e-9, 62.6391 ],
[ -0.3267802, 0.03512887, 0.9444473, 101.67608 ],
[ -0.939037, 0.10094647, -0.328663, 383.45578 ],
[ 0, 0, 0, 1 ] ]
ActiveTransforms1 =
[ [ -0.106884174, -0.99427146, -1.3623458e-9, 62.6391 ],
[ -0.3267802, 0.03512887, 0.9444473, 101.67608 ],
[ -0.939037, 0.10094647, -0.328663, 383.45578 ],
[ 0, 0, 0, 1 ] ]
两组矩阵大小相同。
(2)Rotate
Rotate -5 0 0 1
else if (tok->token == "Rotate") {
Float v[4];
for (int i = 0; i < 4; ++i)
v[i] = parseFloat(*nextToken(TokenRequired));
target->Rotate(v[0], v[1], v[2], v[3], tok->loc);
}
与LookAt同理,进入Rotate中
void BasicSceneBuilder::Rotate(Float angle, Float dx, Float dy, Float dz, FileLoc loc) {
graphicsState.ForActiveTransforms(
[=](auto t) { return t * pbrt::Rotate(angle, Vector3f(dx, dy, dz)); });
}
同样是修改图形状态
1 : Rotate
ActiveTransforms0 =
[ [ -0.019820984, -0.99980354, -1.3623458e-9, 62.6391 ],
[ -0.32859838, 0.006514424, 0.9444473, 101.67608 ],
[ -0.9442618, 0.018719874, -0.328663, 383.45578 ],
[ 0, 0, 0, 1 ] ]
ActiveTransforms1 =
[ [ -0.019820984, -0.99980354, -1.3623458e-9, 62.6391 ],
[ -0.32859838, 0.006514424, 0.9444473, 101.67608 ],
[ -0.9442618, 0.018719874, -0.328663, 383.45578 ],
[ 0, 0, 0, 1 ] ]
此状态是先前状态与Rotate矩阵相乘的结果。
(3)Camera
Camera "perspective"
"float fov" [ 39 ]
else if (tok->token == "Camera")
basicParamListEntrypoint(&ParserTarget::Camera, tok->loc);
如上所属basicParamListEntrypoint
是一个辅助函数,&ParserTarget::Camera
为一个函数指针。
/// <summary>
/// Helper function for pbrt API entrypoints that take a single string
/// parameter and a ParameterVector (e.g. pbrtShape()).
/// pbrt API入口点的辅助函数,它接受一个字符串参数和一个参数向量(例如pbrtShape())。
///
/// 注:文件格式为
/// "name" "参数类型 参数名称" [参数值1,参数值2,...]
/// 或 "name" "参数类型 参数名称" 参数值1
/// </summary>
auto basicParamListEntrypoint =
[&](void (ParserTarget::*apiFunc)(const std::string &, ParsedParameterVector, FileLoc),
FileLoc loc)
{
Token t = *nextToken(TokenRequired);
// 去除前后双引号
std::string_view dequoted = dequoteString(t);
std::string n = toString(dequoted);
printf("basicParamListEntrypoint nextToken = %s\n", n.c_str());
// 解析该名称的参数
ParsedParameterVector parameterVector = parseParameters(
nextToken,
unget,
formatting,
[&](const Token &t, const char *msg)
{
std::string token = toString(t.token);
std::string str = StringPrintf("%s: %s", token, msg);
parseError(str.c_str(), &t.loc);
}
);
// 调用该类的函数
(target->*apiFunc)(n, std::move(parameterVector), loc);
};
首先Token t = *nextToken(TokenRequired);
获取了Camera之后的Token “perspective”,作为Camera的参数调用的名称。之后去除Token前后双引号 并保存在 std::string n = "perspective";
;之后解析"perspective"的参数,调用parseParameters
函数。
Camera "perspective"
"float fov" [ 39 ]
# zoom in by feet
# "integer xresolution" [1500] "integer yresolution" [1500]
# "float cropwindow" [ .34 .49 .67 .8 ]
Film "rgb"
函数内部循环查找下一个Token,如果Token为双引号引起来的语句,则解析参数
例如"perspective"
的下一个Token为"float fov"
,"float fov"
有双引号,则解析参数类型float
,参数名称fov
,以及之后的参数值[ 39 ]
。
nextToken
函数会自动跳过注释行#xxxxx
,因此当下一次循环时,token为Film
,Film
没有双引号,因此使用此Token调用unget
函数,unget
函数将此Token保存到ungetToken
中,然后结束循环。当下一次调用nextToken
函数时,会首先考虑ungetToken
是否为空,如果不为空,返回 ungetToken
的值,再将 ungetToken
置空。
// unget函数相当于一个缓冲,将读入的却又不需要的数据写入该缓冲,供之后读取使用
auto unget = [&](Token t) {
CHECK(!ungetToken.has_value());
ungetToken = t;
};
解析的函数参数全部保存到ParsedParameterVector类型中,并返回。
最后调用传入的BasicSceneBuilder::Camera
函数
void BasicSceneBuilder::Camera(const std::string &name, ParsedParameterVector params,
FileLoc loc) {
// 将params传入ParameterDictionary,至于为什么传入graphicsState.colorSpace还不知道原因
ParameterDictionary dict(std::move(params), graphicsState.colorSpace);
VERIFY_OPTIONS("Camera");
TransformSet cameraFromWorld = graphicsState.ctm;
TransformSet worldFromCamera = Inverse(graphicsState.ctm);
namedCoordinateSystems["camera"] = Inverse(cameraFromWorld);//不懂为什么还要计算一次
// 为什么TransformSet使用两个矩阵,是因为他代表两个时间节点的矩阵变化
CameraTransform cameraTransform(
AnimatedTransform(worldFromCamera[0], graphicsState.transformStartTime,
worldFromCamera[1], graphicsState.transformEndTime));
renderFromWorld = cameraTransform.RenderFromWorld();
// 初始化camera
camera = CameraSceneEntity(name, std::move(dict), loc, cameraTransform,
graphicsState.currentOutsideMedium);
}
其中camera = CameraSceneEntity(name, std::move(dict), loc, cameraTransform,graphicsState.currentOutsideMedium);
也只是将如上数据直接存入
- CameraSceneEntity.cameraTransform
- CameraSceneEntity.medium
- SceneEntity.name
- SceneEntity.parameters
- SceneEntity.loc
未作任何其他处理。
(4)之后同理
所有类型的文件格式为
- “name” “参数类型 参数名称” [参数值1,参数值2,…]
- 或 “name” “参数类型 参数名称” 参数值1
因此如下同理
Film "rgb"
"string filename" [ "killeroo-simple.exr" ]
"integer yresolution" [ 700 ]
"integer xresolution" [ 700 ]
Sampler "halton"
"integer pixelsamples" [ 256 ]
2.2 局部渲染选项解析分析
pbrt场景描述被WorldBegin语句分成多个部分。在遇到WorldBegin之前,指定全局渲染选项是合法的,包括相机、胶片、采样器和积分器,但还不能指定形状、灯光、纹理和材料。在WorldBegin之后,所有这些都翻转过来:像相机规格这样的东西是固定的,之后可以指定场景的其余部分。
WorldBegin
AttributeBegin
Material "diffuse"
"rgb reflectance" [ 0 0 0 ]
Translate 150 0 20
Translate 0 120 0
AreaLightSource "diffuse"
"rgb L" [ 2000 2000 2000 ]
Shape "sphere"
"float radius" [ 3 ]
AttributeEnd
2.2.1 WorldBegin
case 'W':
if (tok->token == "WorldBegin")
target->WorldBegin(tok->loc);
void BasicSceneBuilder::WorldBegin(FileLoc loc) {
VERIFY_OPTIONS("WorldBegin");
// Reset graphics state for _WorldBegin_
currentBlock = BlockState::WorldBlock;
for (int i = 0; i < MaxTransforms; ++i)
graphicsState.ctm[i] = pbrt::Transform();
graphicsState.activeTransformBits = AllTransformsBits;
namedCoordinateSystems["world"] = graphicsState.ctm;
// Pass pre-_WorldBegin_ entities to _scene_
scene->SetOptions(filter, film, camera, sampler, integrator, accelerator);
}
WorldBegin之后,将currentBlock = BlockState::WorldBlock;
;并且将graphicsState.ctm
设置为默认矩阵。
进入BasicScene::SetOptions
scene->SetOptions(filter, film, camera, sampler, integrator, accelerator);
内将创建全局实例。
void BasicScene::SetOptions(SceneEntity filter, SceneEntity film,
CameraSceneEntity camera, SceneEntity sampler,
SceneEntity integ, SceneEntity accel) {
// Store information for specified integrator and accelerator
// 存储指定积分器和加速器的信息
filmColorSpace = film.parameters.ColorSpace();
integrator = integ;
accelerator = accel;
首先,赋值胶片的颜色空间,积分器,和加速器.
已知film.parameters.ColorSpace()
传值为默认值sRGB:RGBColorSpace::sRGB
。加速器和积分器为默认值。
// Immediately create filter and film
// 立即创建滤镜和胶片
LOG_VERBOSE("Starting to create filter and film");
Allocator alloc = threadAllocators.Get();
Filter filt = Filter::Create(filter.name, filter.parameters, &filter.loc, alloc);
Create函数才是真正的对象构造。
Filter Filter::Create(const std::string &name, const ParameterDictionary ¶meters,
const FileLoc *loc, Allocator alloc) {
Filter filter = nullptr;
if (name == "box")
filter = BoxFilter::Create(parameters, loc, alloc);
else if (name == "gaussian")
filter = GaussianFilter::Create(parameters, loc, alloc);
else if (name == "mitchell")
filter = MitchellFilter::Create(parameters, loc, alloc);
else if (name == "sinc")
filter = LanczosSincFilter::Create(parameters, loc, alloc);
else if (name == "triangle")
filter = TriangleFilter::Create(parameters, loc, alloc);
else
ErrorExit(loc, "%s: filter type unknown.", name);
if (!filter)
ErrorExit(loc, "%s: unable to create filter.", name);
parameters.ReportUnused();
return filter;
}
在BaicSceneBuilder的构造函数中设置了默认值filter.name = SceneEntity::internedStrings.Lookup("gaussian");
,进入:
else if (name == "gaussian")
filter = GaussianFilter::Create(parameters, loc, alloc);
有
GaussianFilter *GaussianFilter::Create(const ParameterDictionary ¶meters,
const FileLoc *loc, Allocator alloc) {
// Find common filter parameters
Float xw = parameters.GetOneFloat("xradius", 1.5f);
Float yw = parameters.GetOneFloat("yradius", 1.5f);
Float sigma = parameters.GetOneFloat("sigma", 0.5f); // equivalent to old alpha = 2
return alloc.new_object<GaussianFilter>(Vector2f(xw, yw), sigma, alloc);
}
返回GaussianFilter类实例,并保存在局部变量Filter filt中。
之后设置相机曝光时间
// It's a little ugly to poke into the camera's parameters here, but we
// have this circular dependency that Camera::Create() expects a
// Film, yet now the film needs to know the exposure time from
// the camera....
// 在这里插入相机的参数有点难看,但是我们有这个循环依赖,
// camera::Create()期望一个Film,但是现在胶片需要知道来自相机的曝光时间....
Float exposureTime = camera.parameters.GetOneFloat("shutterclose", 1.f) -
camera.parameters.GetOneFloat("shutteropen", 0.f);
if (exposureTime <= 0)
ErrorExit(&camera.loc,
"The specified camera shutter times imply that the shutter "
"does not open. A black image will result.");
再之后,创建Film实例
this->film = Film::Create(film.name, film.parameters, exposureTime,
camera.cameraTransform, filt, &film.loc, alloc);
Film Film::Create(const std::string &name, const ParameterDictionary ¶meters,
Float exposureTime, const CameraTransform &cameraTransform,
Filter filter, const FileLoc *loc, Allocator alloc) {
Film film;
if (name == "rgb")
film = RGBFilm::Create(parameters, exposureTime, filter, parameters.ColorSpace(),
loc, alloc);
else if (name == "gbuffer")
film = GBufferFilm::Create(parameters, exposureTime, cameraTransform, filter,
parameters.ColorSpace(), loc, alloc);
else if (name == "spectral")
film = SpectralFilm::Create(parameters, exposureTime, filter,
parameters.ColorSpace(), loc, alloc);
else
ErrorExit(loc, "%s: film type unknown.", name);
if (!film)
ErrorExit(loc, "%s: unable to create film.", name);
parameters.ReportUnused();
return film;
}
Film中默认名称为rgb,进入
if (name == "rgb")
film = RGBFilm::Create(parameters, exposureTime, filter, parameters.ColorSpace(),
loc, alloc);
有
RGBFilm *RGBFilm::Create(const ParameterDictionary ¶meters, Float exposureTime,
Filter filter, const RGBColorSpace *colorSpace,
const FileLoc *loc, Allocator alloc) {
Float maxComponentValue = parameters.GetOneFloat("maxcomponentvalue", Infinity);
bool writeFP16 = parameters.GetOneBool("savefp16", true);
// 创建子类的传感器
PixelSensor *sensor =
PixelSensor::Create(parameters, colorSpace, exposureTime, loc, alloc);
// 构造结构体FilmBaseParameters
FilmBaseParameters filmBaseParameters(parameters, filter, sensor, loc);
// 创建实例RGBFilm
return alloc.new_object<RGBFilm>(filmBaseParameters, colorSpace, maxComponentValue,
writeFP16, alloc);
}
其中创建了传感器,并以此传感器构建结构体,再传递给RGBFilm用于构建。
之后两个RunAsync函数,异步创建采样器和摄像机。
// Enqueue asynchronous job to create sampler
// 排队异步作业以创建采样器
samplerJob = RunAsync([sampler, this]() {
LOG_VERBOSE("Starting to create sampler");
Allocator alloc = threadAllocators.Get();
Point2i res = this->film.FullResolution();
return Sampler::Create(sampler.name, sampler.parameters, res, &sampler.loc,
alloc);
});
// Enqueue asynchronous job to create camera
cameraJob = RunAsync([camera, this]() {
LOG_VERBOSE("Starting to create camera");
Allocator alloc = threadAllocators.Get();
Medium cameraMedium = GetMedium(camera.medium, &camera.loc);
Camera c = Camera::Create(camera.name, camera.parameters, cameraMedium,
camera.cameraTransform, this->film, &camera.loc, alloc);
LOG_VERBOSE("Finished creating camera");
return c;
});
采样器为halton类型
HaltonSampler *HaltonSampler::Create(const ParameterDictionary ¶meters,
Point2i fullResolution, const FileLoc *loc,
Allocator alloc) {
int nsamp = parameters.GetOneInt("pixelsamples", 16);
if (Options->pixelSamples)
nsamp = *Options->pixelSamples;
int seed = parameters.GetOneInt("seed", Options->seed);
if (Options->quickRender)
nsamp = 1;
其中采样数为int nsamp = parameters.GetOneInt("pixelsamples", 16);
,该函数指的是,如果没有找到"pixelsamples"
设置,则返回16.但是在文件中,我们有
Sampler "halton"
"integer pixelsamples" [ 256 ]
因此此处nsamp = 256
,继续往后
if (Options->pixelSamples)
nsamp = *Options->pixelSamples;
如果在命令行中设置了spp,则会复写 nsamp 数量。
Camera的创建同理
Camera c = Camera::Create(camera.name, camera.parameters, cameraMedium,
camera.cameraTransform, this->film, &camera.loc, alloc);
进入函数,创建"perspective"类型摄像机
综上
WorldBegin构建(Create)多个全局对象,包括摄像机,采样器,胶片,积分器,加速器,过滤器。每个对象的设置,要不在文件中有相关名称和参数,要不使用默认参数,部分参数设置可以通过命令行修改。
一旦WorldBegin语句被解析,采样器、相机、胶片、像素滤波器、加速器和积分器都被设置好了;它们随后不能更改。因此,当解析器调用BasicSceneBuilder的WorldBegin()方法时,每个对应的SceneEntity都可以传递给BasicScene。(此方法还对图形状态进行了一些维护,将CTM重置为单位变换并处理其他细节。)
2.2.2 层级图形状态
当指定场景时,能够对图形状态进行一组更改是很有用的,可以实例化一些场景对象,然后回滚到之前的图形状态。例如,我们可能希望指定一个基本变换来定位场景中的汽车模型,然后使用相对于初始变换的其他变换来放置车轮、座椅等。一种方便的方法是通过保存GraphicsState对象的栈来实现这一点:用户可以指定当前图形状态应该被复制并将其压入栈中,然后指定将当前状态替换为栈顶状态。
这个栈是由pbrt的场景描述文件中的AttributeBegin和AttributeEnd语句管理的。 前者保存当前图形状态,后者恢复最近保存的状态。 因此,当前场景killeroo-simple.pbrt
描述文件包含以下内容:
AttributeBegin
Material "diffuse"
"rgb reflectance" [ 0 0 0 ]
Translate 150 0 20
Translate 0 120 0
AreaLightSource "diffuse"
"rgb L" [ 2000 2000 2000 ]
Shape "sphere"
"float radius" [ 3 ]
AttributeEnd
(1)AttributeBegin AttributeEnd
一个压栈一个出栈,放入信息graphicsState。
void BasicSceneBuilder::AttributeBegin(FileLoc loc) {
VERIFY_WORLD("AttributeBegin");
pushedGraphicsStates.push_back(graphicsState);
pushStack.push_back(std::make_pair('a', loc));
}
void BasicSceneBuilder::AttributeEnd(FileLoc loc) {
VERIFY_WORLD("AttributeEnd");
// Issue error on unmatched _AttributeEnd_
if (pushedGraphicsStates.empty()) {
Error(&loc, "Unmatched AttributeEnd encountered. Ignoring it.");
return;
}
// NOTE: must keep the following consistent with code in ObjectEnd
graphicsState = std::move(pushedGraphicsStates.back());
pushedGraphicsStates.pop_back();
if (pushStack.back().first == 'o')
ErrorExitDeferred(&loc,
"Mismatched nesting: open ObjectBegin from %s at AttributeEnd",
pushStack.back().second);
else
CHECK_EQ(pushStack.back().first, 'a');
pushStack.pop_back();
}
(2)Material
Material "diffuse"
"rgb reflectance" [ 0.5 0.5 0.8 ]
void BasicSceneBuilder::Material(const std::string &name, ParsedParameterVector params,
FileLoc loc)
{
ParameterDictionary dict(std::move(params), graphicsState.materialAttributes,
graphicsState.colorSpace);
graphicsState.currentMaterialIndex =
scene->AddMaterial(SceneEntity(name, std::move(dict), loc));
graphicsState.currentMaterialName.clear();
}
AddMaterial函数加载NormalMap,将当前材质入栈,并返回材质下标
int BasicScene::AddMaterial(SceneEntity material) {
std::lock_guard<std::mutex> lock(materialMutex);
startLoadingNormalMaps(material.parameters);
materials.push_back(std::move(material));
return int(materials.size() - 1);
}
(3)Translate
Translate 150 0 20
void BasicSceneBuilder::Translate(Float dx, Float dy, Float dz, FileLoc loc) {
graphicsState.ForActiveTransforms(
[=](auto t) { return t * pbrt::Translate(Vector3f(dx, dy, dz)); });
}
结果
ActiveTransforms0 =
[ [ 1, 0, 0, 150 ],
[ 0, 1, 0, 0 ],
[ 0, 0, 1, 20 ],
[ 0, 0, 0, 1 ] ]
ActiveTransforms1 =
[ [ 1, 0, 0, 150 ],
[ 0, 1, 0, 0 ],
[ 0, 0, 1, 20 ],
[ 0, 0, 0, 1 ] ]
之后
Translate 0 120 0
有
ActiveTransforms0 =
[ [ 1, 0, 0, 150 ],
[ 0, 1, 0, 120 ],
[ 0, 0, 1, 20 ],
[ 0, 0, 0, 1 ] ]
ActiveTransforms1 =
[ [ 1, 0, 0, 150 ],
[ 0, 1, 0, 120 ],
[ 0, 0, 1, 20 ],
[ 0, 0, 0, 1 ] ]
(4)AreaLightSource
可以看出,该物体是光源。此处跳过解析。
AreaLightSource "diffuse"
"rgb L" [ 2000 2000 2000 ]
Shape "sphere"
"float radius" [ 3 ]
我们看下一个物体
(5)Shape “trianglemesh”
可以看出如下是两个平面,他们材质相同位置不同
AttributeBegin
Material "diffuse"
"rgb reflectance" [ 0.5 0.5 0.8 ]
Translate 0 0 -140
Shape "trianglemesh"
"point2 uv" [ 0 0 5 0 5 5 0 5
]
"integer indices" [ 0 1 2 2 3 0 ]
"point3 P" [ -1000 -1000 0 1000 -1000 0 1000 1000 0 -1000 1000 0 ]
Shape "trianglemesh"
"point2 uv" [ 0 0 5 0 5 5 0 5
]
"integer indices" [ 0 1 2 2 3 0 ]
"point3 P" [ -400 -1000 -1000 -400 1000 -1000 -400 1000 1000 -400 -1000 1000 ]
AttributeEnd
(5)Include
之后的物体才是两个小龙
AttributeBegin
Scale 0.5 0.5 0.5
Rotate -60 0 0 1
Material "coateddiffuse"
"float roughness" [ 0.025 ]
"rgb reflectance" [ 0.4 0.2 0.2 ]
Translate 100 200 -140
Include "geometry/killeroo.pbrt"
Material "coateddiffuse"
"float roughness" [ 0.15 ]
"rgb reflectance" [ 0.4 0.5 0.4 ]
Translate -200 0 0
Include "geometry/killeroo.pbrt"
AttributeEnd
Include打开新文件,继续解析。
else if (tok->token == "Include")
{
Token filenameToken = *nextToken(TokenRequired);
std::string filename = toString(dequoteString(filenameToken));
if (formatting)
。。。
else {
filename = ResolveFilename(filename);
std::unique_ptr<Tokenizer> tinc =
Tokenizer::CreateFromFile(filename, parseError);
if (tinc) {
LOG_VERBOSE("Started parsing %s",
std::string(tinc->loc.filename.begin(),
tinc->loc.filename.end()));
fileStack.push_back(std::move(tinc));
}
}
}
(6)killeroo.pbrt
Shape "loopsubdiv"
"integer levels" [ 1 ]
"point3 P" [ ... ]
"integer indices" [ ... ]