【代码解读】2. 场景描述文件解析 - Physically Based Rendering From Theory To Implementation(PBRT)

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为FilmFilm没有双引号,因此使用此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 &parameters,
                      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 &parameters,
                                       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 &parameters,
                  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 &parameters, 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 &parameters,
                                     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" [ ... ]
    
python+opencv简谱识别音频生成系统源码含GUI界面+详细运行教程+数据 一、项目简介 提取简谱中的音乐信息,依据识别到的信息生成midi文件。 Extract music information from musical scores and generate a midi file according to it. 二、项目运行环境 python=3.11.1 第三方库依赖 opencv-python=4.7.0.68 numpy=1.24.1 可以使用命令 pip install -r requirements.txt 来安装所需的第三方库。 三、项目运行步骤 3.1 命令行运行 运行main.py。 输入简谱路径:支持图片或文件夹,相对路径或绝对路径都可以。 输入简谱主音:它通常在第一页的左上角“1=”之后。 输入简谱速度:即每分钟拍数,同在左上角。 选择是否输出程序中间提示信息:请输入Y或N(不区分大小写,下同)。 选择匹配精度:请输入L或M或H,对应低/中/高精度,一般而言输入L即可。 选择使用的线程数:一般与CPU核数相同即可。虽然python的线程不是真正的多线程,但仍能起到加速作用。 估算字符上下间距:这与简谱中符号的密集程度有关,一般来说纵向符号越稀疏,这个值需要设置得越大,范围通常在1.0-2.5。 二值化算法:使用全局阈值则跳过该选项即可,或者也可输入OTSU、采用大津二值化算法。 设置全局阈值:如果上面选择全局阈值则需要手动设置全局阈值,对于.\test.txt中所提样例,使用全局阈值并在后面设置为160即可。 手动调整中间结果:若输入Y/y,则在识别简谱后会暂停代码,并生成一份txt文件,在其中展示识别结果,此时用户可以通过修改这份txt文件来更正识别结果。 如果选择文件夹的话,还可以选择所选文件夹中不需要识别的文件以排除干扰
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Elsa的迷弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值