/// Generate a histogram of the BSDF density function via MC sampling void FrequencyTable(const BSDF* bsdf, const Vector3f& wo, RNG& rng, int sampleCount, int thetaRes, int phiRes, Float* target) { memset(target, 0, thetaRes * phiRes * sizeof(Float)); Float factorTheta = thetaRes / Pi, factorPhi = phiRes / (2 * Pi); BxDFType flags; Vector3f wi; Float pdf; for (int i = 0; i < sampleCount; ++i) { Point2f sample {rng.UniformFloat(), rng.UniformFloat()}; Spectrum f = bsdf->Sample_f(wo, &wi, sample, &pdf, BSDF_ALL, &flags); if (f == Spectrum() || (flags & BSDF_SPECULAR)) continue; Vector3f wiL = bsdf->WorldToLocal(wi); Point2f coords(std::acos(Clamp(wiL.z, -1, 1)) * factorTheta, std::atan2(wiL.y, wiL.x) * factorPhi); if (coords.y < 0) coords.y += 2 * Pi * factorPhi; int thetaBin = std::min(std::max(0, (int)std::floor(coords.x)), thetaRes - 1); int phiBin = std::min(std::max(0, (int)std::floor(coords.y)), phiRes - 1); target[thetaBin * phiRes + phiBin] += 1; } }
void Gen_Sample_f(BSDF* bsdf, const Vector3f& wo, Vector3f* wi, Float* pdf, Spectrum* f) { // only glossy or diffuse reflections (no specular reflections) BxDFType inflags = BxDFType(BSDF_REFLECTION | BSDF_DIFFUSE | BSDF_GLOSSY); BxDFType outflags; Point2f sample(rng.UniformFloat(), rng.UniformFloat()); *f = bsdf->Sample_f(wo, wi, sample, pdf, inflags, &outflags); // double check bsdf->Pdf() gives us the same answer Vector3f wiL = bsdf->WorldToLocal(*wi); float wiCosTheta = wiL.z; bool validSample = (wiCosTheta > 1e-7); if (validSample) { float verifyPdf = bsdf->Pdf(wo, *wi, inflags); float relErr = fabs(verifyPdf - *pdf) / *pdf; if (relErr > 1e-3) { fprintf(stderr, "BSDF::Pdf() doesn't match BSDF::Sample_f() !\n" " Sample_f pdf %.3f, Pdf pdf %.3f (rel error %f)\n" " wo %.3f %.3f %.3f, wi %.3f %.3f %.3f\n", *pdf, verifyPdf, relErr, wo[0], wo[1], wo[2], (*wi)[0], (*wi)[1], (*wi)[2]); fprintf( stderr, "blah! validSample %d, wiCosTheta %.3f, wiL %.3f %.3f %.3f\n", validSample, wiCosTheta, wiL[0], wiL[1], wiL[2]); } } }
TEST(EXR, Randoms) { int width = 1024; int height = 1024; RNG rng; float *buf = new float[4 * width * height]; for (int i = 0; i < 4 * width * height; ++i) { buf[i] = -20 + 20. * rng.UniformFloat(); } EXRImage image; image.num_channels = 4; const char *channels[] = { "B", "G", "R", "A" }; image.channel_names = channels; unsigned char *images[] = { (unsigned char *)buf, (unsigned char *)(buf + width * height), (unsigned char *)(buf + 2 * width * height), (unsigned char *)(buf + 3 * width * height) }; image.images = images; int pixel_types[] = { TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF }; image.pixel_types = pixel_types; image.width = width; image.height = height; const char *err = nullptr; EXPECT_EQ(0, SaveMultiChannelEXRToFile(&image, "test.exr", &err)) << err; EXRImage readImage; EXPECT_EQ(0, LoadMultiChannelEXRFromFile(&readImage, "test.exr", &err)) << err; CompareImages(image, readImage, true); }
// Sampling Function Definitions void StratifiedSample1D(Float *samp, int nSamples, RNG &rng, bool jitter) { Float invNSamples = (Float)1 / nSamples; for (int i = 0; i < nSamples; ++i) { Float delta = jitter ? rng.UniformFloat() : 0.5f; samp[i] = std::min((i + delta) * invNSamples, OneMinusEpsilon); } }
Point2f RejectionSampleDisk(RNG &rng) { Point2f p; do { p.x = 1 - 2 * rng.UniformFloat(); p.y = 1 - 2 * rng.UniformFloat(); } while (p.x * p.x + p.y * p.y > 1); return p; }
void RealisticCamera::TestExitPupilBounds() const { Float filmDiagonal = film->diagonal; static RNG rng; Float u = rng.UniformFloat(); Point3f pFilm(u * filmDiagonal / 2, 0, 0); Float r = pFilm.x / (filmDiagonal / 2); int pupilIndex = std::min((int)exitPupilBounds.size() - 1, (int)std::floor(r * (exitPupilBounds.size() - 1))); Bounds2f pupilBounds = exitPupilBounds[pupilIndex]; if (pupilIndex + 1 < (int)exitPupilBounds.size()) pupilBounds = Union(pupilBounds, exitPupilBounds[pupilIndex + 1]); // Now, randomly pick points on the aperture and see if any are outside // of pupil bounds... for (int i = 0; i < 1000; ++i) { Point2f pd = ConcentricSampleDisk( Point2f(rng.UniformFloat(), rng.UniformFloat())); pd *= RearElementRadius(); Ray testRay(pFilm, Point3f(pd.x, pd.y, 0.f) - pFilm); Ray testOut; if (!TraceLensesFromFilm(testRay, &testOut)) continue; if (!Inside(pd, pupilBounds)) { fprintf(stderr, "Aha! (%f,%f) went through, but outside bounds (%f,%f) - " "(%f,%f)\n", pd.x, pd.y, pupilBounds.pMin[0], pupilBounds.pMin[1], pupilBounds.pMax[0], pupilBounds.pMax[1]); RenderExitPupil( (Float)pupilIndex / exitPupilBounds.size() * filmDiagonal / 2.f, 0.f, "low.exr"); RenderExitPupil((Float)(pupilIndex + 1) / exitPupilBounds.size() * filmDiagonal / 2.f, 0.f, "high.exr"); RenderExitPupil(pFilm.x, 0.f, "mid.exr"); exit(0); } } fprintf(stderr, "."); }
void Gen_UniformHemisphere(BSDF* bsdf, const Vector3f& wo, Vector3f* wi, Float* pdf, Spectrum* f) { float u1 = rng.UniformFloat(); float u2 = rng.UniformFloat(); Vector3f wiL = UniformSampleHemisphere(Point2f(u1, u2)); *wi = bsdf->LocalToWorld(wiL); *pdf = UniformHemispherePdf(); *f = bsdf->f(wo, *wi); }
void StratifiedSample2D(Point2f *samp, int nx, int ny, RNG &rng, bool jitter) { Float dx = (Float)1 / nx, dy = (Float)1 / ny; for (int y = 0; y < ny; ++y) for (int x = 0; x < nx; ++x) { Float jx = jitter ? rng.UniformFloat() : 0.5f; Float jy = jitter ? rng.UniformFloat() : 0.5f; samp->x = std::min((x + jx) * dx, OneMinusEpsilon); samp->y = std::min((y + jy) * dy, OneMinusEpsilon); ++samp; } }
void Gen_CosHemisphere(BSDF* bsdf, const Vector3f& wo, Vector3f* wi, Float* pdf, Spectrum* f) { float u1 = rng.UniformFloat(); float u2 = rng.UniformFloat(); Vector3f wiL = CosineSampleHemisphere(Point2f(u1, u2)); *wi = bsdf->LocalToWorld(wiL); float cosTheta = wiL.z; *pdf = CosineHemispherePdf(cosTheta); *f = bsdf->f(wo, *wi); }
void LatinHypercube(Float *samples, int nSamples, int nDim, RNG &rng) { // Generate LHS samples along diagonal Float invNSamples = (Float)1 / nSamples; for (int i = 0; i < nSamples; ++i) for (int j = 0; j < nDim; ++j) { Float sj = (i + (rng.UniformFloat())) * invNSamples; samples[nDim * i + j] = std::min(sj, OneMinusEpsilon); } // Permute LHS samples in each dimension for (int i = 0; i < nDim; ++i) { for (int j = 0; j < nSamples; ++j) { int other = j + rng.UniformUInt32(nSamples - j); std::swap(samples[nDim * j + i], samples[nDim * other + i]); } } }
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(); }