// main program
int main(int argc, char *argv[]) {
    Options options;
    vector<string> filenames;
    // Process command-line arguments
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "--ncores")) options.nCores = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--outfile")) options.imageFile = argv[++i];
        else if (!strcmp(argv[i], "--quick")) options.quickRender = true;
        else if (!strcmp(argv[i], "--topng")){ options.topng = true;
        options.pngFile = argv[++i];
        }
        else if (!strcmp(argv[i], "--background")){ options.withBackground = true;
        options.backgroundImage = argv[++i];
        //This is neccesary to prevent exceptions caused by Magick++
        //@TODO: Mail the Magick++ list about this.
        options.nCores = 1;
        }
        else if (!strcmp(argv[i], "--quiet")) options.quiet = false;
        else if (!strcmp(argv[i], "--verbose")) options.verbose = true;
        else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
            printf("usage: pbrt [--ncores n] [--outfile filename] [--quick] [--quiet] "
                   "[--verbose] [--topng png_filename] [--help] <filename.pbrt> ...\n");
            return 0;
        }
        else filenames.push_back(argv[i]);
    }

    // Print welcome banner
    // Disabled, enable later.
    if (!options.quiet) {
        printf("pbrt version %s of %s at %s [Detected %d core(s)]\n",
               PBRT_VERSION, __DATE__, __TIME__, NumSystemCores());
        printf("Relativistic Rendering, Alex, Allwin, Sanchit\n");
        /*
        printf("Copyright (c)1998-2010 Matt Pharr and Greg Humphreys.\n");
        printf("The source code to pbrt (but *not* the book contents) is covered by the GNU GPL.\n");
        printf("See the file COPYING.txt for the conditions of the license.\n");
        */
        fflush(stdout);

    }
    pbrtInit(options);

    //extern Background bground;
    // Process scene description
    PBRT_STARTED_PARSING();
    if (filenames.size() == 0) {
        // Parse scene from standard input
        ParseFile("-");
    } else {
        // Parse scene from input files
        for (u_int i = 0; i < filenames.size(); i++)
            if (!ParseFile(filenames[i]))
                Error("Couldn't open scene file \"%s\"", filenames[i].c_str());
    }
    pbrtCleanup();
    return 0;
}
Exemple #2
0
TEST_P(RenderTest, RadianceMatches) {
  Options options;
  options.quiet = true;
  pbrtInit(options);

  const TestIntegrator &tr = GetParam();
  tr.integrator->Render(*tr.scene.scene);
  CheckSceneAverage("test.exr", tr.scene.expected);

  pbrtCleanup();
}
Exemple #3
0
// main program
int main(int argc, char *argv[]) {
    Options options;
    vector<string> filenames;

    /************* TWEAKS *************/

    if(chdir("source_files/pbrt"));

    argc = 4;
    argv[1] = "--ncores";
    argv[2] = "1";
    argv[3] = "cornell-ML.pbrt";
    /***********************************/

    // Process command-line arguments
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "--ncores")) options.nCores = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--outfile")) options.imageFile = argv[++i];
        else if (!strcmp(argv[i], "--quick")) options.quickRender = true;
        else if (!strcmp(argv[i], "--quiet")) options.quiet = true;
        else if (!strcmp(argv[i], "--verbose")) options.verbose = true;
        else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
            printf("usage: pbrt [--ncores n] [--outfile filename] [--quick] [--quiet] "
                   "[--verbose] [--help] <filename.pbrt> ...\n");
            return 0;
        }
        else filenames.push_back(argv[i]);
    }

    // Print welcome banner
    if (!options.quiet) {
        printf("pbrt version %s of %s at %s [Detected %d core(s)]\n",
               PBRT_VERSION, __DATE__, __TIME__, NumSystemCores());
        printf("Copyright (c)1998-2014 Matt Pharr and Greg Humphreys.\n");
        printf("The source code to pbrt (but *not* the book contents) is covered by the BSD License.\n");
        printf("See the file LICENSE.txt for the conditions of the license.\n");
        fflush(stdout);
    }
    pbrtInit(options);
    // Process scene description
    PBRT_STARTED_PARSING();
    if (filenames.size() == 0) {
        // Parse scene from standard input
        ParseFile("-");
    } else {
        // Parse scene from input files
        for (u_int i = 0; i < filenames.size(); i++)
            if (!ParseFile(filenames[i]))
                Error("Couldn't open scene file \"%s\"", filenames[i].c_str());
    }
    pbrtCleanup();
    return 0;
}
Exemple #4
0
// main program
int main(int argc, char *argv[]) {
    Options options;
    std::vector<std::string> filenames;
    // Process command-line arguments
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "--ncores") || !strcmp(argv[i], "--nthreads"))
            options.nThreads = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--outfile"))
            options.imageFile = argv[++i];
        else if (!strcmp(argv[i], "--quick"))
            options.quickRender = true;
        else if (!strcmp(argv[i], "--quiet"))
            options.quiet = true;
        else if (!strcmp(argv[i], "--verbose"))
            options.verbose = true;
        else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
            printf(
                "usage: pbrt [--nthreads n] [--outfile filename] [--quick] "
                "[--quiet] "
                "[--verbose] [--help] <filename.pbrt> ...\n");
            return 0;
        } else
            filenames.push_back(argv[i]);
    }

    // Print welcome banner
    if (!options.quiet) {
        printf("pbrt version 3 (built %s at %s) [Detected %d cores]\n",
               __DATE__, __TIME__, NumSystemCores());
        printf(
            "Copyright (c)1998-2015 Matt Pharr, Greg Humphreys, and Wenzel "
            "Jakob.\n");
        printf(
            "The source code to pbrt (but *not* the book contents) is covered "
            "by the BSD License.\n");
        printf("See the file LICENSE.txt for the conditions of the license.\n");
        fflush(stdout);
    }
    pbrtInit(options);
    // Process scene description
    if (filenames.size() == 0) {
        // Parse scene from standard input
        ParseFile("-");
    } else {
        // Parse scene from input files
        for (const std::string &f : filenames)
            if (!ParseFile(f))
                Error("Couldn't open scene file \"%s\"", f.c_str());
    }
    pbrtCleanup();
    return 0;
}
Exemple #5
0
// main program
int main(int argc, char *argv[]) {
	// Print welcome banner
	printf("pbrt version %1.3f of %s at %s\n",
	       PBRT_VERSION, __DATE__, __TIME__);
	printf("Copyright (c)1998-2010 Matt Pharr and "
	       "Greg Humphreys.\n");
	printf("The source code to pbrt (but *not* the contents of the book) is\n");
	printf("covered by the GNU General Public License.  See the file COPYING.txt\n");
	printf("for the conditions of the license.\n");
	fflush(stdout);
	pbrtInit();
	// Process scene description
	if (argc == 1) {
		// Parse scene from standard input
		ParseFile("-");
	} else {
		// Parse scene from input files
		for (int i = 1; i < argc; i++)
			if (!ParseFile(argv[i]))
				Error("Couldn't open scene file \"%s\"\n", argv[i]);
	}
	pbrtCleanup();
	return 0;
}
Exemple #6
0
int main(int argc, char *argv[]) 
{
    pbrtInit();

    // number of monte carlo estimates
    //const int estimates = 1;
    const int estimates = 10000000;

    // radiance of uniform environment map
    const double environmentRadiance = 1.0;


    fprintf(stderr, "outgoing radiance from a surface viewed\n"
        "straight on with uniform lighting\n\n"
        "    uniform incoming radiance = %.3f\n"
        "    monte carlo samples = %d\n\n\n",
        environmentRadiance, estimates);


    CreateBSDFFunc BSDFFuncArray[] = {
//CO        createBlinn0,
//CO        createBlinn05,
//CO        createBlinn2,
//CO        createBlinn30and0,
        createAniso0_0,
        createAniso30_30,
        createLambertian,
        createOrenNayar0,
        createOrenNayar20,
        createFresnelBlend0,
        createFresnelBlend30,
        createPlastic,
        createSubstrate,
    };

    const char* BSDFFuncDescripArray[] = {
//CO        "Blinn (exponent 0)",
//CO        "Blinn (exponent 0.5)",
//CO        "Blinn (exponent 2)",
//CO        "Blinn (exponent 30 and 0)",
        "Anisotropic (exponent 0, 0)",
        "Anisotropic (exponent 30, 30)",
        "Lambertian",
        "Oren Nayar (sigma 0)",
        "Oren Nayar (sigma 20)",
        "FresnelBlend (Blinn exponent 0)",
        "FresnelBlend (Blinn exponent 30)",
        "Plastic",
        "Substrate",
    };

    GenSampleFunc SampleFuncArray[] = {
        Gen_Sample_f,
        Gen_CosHemisphere,
        Gen_UniformHemisphere,
    };

    const char* SampleFuncDescripArray[] = {
        "BSDF Importance Sampling",
        "Cos Hemisphere",
        "Uniform Hemisphere",
    };

    int numModels = sizeof(BSDFFuncArray) / sizeof(BSDFFuncArray[0]);
    int numModelsDescrip = sizeof(BSDFFuncDescripArray) /
        sizeof(BSDFFuncDescripArray[0]);
    int numGenerators = sizeof(SampleFuncArray) / sizeof(SampleFuncArray[0]);
    int numGeneratorsDescrip = sizeof(SampleFuncDescripArray) /
        sizeof(SampleFuncDescripArray[0]);

    if (numModels != numModelsDescrip) {
        fprintf(stderr, "BSDFFuncArray and BSDFFuncDescripArray out of sync!\n");
        exit(1);
    }

    if (numGenerators != numGeneratorsDescrip) {
        fprintf(stderr, "SampleFuncArray and SampleFuncDescripArray out of sync!\n");
        exit(1);
    }

    // for each bsdf model
    for (int model = 0; model < numModels; model++) {

        BSDF* bsdf;

        // create BSDF which requires creating a Shape, casting a Ray
        // that hits the shape to get a DifferentialGeometry object,
        // and passing the DifferentialGeometry object into the BSDF
        {
            Transform t = RotateX(-90);
            bool reverseOrientation = false;
            ParamSet p;

            Reference<Shape> disk = new Disk(new Transform(t), new Transform(Inverse(t)),
                                             reverseOrientation, 0., 1., 0, 360.);
            if (!disk) {
                fprintf(stderr, "Could not load disk plugin\n"
                    "  make sure the PBRT_SEARCHPATH environment variable is set\n");
                exit(1);
            }

            Point origin(0.1, 1, 0);  // offset slightly so we don't hit center of disk
            Vector direction(0, -1, 0);
            float tHit, rayEps;
            Ray r(origin, direction, 1e-3, INFINITY);
            DifferentialGeometry* dg = BSDF_ALLOC(arena, DifferentialGeometry)();
            disk->Intersect(r, &tHit, &rayEps, dg);

            bsdf = BSDF_ALLOC(arena, BSDF)(*dg, dg->nn);
            (BSDFFuncArray[model])(bsdf);
        }


        // facing directly at normal
        Vector woL = Normalize(Vector(0, 0, 1));
        Vector wo = bsdf->LocalToWorld(woL);
        const Normal &n = bsdf->dgShading.nn;

        // for each method of generating samples over the hemisphere
        for (int gen = 0; gen < numGenerators; gen++) {
            double redSum = 0.0;

            const int numHistoBins = 10;
            double histogram[numHistoBins][numHistoBins];
            for (int i = 0; i < numHistoBins; i++) {
                for (int j = 0; j < numHistoBins; j++) {
                    histogram[i][j] = 0;
                }
            }
            int badSamples = 0;
            int outsideSamples = 0;

            int warningTarget = 1;
            for (int sample = 0; sample < estimates; sample++) {
                Vector wi;
                float pdf;
                Spectrum f;

                // sample hemisphere around bsdf, wo is fixed
                (SampleFuncArray[gen])(bsdf, wo, & wi, & pdf, & f);

                double redF = spectrumRedValue(f);

                // add hemisphere sample to histogram
                Vector wiL = bsdf->WorldToLocal(wi);
                float x = Clamp(wiL.x, -1.f, 1.f);
                float y = Clamp(wiL.y, -1.f, 1.f);
                float wiPhi = (atan2(y, x) + M_PI) / (2.0 * M_PI);
                float wiCosTheta = wiL.z;
                bool validSample = (wiCosTheta > 1e-7);
                if (wiPhi < -0.0001 || wiPhi > 1.0001 || wiCosTheta > 1.0001) {
                    // wiCosTheta can be less than 0
                    fprintf(stderr, "bad wi! %.3f %.3f %.3f, (%.3f %.3f)\n",
                        wiL[0], wiL[1], wiL[2], wiPhi, wiCosTheta);
                } else if (validSample) {
                    int histoPhi      = (int) (wiPhi * numHistoBins);
                    int histoCosTheta = (int) (wiCosTheta * numHistoBins);
                    histogram[histoCosTheta][histoPhi] += 1.0 / pdf;
                }

                if (!validSample) {
                    outsideSamples++;
                } else if (pdf == 0.f || isnan(pdf) || redF < 0 || isnan(redF)) {
                    if (badSamples == warningTarget) {
                        fprintf(stderr, "warning %d, bad sample %d! "
                            "pdf: %.3f, redF: %.3f\n",
                            warningTarget, sample, pdf, redF);
                        warningTarget *= 10;
                    }
                    badSamples++;
                } else {
                    // outgoing radiance estimate =
                    //   bsdf * incomingRadiance * cos(wi) / pdf
                    redSum += redF * environmentRadiance * AbsDot(wi, n) / pdf;
                }
            }
            int goodSamples = estimates - badSamples;

            // print results
            fprintf(stderr, "*** BRDF: '%s', Samples: '%s'\n\n"
                "wi histogram showing the relative weight in each bin\n"
                "  all entries should be close to 2pi = %.5f:\n"
                "  (%d bad samples, %d outside samples)\n\n"
                "                          cos(theta) bins\n",
                BSDFFuncDescripArray[model], SampleFuncDescripArray[gen],
                M_PI * 2.0, badSamples, outsideSamples);
            double totalSum = 0.0;
            for (int i = 0; i < numHistoBins; i++) {
                fprintf(stderr, "  phi bin %02d:", i);
                for (int j = 0; j < numHistoBins; j++) {
                    fprintf(stderr, " %5.2f", histogram[i][j] *
                        numHistoBins * numHistoBins / goodSamples);
                    totalSum += histogram[i][j];
                }
                fprintf(stderr, "\n");
            }
            fprintf(stderr, "\n  final average :  %.5f (error %.5f)\n\n"
                "  radiance = %.5f\n\n",
                totalSum / goodSamples, totalSum / goodSamples - M_PI * 2.0,
                redSum / goodSamples);
        }
    }
    
    pbrtCleanup();
    return 0;
}
Exemple #7
0
void TestBSDF(void (*createBSDF)(BSDF*, MemoryArena&),
              const char* description) {
    MemoryArena arena;

    Options opt;
    pbrtInit(opt);

    const int thetaRes = CHI2_THETA_RES;
    const int phiRes = CHI2_PHI_RES;
    const int sampleCount = CHI2_SAMPLECOUNT;
    Float* frequencies = new Float[thetaRes * phiRes];
    Float* expFrequencies = new Float[thetaRes * phiRes];
    RNG rng;

    int index = 0;
    std::cout.precision(3);

    // Create BSDF, which requires creating a Shape, casting a Ray that
    // hits the shape to get a SurfaceInteraction object.
    BSDF* bsdf = nullptr;
    Transform t = RotateX(-90);
    Transform tInv = Inverse(t);
    {
        bool reverseOrientation = false;
        ParamSet p;

        std::shared_ptr<Shape> disk(
            new Disk(&t, &tInv, reverseOrientation, 0., 1., 0, 360.));
        Point3f origin(0.1, 1,
                       0);  // offset slightly so we don't hit center of disk
        Vector3f direction(0, -1, 0);
        Float tHit;
        Ray r(origin, direction);
        SurfaceInteraction isect;
        disk->Intersect(r, &tHit, &isect);
        bsdf = ARENA_ALLOC(arena, BSDF)(isect);
        createBSDF(bsdf, arena);
    }

    for (int k = 0; k < CHI2_RUNS; ++k) {
        /* Randomly pick an outgoing direction on the hemisphere */
        Point2f sample {rng.UniformFloat(), rng.UniformFloat()};
        Vector3f woL = CosineSampleHemisphere(sample);
        Vector3f wo = bsdf->LocalToWorld(woL);

        FrequencyTable(bsdf, wo, rng, sampleCount, thetaRes, phiRes,
                       frequencies);

        IntegrateFrequencyTable(bsdf, wo, sampleCount, thetaRes, phiRes,
                                expFrequencies);

        std::string filename = StringPrintf("/tmp/chi2test_%s_%03i.m",
                                            description, ++index);
        DumpTables(frequencies, expFrequencies, thetaRes, phiRes,
                   filename.c_str());

        auto result =
            Chi2Test(frequencies, expFrequencies, thetaRes, phiRes, sampleCount,
                     CHI2_MINFREQ, CHI2_SLEVEL, CHI2_RUNS);
        EXPECT_TRUE(result.first) << result.second << ", iteration " << k;
    }

    delete[] frequencies;
    delete[] expFrequencies;

    pbrtCleanup();
}
Exemple #8
0
int main(int argc, char* argv[]) {
    Options opt;
    pbrtInit(opt);

    // number of monte carlo estimates
    // const int estimates = 1;
    const int estimates = 10000000;

    // radiance of uniform environment map
    const double environmentRadiance = 1.0;

    fprintf(stderr,
            "outgoing radiance from a surface viewed\n"
            "straight on with uniform lighting\n\n"
            "    uniform incoming radiance = %.3f\n"
            "    monte carlo samples = %d\n\n\n",
            environmentRadiance, estimates);

    CreateBSDFFunc BSDFFuncArray[] = {
        createLambertian,
        createOrenNayar0,
        createOrenNayar20,
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, true, 0.5, 0.5); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, true, 0.5, 0.5); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, true, 0.2, 0.1); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, true, 0.2, 0.1); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, true, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, true, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, true, 0.33, 0.033); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, true, 0.33, 0.033); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, false, 0.5, 0.5); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, false, 0.5, 0.5); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, false, 0.2, 0.1); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, false, 0.2, 0.1); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, false, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, false, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, true, false, 0.33, 0.033); },
        [](BSDF* bsdf) -> void
        { createMicrofacet(bsdf, false, false, 0.33, 0.033); },
        [](BSDF* bsdf) -> void
        { createFresnelBlend(bsdf, true, true, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createFresnelBlend(bsdf, false, true, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createFresnelBlend(bsdf, true, false, 0.15, 0.25); },
        [](BSDF* bsdf) -> void
        { createFresnelBlend(bsdf, false, false, 0.15, 0.25); },
    };

    const char* BSDFFuncDescripArray[] = {
        "Lambertian",
        "Oren Nayar (sigma 0)",
        "Oren Nayar (sigma 20)",
        "Beckmann (roughness 0.5, sample visible mf area)",
        "Trowbridge-Reitz (roughness 0.5, sample visible mf area)",
        "Beckmann (roughness 0.2/0.1, sample visible mf area)",
        "Trowbridge-Reitz (roughness 0.2/0.1, sample visible mf area)",
        "Beckmann (roughness 0.15/0.25, sample visible mf area)",
        "Trowbridge-Reitz (roughness 0.15/0.25, sample visible mf area)",
        "Beckmann (roughness 0.33/0.033, sample visible mf area)",
        "Trowbridge-Reitz (roughness 0.33/0.033, sample visible mf area)",
        "Beckmann (roughness 0.5, traditional sample wh)",
        "Trowbridge-Reitz (roughness 0.5, traditional sample wh)",
        "Beckmann (roughness 0.2/0.1, traditional sample wh)",
        "Trowbridge-Reitz (roughness 0.2/0.1, traditional sample wh)",
        "Beckmann (roughness 0.15/0.25, traditional sample wh)",
        "Trowbridge-Reitz (roughness 0.15/0.25, traditional sample wh)",
        "Beckmann (roughness 0.33/0.033, traditional sample wh)",
        "Trowbridge-Reitz (roughness 0.33/0.033, traditional sample wh)",
        "Fresnel Blend Beckmann (roughness 0.15/0.25, sample visible mf area)",
        "Fresnel Blend Trowbridge-Reitz (roughness 0.15/0.25, sample visible mf area)",
        "Fresnel Blend Beckmann (roughness 0.15/0.25, traditional sample wh)",
        "Fresnel Blend Trowbridge-Reitz (roughness 0.15/0.25, traditional sample wh)",
    };

    GenSampleFunc SampleFuncArray[] = {
        Gen_Sample_f,
        // CO        Gen_CosHemisphere,
        // CO        Gen_UniformHemisphere,
    };

    const char* SampleFuncDescripArray[] = {
        "BSDF Importance Sampling",
        // CO        "Cos Hemisphere",
        // CO        "Uniform Hemisphere",
    };

    int numModels = sizeof(BSDFFuncArray) / sizeof(BSDFFuncArray[0]);
    int numModelsDescrip =
        sizeof(BSDFFuncDescripArray) / sizeof(BSDFFuncDescripArray[0]);
    int numGenerators = sizeof(SampleFuncArray) / sizeof(SampleFuncArray[0]);
    int numGeneratorsDescrip =
        sizeof(SampleFuncDescripArray) / sizeof(SampleFuncDescripArray[0]);

    if (numModels != numModelsDescrip) {
        fprintf(stderr,
                "BSDFFuncArray and BSDFFuncDescripArray out of sync!\n");
        exit(1);
    }

    if (numGenerators != numGeneratorsDescrip) {
        fprintf(stderr,
                "SampleFuncArray and SampleFuncDescripArray out of sync!\n");
        exit(1);
    }

    // for each bsdf model
    for (int model = 0; model < numModels; model++) {
        BSDF* bsdf;

        // create BSDF which requires creating a Shape, casting a Ray
        // that hits the shape to get a SurfaceInteraction object.
        {
            Transform t = RotateX(-90);
            bool reverseOrientation = false;
            ParamSet p;

            std::shared_ptr<Shape> disk(
                new Disk(new Transform(t), new Transform(Inverse(t)),
                         reverseOrientation, 0., 1., 0, 360.));
            Point3f origin(
                0.1, 1, 0);  // offset slightly so we don't hit center of disk
            Vector3f direction(0, -1, 0);
            Float tHit;
            Ray r(origin, direction);
            SurfaceInteraction isect;
            disk->Intersect(r, &tHit, &isect);
            bsdf = ARENA_ALLOC(arena, BSDF)(isect);
            (BSDFFuncArray[model])(bsdf);
        }

        // facing directly at normal
        Vector3f woL = Normalize(Vector3f(0, 0, 1));
        Vector3f wo = bsdf->LocalToWorld(woL);
        // was bsdf->dgShading.nn
        const Normal3f n = Normal3f(bsdf->LocalToWorld(Vector3f(0, 0, 1)));

        // for each method of generating samples over the hemisphere
        for (int gen = 0; gen < numGenerators; gen++) {
            double redSum = 0.0;

            const int numHistoBins = 10;
            double histogram[numHistoBins][numHistoBins];
            for (int i = 0; i < numHistoBins; i++) {
                for (int j = 0; j < numHistoBins; j++) {
                    histogram[i][j] = 0;
                }
            }
            int badSamples = 0;
            int outsideSamples = 0;

            int warningTarget = 1;
            for (int sample = 0; sample < estimates; sample++) {
                Vector3f wi;
                Float pdf;
                Spectrum f;

                // sample hemisphere around bsdf, wo is fixed
                (SampleFuncArray[gen])(bsdf, wo, &wi, &pdf, &f);

                double redF = spectrumRedValue(f);

                // add hemisphere sample to histogram
                Vector3f wiL = bsdf->WorldToLocal(wi);
                float x = Clamp(wiL.x, -1.f, 1.f);
                float y = Clamp(wiL.y, -1.f, 1.f);
                float wiPhi = (atan2(y, x) + Pi) / (2.0 * Pi);
                float wiCosTheta = wiL.z;
                bool validSample = (wiCosTheta > 1e-7);
                if (wiPhi < -0.0001 || wiPhi > 1.0001 || wiCosTheta > 1.0001) {
                    // wiCosTheta can be less than 0
                    fprintf(stderr, "bad wi! %.3f %.3f %.3f, (%.3f %.3f)\n",
                            wiL[0], wiL[1], wiL[2], wiPhi, wiCosTheta);
                } else if (validSample) {
                    int histoPhi = (int)(wiPhi * numHistoBins);
                    if (histoPhi == numHistoBins)
                      --histoPhi;
                    int histoCosTheta = (int)(wiCosTheta * numHistoBins);
                    if (histoCosTheta == numHistoBins)
                      --histoCosTheta;
                    assert(histoPhi >= 0 && histoPhi < numHistoBins);
                    assert(histoCosTheta >= 0 && histoCosTheta < numHistoBins);
                    histogram[histoCosTheta][histoPhi] += 1.0 / pdf;
                }

                if (!validSample) {
                    outsideSamples++;
                } else if (pdf == 0.f || std::isnan(pdf) || redF < 0 ||
                           std::isnan(redF)) {
                    if (badSamples == warningTarget) {
                        fprintf(stderr,
                                "warning %d, bad sample %d! "
                                "pdf: %.3f, redF: %.3f\n",
                                warningTarget, sample, pdf, redF);
                        warningTarget *= 10;
                    }
                    badSamples++;
                } else {
                    // outgoing radiance estimate =
                    //   bsdf * incomingRadiance * cos(wi) / pdf
                    redSum += redF * environmentRadiance * AbsDot(wi, n) / pdf;
                }
            }
            int goodSamples = estimates - badSamples;

            // print results
            fprintf(stderr,
                    "*** BRDF: '%s', Samples: '%s'\n\n"
                    "wi histogram showing the relative weight in each bin\n"
                    "  all entries should be close to 2pi = %.5f:\n"
                    "  (%d bad samples, %d outside samples)\n\n"
                    "                          phi bins\n",
                    BSDFFuncDescripArray[model], SampleFuncDescripArray[gen],
                    Pi * 2.0, badSamples, outsideSamples);
            double totalSum = 0.0;
            for (int i = 0; i < numHistoBins; i++) {
                fprintf(stderr, "  cos(theta) bin %02d:", i);
                for (int j = 0; j < numHistoBins; j++) {
                    fprintf(stderr, " %5.2f", histogram[i][j] * numHistoBins *
                                                  numHistoBins / goodSamples);
                    totalSum += histogram[i][j];
                }
                fprintf(stderr, "\n");
            }
            fprintf(stderr,
                    "\n  final average :  %.5f (error %.5f)\n\n"
                    "  radiance = %.5f\n\n",
                    totalSum / goodSamples, totalSum / goodSamples - Pi * 2.0,
                    redSum / goodSamples);
        }
    }

    pbrtCleanup();
    return 0;
}