// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. // The pbrt source code is licensed under the Apache License, Version 2.0. // SPDX: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace pbrt { // CameraTransform Method Definitions CameraTransform::CameraTransform(const AnimatedTransform &worldFromCamera) { switch (Options->renderingSpace) { case RenderingCoordinateSystem::Camera: { // Compute _worldFromRender_ for camera-space rendering Float tMid = (worldFromCamera.startTime + worldFromCamera.endTime) / 2; worldFromRender = worldFromCamera.Interpolate(tMid); break; } case RenderingCoordinateSystem::CameraWorld: { // Compute _worldFromRender_ for camera-world space rendering Float tMid = (worldFromCamera.startTime + worldFromCamera.endTime) / 2; Point3f pCamera = worldFromCamera(Point3f(0, 0, 0), tMid); worldFromRender = Translate(Vector3f(pCamera)); break; } case RenderingCoordinateSystem::World: { // Compute _worldFromRender_ for world-space rendering worldFromRender = Transform(); break; } default: LOG_FATAL("Unhandled rendering coordinate space"); } // Compute _renderFromCamera_ transformation Transform renderFromWorld = Inverse(worldFromRender); Transform rfc[2] = {renderFromWorld * worldFromCamera.startTransform, renderFromWorld * worldFromCamera.endTransform}; renderFromCamera = AnimatedTransform(rfc[0], worldFromCamera.startTime, rfc[1], worldFromCamera.endTime); } std::string CameraTransform::ToString() const { return StringPrintf("[ CameraTransform renderFromCamera: %s worldFromRender: %s ]", renderFromCamera, worldFromRender); } // Camera Method Definitions pstd::optional CameraHandle::GenerateRayDifferential( const CameraSample &sample, SampledWavelengths &lambda) const { auto gen = [&](auto ptr) { return ptr->GenerateRayDifferential(sample, lambda); }; return Dispatch(gen); } void CameraHandle::ApproximatedPdxy(const SurfaceInteraction &si) const { auto approx = [&](auto ptr) { return ptr->ApproximatedPdxy(si); }; return Dispatch(approx); } SampledSpectrum CameraHandle::We(const Ray &ray, SampledWavelengths &lambda, Point2f *pRaster2) const { auto we = [&](auto ptr) { return ptr->We(ray, lambda, pRaster2); }; return Dispatch(we); } void CameraHandle::PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const { auto pdf = [&](auto ptr) { return ptr->PDF_We(ray, pdfPos, pdfDir); }; return Dispatch(pdf); } pstd::optional CameraHandle::SampleWi(const Interaction &ref, const Point2f &u, SampledWavelengths &lambda) const { auto sample = [&](auto ptr) { return ptr->SampleWi(ref, u, lambda); }; return Dispatch(sample); } void CameraHandle::InitMetadata(ImageMetadata *metadata) const { auto init = [&](auto ptr) { return ptr->InitMetadata(metadata); }; return DispatchCPU(init); } std::string CameraHandle::ToString() const { if (ptr() == nullptr) return "(nullptr)"; auto ts = [&](auto ptr) { return ptr->ToString(); }; return DispatchCPU(ts); } // CameraBase Method Definitions CameraBase::CameraBase(const CameraTransform &cameraTransform, Float shutterOpen, Float shutterClose, FilmHandle film, MediumHandle medium) : cameraTransform(cameraTransform), shutterOpen(shutterOpen), shutterClose(shutterClose), film(film), medium(medium) { if (cameraTransform.CameraFromRenderHasScale()) Warning("Scaling detected in world-to-camera transformation!\n" "The system has numerous assumptions, implicit and explicit,\n" "that this transform will have no scale factors in it.\n" "Proceed at your own risk; your image may have errors or\n" "the system may crash as a result of this."); } pstd::optional CameraBase::GenerateRayDifferential( CameraHandle camera, const CameraSample &sample, SampledWavelengths &lambda) { // Find ray differential using differencing // Generate regular camera ray _cr_ for ray differential CameraRay cr = camera.GenerateRay(sample, lambda); if (!cr.weight) return {}; RayDifferential rd(cr.ray); // Find camera ray after shifting one pixel in the $x$ direction CameraRay rx; for (Float eps : {.05, -.05}) { CameraSample sshift = sample; sshift.pFilm.x += eps; // Try to generate ray with _sshift_ and compute $x$ differential if (rx = camera.GenerateRay(sshift, lambda); rx.weight) { rd.rxOrigin = rd.o + (rx.ray.o - rd.o) / eps; rd.rxDirection = rd.d + (rx.ray.d - rd.d) / eps; break; } } if (!rx.weight) return {}; // Find camera ray after shifting one pixel in the $y$ direction CameraRay ry; for (Float eps : {.05, -.05}) { CameraSample sshift = sample; sshift.pFilm.y += eps; if (ry = camera.GenerateRay(sshift, lambda); ry.weight) { rd.ryOrigin = rd.o + (ry.ray.o - rd.o) / eps; rd.ryDirection = rd.d + (ry.ray.d - rd.d) / eps; break; } } if (!ry.weight) return {}; rd.hasDifferentials = true; return CameraRayDifferential{rd, cr.weight}; } void CameraBase::ApproximatedPdxy(const SurfaceInteraction &si) const { Point3f pc = CameraFromRender(si.p(), si.time); Float dist = Distance(pc, Point3f(0, 0, 0)); Frame f = Frame::FromZ(si.n); // ray plane: // (0,0,0) + minPosDifferential + ((0,0,1) + minDirDifferantial)) * t = (x, // x, dist) Float tx = (dist - minPosDifferentialX.z) / (1 + minDirDifferentialX.z); // 0.5 factor to sharpen them up slightly (could be / should be based // on spp?) si.dpdx = .5f * f.FromLocal(minPosDifferentialX + tx * minDirDifferentialX); Float ty = (dist - minPosDifferentialY.z) / (1 + minDirDifferentialY.z); si.dpdy = .5f * f.FromLocal(minPosDifferentialY + ty * minDirDifferentialY); } void CameraBase::InitMetadata(ImageMetadata *metadata) const { metadata->cameraFromWorld = cameraTransform.CameraFromWorld(shutterOpen).GetMatrix(); } void CameraBase::FindMinimumDifferentials(CameraHandle camera) { minPosDifferentialX = minPosDifferentialY = minDirDifferentialX = minDirDifferentialY = Vector3f(Infinity, Infinity, Infinity); CameraSample sample; sample.pLens = Point2f(0.5, 0.5); sample.time = 0.5; SampledWavelengths lambda = SampledWavelengths::SampleXYZ(0.5); int n = 512; for (int i = 0; i < n; ++i) { sample.pFilm.x = Float(i) / (n - 1) * film.FullResolution().x; sample.pFilm.y = Float(i) / (n - 1) * film.FullResolution().y; pstd::optional crd = camera.GenerateRayDifferential(sample, lambda); if (!crd) continue; RayDifferential &ray = crd->ray; Vector3f dox = CameraFromRender(ray.rxOrigin - ray.o, ray.time); if (Length(dox) < Length(minPosDifferentialX)) minPosDifferentialX = dox; Vector3f doy = CameraFromRender(ray.ryOrigin - ray.o, ray.time); if (Length(doy) < Length(minPosDifferentialY)) minPosDifferentialY = doy; ray.d = Normalize(ray.d); ray.rxDirection = Normalize(ray.rxDirection); ray.ryDirection = Normalize(ray.ryDirection); Frame f = Frame::FromZ(ray.d); Vector3f df = f.ToLocal(ray.d); // should be (0, 0, 1); Vector3f dxf = Normalize(f.ToLocal(ray.rxDirection)); Vector3f dyf = Normalize(f.ToLocal(ray.ryDirection)); if (Length(dxf - df) < Length(minDirDifferentialX)) minDirDifferentialX = dxf - df; if (Length(dyf - df) < Length(minDirDifferentialY)) minDirDifferentialY = dyf - df; } LOG_VERBOSE("Camera min pos differentials: %s, %s", minPosDifferentialX, minPosDifferentialY); LOG_VERBOSE("Camera min dir differentials: %s, %s", minDirDifferentialX, minDirDifferentialY); } std::string CameraBase::ToString() const { return StringPrintf("cameraTransform: %s shutterOpen: %f shutterClose: %f film: %s " "medium: %s minPosDifferentialX: %s minPosDifferentialY: %s " "minDirDifferentialX: %s minDirDifferentialY: %s ", cameraTransform, shutterOpen, shutterClose, film, medium ? medium.ToString().c_str() : "(nullptr)", minPosDifferentialX, minPosDifferentialY, minDirDifferentialX, minDirDifferentialY); } std::string CameraSample::ToString() const { return StringPrintf("[ pFilm: %s pLens: %s time: %f weight: %f ]", pFilm, pLens, time, weight); } // ProjectiveCamera Method Definitions void ProjectiveCamera::InitMetadata(ImageMetadata *metadata) const { metadata->cameraFromWorld = cameraTransform.CameraFromWorld(shutterOpen).GetMatrix(); // TODO: double check this Transform NDCFromWorld = Translate(Vector3f(0.5, 0.5, 0.5)) * Scale(0.5, 0.5, 0.5) * screenFromCamera * *metadata->cameraFromWorld; metadata->NDCFromWorld = NDCFromWorld.GetMatrix(); CameraBase::InitMetadata(metadata); } std::string ProjectiveCamera::BaseToString() const { return CameraBase::ToString() + StringPrintf("screenFromCamera: %s cameraFromRaster: %s " "rasterFromScreen: %s screenFromRaster: %s " "lensRadius: %f focalDistance: %f", screenFromCamera, cameraFromRaster, rasterFromScreen, screenFromRaster, lensRadius, focalDistance); } CameraHandle CameraHandle::Create(const std::string &name, const ParameterDictionary ¶meters, MediumHandle medium, const CameraTransform &cameraTransform, FilmHandle film, const FileLoc *loc, Allocator alloc) { CameraHandle camera; if (name == "perspective") camera = PerspectiveCamera::Create(parameters, cameraTransform, film, medium, loc, alloc); else if (name == "orthographic") camera = OrthographicCamera::Create(parameters, cameraTransform, film, medium, loc, alloc); else if (name == "realistic") camera = RealisticCamera::Create(parameters, cameraTransform, film, medium, loc, alloc); else if (name == "spherical") camera = SphericalCamera::Create(parameters, cameraTransform, film, medium, loc, alloc); else ErrorExit(loc, "%s: camera type unknown.", name); if (!camera) ErrorExit(loc, "%s: unable to create camera.", name); parameters.ReportUnused(); return camera; } // OrthographicCamera Method Definitions CameraRay OrthographicCamera::GenerateRay(CameraSample sample, SampledWavelengths &lambda) const { // Compute raster and camera sample positions Point3f pFilm = Point3f(sample.pFilm.x, sample.pFilm.y, 0); Point3f pCamera = cameraFromRaster(pFilm); Ray ray(pCamera, Vector3f(0, 0, 1), SampleTime(sample.time), medium); // Modify ray for depth of field if (lensRadius > 0) { // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); // Compute point on plane of focus Float ft = focalDistance / ray.d.z; Point3f pFocus = ray(ft); // Update ray for effect of lens ray.o = Point3f(pLens.x, pLens.y, 0); ray.d = Normalize(pFocus - ray.o); } return CameraRay{RenderFromCamera(ray)}; } pstd::optional OrthographicCamera::GenerateRayDifferential( const CameraSample &sample, SampledWavelengths &lambda) const { // Compute main orthographic viewing ray // Compute raster and camera sample positions Point3f pFilm = Point3f(sample.pFilm.x, sample.pFilm.y, 0); Point3f pCamera = cameraFromRaster(pFilm); RayDifferential ray(pCamera, Vector3f(0, 0, 1), SampleTime(sample.time), medium); // Modify ray for depth of field if (lensRadius > 0) { // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); // Compute point on plane of focus Float ft = focalDistance / ray.d.z; Point3f pFocus = ray(ft); // Update ray for effect of lens ray.o = Point3f(pLens.x, pLens.y, 0); ray.d = Normalize(pFocus - ray.o); } // Compute ray differentials for _OrthographicCamera_ if (lensRadius > 0) { // Compute \use{OrthographicCamera} ray differentials accounting for lens // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); Float ft = focalDistance / ray.d.z; Point3f pFocus = pCamera + dxCamera + (ft * Vector3f(0, 0, 1)); ray.rxOrigin = Point3f(pLens.x, pLens.y, 0); ray.rxDirection = Normalize(pFocus - ray.rxOrigin); pFocus = pCamera + dyCamera + (ft * Vector3f(0, 0, 1)); ray.ryOrigin = Point3f(pLens.x, pLens.y, 0); ray.ryDirection = Normalize(pFocus - ray.ryOrigin); } else { ray.rxOrigin = ray.o + dxCamera; ray.ryOrigin = ray.o + dyCamera; ray.rxDirection = ray.ryDirection = ray.d; } ray.hasDifferentials = true; return CameraRayDifferential{RenderFromCamera(ray)}; } std::string OrthographicCamera::ToString() const { return StringPrintf("[ OrthographicCamera %s dxCamera: %s dyCamera: %s ]", BaseToString(), dxCamera, dyCamera); } OrthographicCamera *OrthographicCamera::Create(const ParameterDictionary ¶meters, const CameraTransform &cameraTransform, FilmHandle film, MediumHandle medium, const FileLoc *loc, Allocator alloc) { // Extract common camera parameters from _ParameterDictionary_ Float shutteropen = parameters.GetOneFloat("shutteropen", 0.f); Float shutterclose = parameters.GetOneFloat("shutterclose", 1.f); if (shutterclose < shutteropen) { Warning(loc, "Shutter close time %f < shutter open %f. Swapping them.", shutterclose, shutteropen); pstd::swap(shutterclose, shutteropen); } Float lensradius = parameters.GetOneFloat("lensradius", 0.f); Float focaldistance = parameters.GetOneFloat("focaldistance", 1e6f); Float frame = parameters.GetOneFloat("frameaspectratio", Float(film.FullResolution().x) / Float(film.FullResolution().y)); Bounds2f screen; if (frame > 1.f) { screen.pMin.x = -frame; screen.pMax.x = frame; screen.pMin.y = -1.f; screen.pMax.y = 1.f; } else { screen.pMin.x = -1.f; screen.pMax.x = 1.f; screen.pMin.y = -1.f / frame; screen.pMax.y = 1.f / frame; } std::vector sw = parameters.GetFloatArray("screenwindow"); if (!sw.empty()) { if (sw.size() == 4) { screen.pMin.x = sw[0]; screen.pMax.x = sw[1]; screen.pMin.y = sw[2]; screen.pMax.y = sw[3]; } else Error("\"screenwindow\" should have four values"); } return alloc.new_object(cameraTransform, screen, shutteropen, shutterclose, lensradius, focaldistance, film, medium); } // PerspectiveCamera Method Definitions CameraRay PerspectiveCamera::GenerateRay(CameraSample sample, SampledWavelengths &lambda) const { // Compute raster and camera sample positions Point3f pFilm = Point3f(sample.pFilm.x, sample.pFilm.y, 0); Point3f pCamera = cameraFromRaster(pFilm); Ray ray(Point3f(0, 0, 0), Normalize(Vector3f(pCamera)), SampleTime(sample.time), medium); // Modify ray for depth of field if (lensRadius > 0) { // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); // Compute point on plane of focus Float ft = focalDistance / ray.d.z; Point3f pFocus = ray(ft); // Update ray for effect of lens ray.o = Point3f(pLens.x, pLens.y, 0); ray.d = Normalize(pFocus - ray.o); } return CameraRay{RenderFromCamera(ray)}; } pstd::optional PerspectiveCamera::GenerateRayDifferential( const CameraSample &sample, SampledWavelengths &lambda) const { // Compute raster and camera sample positions Point3f pFilm = Point3f(sample.pFilm.x, sample.pFilm.y, 0); Point3f pCamera = cameraFromRaster(pFilm); Vector3f dir = Normalize(Vector3f(pCamera.x, pCamera.y, pCamera.z)); RayDifferential ray(Point3f(0, 0, 0), dir, SampleTime(sample.time), medium); // Modify ray for depth of field if (lensRadius > 0) { // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); // Compute point on plane of focus Float ft = focalDistance / ray.d.z; Point3f pFocus = ray(ft); // Update ray for effect of lens ray.o = Point3f(pLens.x, pLens.y, 0); ray.d = Normalize(pFocus - ray.o); } // Compute offset rays for \use{PerspectiveCamera} ray differentials if (lensRadius > 0) { // Compute \use{PerspectiveCamera} ray differentials accounting for lens // Sample point on lens Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens); // Compute $x$ ray differential for _PerspectiveCamera_ with lens Vector3f dx = Normalize(Vector3f(pCamera + dxCamera)); Float ft = focalDistance / dx.z; Point3f pFocus = Point3f(0, 0, 0) + (ft * dx); ray.rxOrigin = Point3f(pLens.x, pLens.y, 0); ray.rxDirection = Normalize(pFocus - ray.rxOrigin); // Compute $y$ ray differential for _PerspectiveCamera_ with lens Vector3f dy = Normalize(Vector3f(pCamera + dyCamera)); ft = focalDistance / dy.z; pFocus = Point3f(0, 0, 0) + (ft * dy); ray.ryOrigin = Point3f(pLens.x, pLens.y, 0); ray.ryDirection = Normalize(pFocus - ray.ryOrigin); } else { ray.rxOrigin = ray.ryOrigin = ray.o; ray.rxDirection = Normalize(Vector3f(pCamera) + dxCamera); ray.ryDirection = Normalize(Vector3f(pCamera) + dyCamera); } ray.hasDifferentials = true; return CameraRayDifferential{RenderFromCamera(ray)}; } std::string PerspectiveCamera::ToString() const { return StringPrintf("[ PerspectiveCamera %s dxCamera: %s dyCamera: %s A: " "%f cosTotalWidth: %f ]", BaseToString(), dxCamera, dyCamera, A, cosTotalWidth); } PerspectiveCamera *PerspectiveCamera::Create(const ParameterDictionary ¶meters, const CameraTransform &cameraTransform, FilmHandle film, MediumHandle medium, const FileLoc *loc, Allocator alloc) { // Extract common camera parameters from _ParameterDictionary_ Float shutteropen = parameters.GetOneFloat("shutteropen", 0.f); Float shutterclose = parameters.GetOneFloat("shutterclose", 1.f); if (shutterclose < shutteropen) { Warning(loc, "Shutter close time %f < shutter open %f. Swapping them.", shutterclose, shutteropen); pstd::swap(shutterclose, shutteropen); } Float lensradius = parameters.GetOneFloat("lensradius", 0.f); Float focaldistance = parameters.GetOneFloat("focaldistance", 1e6); Float frame = parameters.GetOneFloat("frameaspectratio", Float(film.FullResolution().x) / Float(film.FullResolution().y)); Bounds2f screen; if (frame > 1.f) { screen.pMin.x = -frame; screen.pMax.x = frame; screen.pMin.y = -1.f; screen.pMax.y = 1.f; } else { screen.pMin.x = -1.f; screen.pMax.x = 1.f; screen.pMin.y = -1.f / frame; screen.pMax.y = 1.f / frame; } std::vector sw = parameters.GetFloatArray("screenwindow"); if (!sw.empty()) { if (sw.size() == 4) { screen.pMin.x = sw[0]; screen.pMax.x = sw[1]; screen.pMin.y = sw[2]; screen.pMax.y = sw[3]; } else Error(loc, "\"screenwindow\" should have four values"); } Float fov = parameters.GetOneFloat("fov", 90.); return alloc.new_object(cameraTransform, screen, shutteropen, shutterclose, lensradius, focaldistance, fov, film, medium); } SampledSpectrum PerspectiveCamera::We(const Ray &ray, SampledWavelengths &lambda, Point2f *pRaster2) const { // Check if ray is forward-facing with respect to the camera Float cosTheta = Dot(ray.d, RenderFromCamera(Vector3f(0, 0, 1), ray.time)); if (cosTheta <= cosTotalWidth) return SampledSpectrum(0.); // Map ray $(\p{}, \w{})$ onto the raster grid Point3f pFocus = ray((lensRadius > 0 ? focalDistance : 1) / cosTheta); Point3f pCamera = CameraFromRender(pFocus, ray.time); Point3f pRaster = cameraFromRaster.ApplyInverse(pCamera); // Return raster position if requested if (pRaster2) *pRaster2 = Point2f(pRaster.x, pRaster.y); // Return zero importance for out of bounds points Bounds2f sampleBounds = film.SampleBounds(); if (!Inside(Point2f(pRaster.x, pRaster.y), sampleBounds)) return SampledSpectrum(0.); // Compute lens area of perspective camera Float lensArea = lensRadius != 0 ? (Pi * lensRadius * lensRadius) : 1; // Return importance for point on image plane return SampledSpectrum(1 / (A * lensArea * Pow<4>(cosTheta))); } void PerspectiveCamera::PDF_We(const Ray &ray, Float *pdfPos, Float *pdfDir) const { // Return zero PDF values if ray direction is not front-facing Float cosTheta = Dot(ray.d, RenderFromCamera(Vector3f(0, 0, 1), ray.time)); if (cosTheta <= cosTotalWidth) { *pdfPos = *pdfDir = 0; return; } // Map ray $(\p{}, \w{})$ onto the raster grid Point3f pFocus = ray((lensRadius > 0 ? focalDistance : 1) / cosTheta); Point3f pCamera = CameraFromRender(pFocus, ray.time); Point3f pRaster = cameraFromRaster.ApplyInverse(pCamera); // Return zero probability for out of bounds points Bounds2f sampleBounds = film.SampleBounds(); if (!Inside(Point2f(pRaster.x, pRaster.y), sampleBounds)) { *pdfPos = *pdfDir = 0; return; } // Compute lens area and return perspective camera probabilities Float lensArea = lensRadius != 0 ? (Pi * lensRadius * lensRadius) : 1; *pdfPos = 1 / lensArea; *pdfDir = 1 / (A * Pow<3>(cosTheta)); } pstd::optional PerspectiveCamera::SampleWi( const Interaction &ref, const Point2f &u, SampledWavelengths &lambda) const { // Uniformly sample a lens interaction _lensIntr_ Point2f pLens = lensRadius * SampleUniformDiskConcentric(u); Point3f pLensRender = RenderFromCamera(Point3f(pLens.x, pLens.y, 0), ref.time); Normal3f n = Normal3f(RenderFromCamera(Vector3f(0, 0, 1), ref.time)); Interaction lensIntr(pLensRender, n, ref.time, medium); // Populate arguments and compute the importance value // Compute incident direction to camera _wi_ at _ref_ Vector3f wi = lensIntr.p() - ref.p(); Float dist = Length(wi); wi /= dist; // Compute PDF for importance arriving at _ref_ Float lensArea = lensRadius != 0 ? (Pi * lensRadius * lensRadius) : 1; Float pdf = (dist * dist) / (AbsDot(lensIntr.n, wi) * lensArea); Point2f pRaster; SampledSpectrum Wi = We(lensIntr.SpawnRay(-wi), lambda, &pRaster); if (!Wi) return {}; return CameraWiSample(Wi, wi, pdf, pRaster, ref, lensIntr); } // SphericalCamera Method Definitions CameraRay SphericalCamera::GenerateRay(CameraSample sample, SampledWavelengths &lambda) const { // Compute spherical camera ray direction Vector3f dir; if (mapping == EquiRect) { // Compute ray direction using equi-rectangular mapping Float theta = Pi * sample.pFilm.y / film.FullResolution().y; Float phi = 2 * Pi * sample.pFilm.x / film.FullResolution().x; dir = SphericalDirection(std::sin(theta), std::cos(theta), phi); } else { // Compute ray direction using equi-area mapping Point2f uv(sample.pFilm.x / film.FullResolution().x, sample.pFilm.y / film.FullResolution().y); uv = WrapEquiAreaSquare(uv); dir = EquiAreaSquareToSphere(uv); } pstd::swap(dir.y, dir.z); Ray ray(Point3f(0, 0, 0), dir, SampleTime(sample.time), medium); return CameraRay{RenderFromCamera(ray)}; } SphericalCamera *SphericalCamera::Create(const ParameterDictionary ¶meters, const CameraTransform &cameraTransform, FilmHandle film, MediumHandle medium, const FileLoc *loc, Allocator alloc) { // Extract common camera parameters from _ParameterDictionary_ Float shutteropen = parameters.GetOneFloat("shutteropen", 0.f); Float shutterclose = parameters.GetOneFloat("shutterclose", 1.f); if (shutterclose < shutteropen) { Warning(loc, "Shutter close time %f < shutter open %f. Swapping them.", shutterclose, shutteropen); pstd::swap(shutterclose, shutteropen); } Float lensradius = parameters.GetOneFloat("lensradius", 0.f); Float focaldistance = parameters.GetOneFloat("focaldistance", 1e30f); Float frame = parameters.GetOneFloat("frameaspectratio", Float(film.FullResolution().x) / Float(film.FullResolution().y)); Bounds2f screen; if (frame > 1.f) { screen.pMin.x = -frame; screen.pMax.x = frame; screen.pMin.y = -1.f; screen.pMax.y = 1.f; } else { screen.pMin.x = -1.f; screen.pMax.x = 1.f; screen.pMin.y = -1.f / frame; screen.pMax.y = 1.f / frame; } std::vector sw = parameters.GetFloatArray("screenwindow"); if (!sw.empty()) { if (sw.size() == 4) { screen.pMin.x = sw[0]; screen.pMax.x = sw[1]; screen.pMin.y = sw[2]; screen.pMax.y = sw[3]; } else Error(loc, "\"screenwindow\" should have four values"); } (void)lensradius; // don't need this (void)focaldistance; // don't need this std::string m = parameters.GetOneString("mapping", "equiarea"); Mapping mapping; if (m == "equiarea") mapping = EquiArea; else if (m == "equirect") mapping = EquiRect; else ErrorExit(loc, "%s: unknown mapping for spherical camera. (Must be " "\"equiarea\" or \"equirect\".)", m); return alloc.new_object(cameraTransform, shutteropen, shutterclose, film, medium, mapping); } std::string SphericalCamera::ToString() const { return StringPrintf("[ SphericalCamera %s mapping: %s ]", CameraBase::ToString(), mapping == EquiRect ? "EquiRect" : "EquiArea"); } // RealisticCamera Method Definitions RealisticCamera::RealisticCamera(const CameraTransform &cameraTransform, Float shutterOpen, Float shutterClose, Float setApertureDiameter, Float focusDistance, Float dispersionFactor, std::vector &lensData, Float scale, FilmHandle film, MediumHandle medium, Image apertureImage, Allocator alloc) : CameraBase(cameraTransform, shutterOpen, shutterClose, film, medium), scale(scale), dispersionFactor(dispersionFactor), elementInterfaces(alloc), exitPupilBounds(alloc), apertureImage(std::move(apertureImage)) { // Initialize _elementInterfaces_ for camera for (int i = 0; i < (int)lensData.size(); i += 4) { // Extract lens element configuration from _lensData_ Float curvatureRadius = scale * lensData[i] * 0.001f; Float thickness = scale * lensData[i + 1] * 0.001f; Float eta = lensData[i + 2]; Float apertureDiameter = scale * lensData[i + 3] * 0.001f; if (curvatureRadius == 0) { // Set aperture stop diameter setApertureDiameter *= 0.001f; if (setApertureDiameter > apertureDiameter) Warning("Specified aperture diameter %f is greater than maximum " "possible %f. Clamping it.", setApertureDiameter, apertureDiameter); else apertureDiameter = setApertureDiameter; } // Add element interface to end of _elementInterfaces_ LensElementInterface interface{curvatureRadius, thickness, eta, apertureDiameter / 2}; elementInterfaces.emplace_back(interface); } // Compute lens--film distance for given focus distance Float fb = FocusBinarySearch(focusDistance); elementInterfaces.back().thickness = FocusThickLens(focusDistance); // Compute exit pupil bounds at sampled points on the film int nSamples = 64; exitPupilBounds.resize(nSamples); ParallelFor(0, nSamples, [&](int i) { Float r0 = (Float)i / nSamples * FilmDiagonal() / 2; Float r1 = (Float)(i + 1) / nSamples * FilmDiagonal() / 2; exitPupilBounds[i] = BoundExitPupil(r0, r1); }); FindMinimumDifferentials(this); } Float RealisticCamera::TraceLensesFromFilm(const Ray &rCamera, Ray *rOut, Float lambda) const { Float elementZ = 0; Float weight = 1; // Transform _rCamera_ from camera to lens system space Transform LensFromCamera = Scale(1, 1, -1); Ray rLens = LensFromCamera(rCamera); for (int i = elementInterfaces.size() - 1; i >= 0; --i) { const LensElementInterface &element = elementInterfaces[i]; // Update ray from film accounting for interaction with _element_ elementZ -= element.thickness; // Compute intersection of ray with lens element Float t; Normal3f n; bool isStop = (element.curvatureRadius == 0); if (isStop) { // Compute _t_ at plane of aperture stop if (rLens.d.z >= 0.0) return false; t = (elementZ - rLens.o.z) / rLens.d.z; } else { // Intersect ray with element to compute _t_ and _n_ Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, rLens, &t, &n)) return false; } DCHECK_GE(t, 0); // Test intersection point against element aperture Point3f pHit = rLens(t); if (isStop && apertureImage) { // Check intersection point against _apertureImage_ Point2f uv((pHit.x / element.apertureRadius + 1) / 2, (pHit.y / element.apertureRadius + 1) / 2); uv.y = 1 - uv.y; weight = apertureImage.BilerpChannel(uv, 0, WrapMode::Black); if (weight == 0) return 0; } else { // Check intersection point against spherical aperture Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; if (r2 > element.apertureRadius * element.apertureRadius) return 0; } rLens.o = pHit; // Update ray path for element interface interaction if (!isStop) { Vector3f w; Float eta_i = element.eta; Float eta_t = (i > 0 && elementInterfaces[i - 1].eta != 0) ? elementInterfaces[i - 1].eta : 1; // Optionally apply ad-hoc dispersion approximation if (dispersionFactor != 0) { Float offset = (lambda - 550) / (550 - 400); // [-1,1] for lambda in [400,700] eta_i -= offset * dispersionFactor * .02; eta_t -= offset * dispersionFactor * .02; } if (!Refract(Normalize(-rLens.d), n, eta_t / eta_i, &w)) return 0; rLens.d = w; } } // Transform _rLens_ from lens system space back to camera space if (rOut != nullptr) { const Transform LensToCamera = Scale(1, 1, -1); *rOut = LensToCamera(rLens); } return weight; } void RealisticCamera::ComputeCardinalPoints(const Ray &rIn, const Ray &rOut, Float *pz, Float *fz) { Float tf = -rOut.o.x / rOut.d.x; *fz = -rOut(tf).z; Float tp = (rIn.o.x - rOut.o.x) / rOut.d.x; *pz = -rOut(tp).z; } void RealisticCamera::ComputeThickLensApproximation(Float pz[2], Float fz[2]) const { // Find height $x$ from optical axis for parallel rays Float x = .001 * FilmDiagonal(); // Compute cardinal points for film side of lens system Ray rScene(Point3f(x, 0, LensFrontZ() + 1), Vector3f(0, 0, -1)); Ray rFilm; if (!TraceLensesFromScene(rScene, &rFilm)) ErrorExit("Unable to trace ray from scene to film for thick lens " "approximation. Is aperture stop extremely small?"); ComputeCardinalPoints(rScene, rFilm, &pz[0], &fz[0]); // Compute cardinal points for scene side of lens system rFilm = Ray(Point3f(x, 0, LensRearZ() - 1), Vector3f(0, 0, 1)); if (TraceLensesFromFilm(rFilm, &rScene) == 0) ErrorExit("Unable to trace ray from film to scene for thick lens " "approximation. Is aperture stop extremely small?"); ComputeCardinalPoints(rFilm, rScene, &pz[1], &fz[1]); } Float RealisticCamera::FocusThickLens(Float focusDistance) { Float pz[2], fz[2]; ComputeThickLensApproximation(pz, fz); LOG_VERBOSE("Cardinal points: p' = %f f' = %f, p = %f f = %f.\n", pz[0], fz[0], pz[1], fz[1]); LOG_VERBOSE("Effective focal length %f\n", fz[0] - pz[0]); // Compute translation of lens, _delta_, to focus at _focusDistance_ Float f = fz[0] - pz[0]; Float z = -focusDistance; Float c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]); if (c <= 0) ErrorExit("Coefficient must be positive. It looks focusDistance %f " " is too short for a given lenses configuration", focusDistance); Float delta = 0.5f * (pz[1] - z + pz[0] - std::sqrt(c)); return elementInterfaces.back().thickness + delta; } Float RealisticCamera::FocusBinarySearch(Float focusDistance) { Float filmDistanceLower, filmDistanceUpper; // Find _filmDistanceLower_, _filmDistanceUpper_ that bound focus distance filmDistanceLower = filmDistanceUpper = FocusThickLens(focusDistance); while (FocusDistance(filmDistanceLower) > focusDistance) filmDistanceLower *= 1.005f; while (FocusDistance(filmDistanceUpper) < focusDistance) filmDistanceUpper /= 1.005f; // Do binary search on film distances to focus for (int i = 0; i < 20; ++i) { Float fmid = 0.5f * (filmDistanceLower + filmDistanceUpper); Float midFocus = FocusDistance(fmid); if (midFocus < focusDistance) filmDistanceLower = fmid; else filmDistanceUpper = fmid; } return 0.5f * (filmDistanceLower + filmDistanceUpper); } Float RealisticCamera::FocusDistance(Float filmDistance) { // Find offset ray from film center through lens Bounds2f bounds = BoundExitPupil(0, .001 * FilmDiagonal()); Ray ray; bool foundFocusRay = false; for (Float scale : {0.1f, 0.01f, 0.001f}) { Float lu = scale * bounds.pMax[0]; if (TraceLensesFromFilm(Ray(Point3f(0, 0, LensRearZ() - filmDistance), Vector3f(lu, 0, filmDistance)), &ray)) { foundFocusRay = true; break; } } if (!foundFocusRay) { Error("Couldn't fidn a focus ray that made it through the lenses " "with film distance %f?!??\n", filmDistance); return Infinity; } // Compute distance _zFocus_ where ray intersects the principal axis Float tFocus = -ray.o.x / ray.d.x; Float zFocus = ray(tFocus).z; if (zFocus < 0) zFocus = Infinity; return zFocus; } Bounds2f RealisticCamera::BoundExitPupil(Float filmX0, Float filmX1) const { Bounds2f pupilBounds; // Sample a collection of points on the rear lens to find exit pupil const int nSamples = 1024 * 1024; int nExitingRays = 0; // Compute bounding box of projection of rear element on sampling plane Float rearRadius = RearElementRadius(); Bounds2f projRearBounds(Point2f(-1.5f * rearRadius, -1.5f * rearRadius), Point2f(1.5f * rearRadius, 1.5f * rearRadius)); for (int i = 0; i < nSamples; ++i) { // Find location of sample points on $x$ segment and rear lens element Point3f pFilm(Lerp((i + 0.5f) / nSamples, filmX0, filmX1), 0, 0); Float u[2] = {RadicalInverse(0, i), RadicalInverse(1, i)}; Point3f pRear(Lerp(u[0], projRearBounds.pMin.x, projRearBounds.pMax.x), Lerp(u[1], projRearBounds.pMin.y, projRearBounds.pMax.y), LensRearZ()); // Expand pupil bounds if ray makes it through the lens system if (Inside(Point2f(pRear.x, pRear.y), pupilBounds) || TraceLensesFromFilm(Ray(pFilm, pRear - pFilm), nullptr)) { pupilBounds = Union(pupilBounds, Point2f(pRear.x, pRear.y)); ++nExitingRays; } } // Return entire element bounds if no rays made it through the lens system if (nExitingRays == 0) { LOG_VERBOSE("Unable to find exit pupil in x = [%f,%f] on film.", filmX0, filmX1); return projRearBounds; } // Expand bounds to account for sample spacing pupilBounds = Expand(pupilBounds, 2 * Length(projRearBounds.Diagonal()) / std::sqrt(nSamples)); return pupilBounds; } Point3f RealisticCamera::SampleExitPupil(const Point2f &pFilm, const Point2f &lensSample, Float *sampleBoundsArea) const { // Find exit pupil bound for sample distance from film center Float rFilm = std::sqrt(pFilm.x * pFilm.x + pFilm.y * pFilm.y); int rIndex = rFilm / (FilmDiagonal() / 2) * exitPupilBounds.size(); rIndex = std::min(exitPupilBounds.size() - 1, rIndex); Bounds2f pupilBounds = exitPupilBounds[rIndex]; if (sampleBoundsArea != nullptr) *sampleBoundsArea = pupilBounds.Area(); // Generate sample point inside exit pupil bound Point2f pLens = pupilBounds.Lerp(lensSample); // Return sample point rotated by angle of _pFilm_ with $+x$ axis Float sinTheta = (rFilm != 0) ? pFilm.y / rFilm : 0; Float cosTheta = (rFilm != 0) ? pFilm.x / rFilm : 1; return {cosTheta * pLens.x - sinTheta * pLens.y, sinTheta * pLens.x + cosTheta * pLens.y, LensRearZ()}; } CameraRay RealisticCamera::GenerateRay(CameraSample sample, SampledWavelengths &lambda) const { // Find point on film, _pFilm_, corresponding to _sample.pFilm_ // Compute Film's physical extent Float aspect = (Float)film.FullResolution().y / (Float)film.FullResolution().x; Float diagonal = FilmDiagonal(); Float x = std::sqrt(diagonal * diagonal / (1 + aspect * aspect)); Float y = aspect * x; Bounds2f physicalExtent(Point2f(-x / 2, -y / 2), Point2f(x / 2, y / 2)); Point2f s(sample.pFilm.x / film.FullResolution().x, sample.pFilm.y / film.FullResolution().y); Point2f pFilm2 = physicalExtent.Lerp(s); Point3f pFilm(-pFilm2.x, pFilm2.y, 0); // Trace ray from _pFilm_ through lens system Float exitPupilBoundsArea; Point3f pRear = SampleExitPupil(Point2f(pFilm.x, pFilm.y), sample.pLens, &exitPupilBoundsArea); Ray rFilm(pFilm, pRear - pFilm); Ray ray; Float weight = TraceLensesFromFilm(rFilm, &ray, lambda[0]); if (weight == 0) return CameraRay{Ray(), SampledSpectrum(0.f)}; // Finish initialization of _RealisticCamera_ ray ray.time = SampleTime(sample.time); ray.medium = medium; ray = RenderFromCamera(ray); ray.d = Normalize(ray.d); // Terminate secondary rays if lenses are dispersive if (dispersionFactor != 0) lambda.TerminateSecondary(); // Compute weighting for _RealisticCamera_ ray Float cosTheta = Normalize(rFilm.d).z; Float cos4Theta = (cosTheta * cosTheta) * (cosTheta * cosTheta); weight *= (shutterClose - shutterOpen) * (cos4Theta * exitPupilBoundsArea) / (LensRearZ() * LensRearZ()); return CameraRay{ray, SampledSpectrum(weight)}; } STAT_PERCENT("Camera/Rays vignetted by lens system", vignettedRays, totalRays); std::string RealisticCamera::LensElementInterface::ToString() const { return StringPrintf("[ LensElementInterface curvatureRadius: %f thickness: %f " "eta: %f apertureRadius: %f ]", curvatureRadius, thickness, eta, apertureRadius); } bool RealisticCamera::TraceLensesFromScene(const Ray &rCamera, Ray *rOut) const { Float elementZ = -LensFrontZ(); // Transform _rCamera_ from camera to lens system space const Transform LensFromCamera = Scale(1, 1, -1); Ray rLens = LensFromCamera(rCamera); for (size_t i = 0; i < elementInterfaces.size(); ++i) { const LensElementInterface &element = elementInterfaces[i]; // Compute intersection of ray with lens element Float t; Normal3f n; bool isStop = (element.curvatureRadius == 0); if (isStop) t = (elementZ - rLens.o.z) / rLens.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, rLens, &t, &n)) return false; } CHECK_GE(t, 0); // Test intersection point against element aperture // Don't worry about the aperture image here. Point3f pHit = rLens(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; if (r2 > element.apertureRadius * element.apertureRadius) return false; rLens.o = pHit; // Update ray path for from-scene element interface interaction if (!isStop) { Vector3f wt; Float eta_i = (i == 0 || elementInterfaces[i - 1].eta == 0) ? 1 : elementInterfaces[i - 1].eta; Float eta_t = (elementInterfaces[i].eta != 0) ? elementInterfaces[i].eta : 1; if (!Refract(Normalize(-rLens.d), n, eta_t / eta_i, &wt)) return false; rLens.d = wt; } elementZ += element.thickness; } // Transform _rLens_ from lens system space back to camera space if (rOut != nullptr) { const Transform LensToCamera = Scale(1, 1, -1); *rOut = LensToCamera(rLens); } return true; } void RealisticCamera::DrawLensSystem() const { Float sumz = -LensFrontZ(); Float z = sumz; for (size_t i = 0; i < elementInterfaces.size(); ++i) { const LensElementInterface &element = elementInterfaces[i]; Float r = element.curvatureRadius; if (r == 0) { // stop printf("{Thick, Line[{{%f, %f}, {%f, %f}}], ", z, element.apertureRadius, z, 2 * element.apertureRadius); printf("Line[{{%f, %f}, {%f, %f}}]}, ", z, -element.apertureRadius, z, -2 * element.apertureRadius); } else { Float theta = std::abs(SafeASin(element.apertureRadius / r)); if (r > 0) { // convex as seen from front of lens Float t0 = Pi - theta; Float t1 = Pi + theta; printf("Circle[{%f, 0}, %f, {%f, %f}], ", z + r, r, t0, t1); } else { // concave as seen from front of lens Float t0 = -theta; Float t1 = theta; printf("Circle[{%f, 0}, %f, {%f, %f}], ", z + r, -r, t0, t1); } if (element.eta != 0 && element.eta != 1) { // connect top/bottom to next element CHECK_LT(i + 1, elementInterfaces.size()); Float nextApertureRadius = elementInterfaces[i + 1].apertureRadius; Float h = std::max(element.apertureRadius, nextApertureRadius); Float hlow = std::min(element.apertureRadius, nextApertureRadius); Float zp0, zp1; if (r > 0) { zp0 = z + element.curvatureRadius - element.apertureRadius / std::tan(theta); } else { zp0 = z + element.curvatureRadius + element.apertureRadius / std::tan(theta); } Float nextCurvatureRadius = elementInterfaces[i + 1].curvatureRadius; Float nextTheta = std::abs(SafeASin(nextApertureRadius / nextCurvatureRadius)); if (nextCurvatureRadius > 0) { zp1 = z + element.thickness + nextCurvatureRadius - nextApertureRadius / std::tan(nextTheta); } else { zp1 = z + element.thickness + nextCurvatureRadius + nextApertureRadius / std::tan(nextTheta); } // Connect tops printf("Line[{{%f, %f}, {%f, %f}}], ", zp0, h, zp1, h); printf("Line[{{%f, %f}, {%f, %f}}], ", zp0, -h, zp1, -h); // vertical lines when needed to close up the element profile if (element.apertureRadius < nextApertureRadius) { printf("Line[{{%f, %f}, {%f, %f}}], ", zp0, h, zp0, hlow); printf("Line[{{%f, %f}, {%f, %f}}], ", zp0, -h, zp0, -hlow); } else if (element.apertureRadius > nextApertureRadius) { printf("Line[{{%f, %f}, {%f, %f}}], ", zp1, h, zp1, hlow); printf("Line[{{%f, %f}, {%f, %f}}], ", zp1, -h, zp1, -hlow); } } } z += element.thickness; } // 24mm height for 35mm film printf("Line[{{0, -.012}, {0, .012}}], "); // optical axis printf("Line[{{0, 0}, {%f, 0}}] ", 1.2f * sumz); } void RealisticCamera::DrawRayPathFromFilm(const Ray &r, bool arrow, bool toOpticalIntercept) const { Float elementZ = 0; // Transform _ray_ from camera to lens system space static const Transform LensFromCamera = Scale(1, 1, -1); Ray ray = LensFromCamera(r); printf("{ "); if (TraceLensesFromFilm(r, nullptr) == 0) { printf("Dashed, RGBColor[.8, .5, .5]"); } else printf("RGBColor[.5, .5, .8]"); for (int i = elementInterfaces.size() - 1; i >= 0; --i) { const LensElementInterface &element = elementInterfaces[i]; elementZ -= element.thickness; bool isStop = (element.curvatureRadius == 0); // Compute intersection of ray with lens element Float t; Normal3f n; if (isStop) t = -(ray.o.z - elementZ) / ray.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, ray, &t, &n)) goto done; } CHECK_GE(t, 0); printf(", Line[{{%f, %f}, {%f, %f}}]", ray.o.z, ray.o.x, ray(t).z, ray(t).x); // Test intersection point against element aperture Point3f pHit = ray(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; Float apertureRadius2 = element.apertureRadius * element.apertureRadius; if (r2 > apertureRadius2) goto done; ray.o = pHit; // Update ray path for element interface interaction if (!isStop) { Vector3f wt; Float eta_i = element.eta; Float eta_t = (i > 0 && elementInterfaces[i - 1].eta != 0) ? elementInterfaces[i - 1].eta : 1; if (!Refract(Normalize(-ray.d), n, eta_t / eta_i, &wt)) goto done; ray.d = wt; } } ray.d = Normalize(ray.d); { Float ta = std::abs(elementZ / 4); if (toOpticalIntercept) { ta = -ray.o.x / ray.d.x; printf(", Point[{%f, %f}]", ray(ta).z, ray(ta).x); } printf(", %s[{{%f, %f}, {%f, %f}}]", arrow ? "Arrow" : "Line", ray.o.z, ray.o.x, ray(ta).z, ray(ta).x); // overdraw the optical axis if needed... if (toOpticalIntercept) printf(", Line[{{%f, 0}, {%f, 0}}]", ray.o.z, ray(ta).z * 1.05f); } done: printf("}"); } void RealisticCamera::DrawRayPathFromScene(const Ray &r, bool arrow, bool toOpticalIntercept) const { Float elementZ = LensFrontZ() * -1; // Transform _ray_ from camera to lens system space static const Transform LensFromCamera = Scale(1, 1, -1); Ray ray = LensFromCamera(r); for (size_t i = 0; i < elementInterfaces.size(); ++i) { const LensElementInterface &element = elementInterfaces[i]; bool isStop = (element.curvatureRadius == 0); // Compute intersection of ray with lens element Float t; Normal3f n; if (isStop) t = -(ray.o.z - elementZ) / ray.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, ray, &t, &n)) return; } CHECK_GE(t, 0.f); printf("Line[{{%f, %f}, {%f, %f}}],", ray.o.z, ray.o.x, ray(t).z, ray(t).x); // Test intersection point against element aperture Point3f pHit = ray(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; Float apertureRadius2 = element.apertureRadius * element.apertureRadius; if (r2 > apertureRadius2) return; ray.o = pHit; // Update ray path for from-scene element interface interaction if (!isStop) { Vector3f wt; Float eta_i = (i == 0 || elementInterfaces[i - 1].eta == 0.f) ? 1.f : elementInterfaces[i - 1].eta; Float eta_t = (elementInterfaces[i].eta != 0.f) ? elementInterfaces[i].eta : 1.f; if (!Refract(Normalize(-ray.d), n, eta_t / eta_i, &wt)) return; ray.d = wt; } elementZ += element.thickness; } // go to the film plane by default { Float ta = -ray.o.z / ray.d.z; if (toOpticalIntercept) { ta = -ray.o.x / ray.d.x; printf("Point[{%f, %f}], ", ray(ta).z, ray(ta).x); } printf("%s[{{%f, %f}, {%f, %f}}]", arrow ? "Arrow" : "Line", ray.o.z, ray.o.x, ray(ta).z, ray(ta).x); } } void RealisticCamera::RenderExitPupil(Float sx, Float sy, const char *filename) const { Point3f pFilm(sx, sy, 0); const int nSamples = 2048; Image image(PixelFormat::Float, {nSamples, nSamples}, {"Y"}); for (int y = 0; y < nSamples; ++y) { Float fy = (Float)y / (Float)(nSamples - 1); Float ly = Lerp(fy, -RearElementRadius(), RearElementRadius()); for (int x = 0; x < nSamples; ++x) { Float fx = (Float)x / (Float)(nSamples - 1); Float lx = Lerp(fx, -RearElementRadius(), RearElementRadius()); Point3f pRear(lx, ly, LensRearZ()); if (lx * lx + ly * ly > RearElementRadius() * RearElementRadius()) image.SetChannel({x, y}, 0, 1.); else if (TraceLensesFromFilm(Ray(pFilm, pRear - pFilm), nullptr)) image.SetChannel({x, y}, 0, 0.5); else image.SetChannel({x, y}, 0, 0.); } } image.Write(filename); } void RealisticCamera::TestExitPupilBounds() const { Float filmDiagonal = FilmDiagonal(); static RNG rng; Float u = rng.Uniform(); Point3f pFilm(u * filmDiagonal / 2, 0, 0); Float r = pFilm.x / (filmDiagonal / 2); int pupilIndex = std::min(exitPupilBounds.size() - 1, 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 u2{rng.Uniform(), rng.Uniform()}; Point2f pd = SampleUniformDiskConcentric(u2); 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, "."); } std::string RealisticCamera::ToString() const { return StringPrintf("[ RealisticCamera %s dispersionFactor: %f " "elementInterfaces: %s exitPupilBounds: %s ]", CameraBase::ToString(), dispersionFactor, elementInterfaces, exitPupilBounds); } RealisticCamera *RealisticCamera::Create(const ParameterDictionary ¶meters, const CameraTransform &cameraTransform, FilmHandle film, MediumHandle medium, const FileLoc *loc, Allocator alloc) { Float shutteropen = parameters.GetOneFloat("shutteropen", 0.f); Float shutterclose = parameters.GetOneFloat("shutterclose", 1.f); if (shutterclose < shutteropen) { Warning(loc, "Shutter close time %f < shutter open %f. Swapping them.", shutterclose, shutteropen); pstd::swap(shutterclose, shutteropen); } // Realistic camera-specific parameters std::string lensFile = ResolveFilename(parameters.GetOneString("lensfile", "")); Float apertureDiameter = parameters.GetOneFloat("aperturediameter", 1.0); Float focusDistance = parameters.GetOneFloat("focusdistance", 10.0); Float dispersionFactor = parameters.GetOneFloat("dispersionfactor", 0.); Float scale = parameters.GetOneFloat("scale", 1.f); if (lensFile.empty()) { Error(loc, "No lens description file supplied!"); return nullptr; } // Load element data from lens description file std::vector lensData = ReadFloatFile(lensFile); if (lensData.empty()) { Error(loc, "Error reading lens specification file \"%s\".", lensFile); return nullptr; } if (lensData.size() % 4 != 0) { Error(loc, "%s: excess values in lens specification file; " "must be multiple-of-four values, read %d.", lensFile, (int)lensData.size()); return nullptr; } int builtinRes = 256; auto rasterize = [&](pstd::span vert) { Image image(PixelFormat::Float, {builtinRes, builtinRes}, {"Y"}, nullptr, alloc); for (int y = 0; y < image.Resolution().y; ++y) for (int x = 0; x < image.Resolution().x; ++x) { Point2f p(-1 + 2 * (x + 0.5f) / image.Resolution().x, -1 + 2 * (y + 0.5f) / image.Resolution().y); int windingNumber = 0; // Test against edges for (int i = 0; i < vert.size(); ++i) { int i1 = (i + 1) % vert.size(); Float e = (p[0] - vert[i][0]) * (vert[i1][1] - vert[i][1]) - (p[1] - vert[i][1]) * (vert[i1][0] - vert[i][0]); if (vert[i].y <= p.y) { if (vert[i1].y > p.y && e > 0) ++windingNumber; } else if (vert[i1].y <= p.y && e < 0) --windingNumber; } image.SetChannel({x, y}, 0, windingNumber == 0 ? 0.f : 1.f); } return image; }; std::string apertureName = ResolveFilename(parameters.GetOneString("aperture", "")); Image apertureImage; if (!apertureName.empty()) { // built-in diaphragm shapes if (apertureName == "gaussian") { apertureImage = Image(PixelFormat::Float, {builtinRes, builtinRes}, {"Y"}, nullptr, alloc); for (int y = 0; y < apertureImage.Resolution().y; ++y) for (int x = 0; x < apertureImage.Resolution().x; ++x) { Point2f uv(-1 + 2 * (x + 0.5f) / apertureImage.Resolution().x, -1 + 2 * (y + 0.5f) / apertureImage.Resolution().y); Float r2 = Sqr(uv.x) + Sqr(uv.y); Float sigma2 = 1; Float v = std::max( 0, std::exp(-r2 / sigma2) - std::exp(-1 / sigma2)); apertureImage.SetChannel({x, y}, 0, v); } } else if (apertureName == "square") { apertureImage = Image(PixelFormat::Float, {builtinRes, builtinRes}, {"Y"}, nullptr, alloc); for (int y = 0; y < apertureImage.Resolution().y; ++y) for (int x = 0; x < apertureImage.Resolution().x; ++x) apertureImage.SetChannel({x, y}, 0, 1.f); } else if (apertureName == "pentagon") { // https://mathworld.wolfram.com/RegularPentagon.html Float c1 = (std::sqrt(5.f) - 1) / 4; Float c2 = (std::sqrt(5.f) + 1) / 4; Float s1 = std::sqrt(10.f + 2.f * std::sqrt(5.f)) / 4; Float s2 = std::sqrt(10.f - 2.f * std::sqrt(5.f)) / 4; // Vertices in CW order. Point2f vert[5] = {Point2f(0, 1), {s1, c1}, {s2, -c2}, {-s2, -c2}, {-s1, c1}}; // Scale down slightly for (int i = 0; i < 5; ++i) vert[i] *= .8f; apertureImage = rasterize(vert); } else if (apertureName == "star") { // 5-sided. Vertices are two pentagons--inner and outer radius pstd::array vert; for (int i = 0; i < 10; ++i) { // inner radius: https://math.stackexchange.com/a/2136996 Float r = (i & 1) ? 1.f : (std::cos(Radians(72.f)) / std::cos(Radians(36.f))); vert[i] = Point2f(r * std::cos(Pi * i / 5.f), r * std::sin(Pi * i / 5.f)); } std::reverse(vert.begin(), vert.end()); apertureImage = rasterize(vert); } else { ImageAndMetadata im = Image::Read(apertureName, alloc); apertureImage = std::move(im.image); if (apertureImage.NChannels() > 1) { ImageChannelDesc rgbDesc = apertureImage.GetChannelDesc({"R", "G", "B"}); if (!rgbDesc) ErrorExit("%s: didn't find R, G, B channels to average for " "aperture image.", apertureName); Image mono(PixelFormat::Float, apertureImage.Resolution(), {"Y"}, nullptr, alloc); for (int y = 0; y < mono.Resolution().y; ++y) for (int x = 0; x < mono.Resolution().x; ++x) { Float avg = apertureImage.GetChannels({x, y}, rgbDesc).Average(); mono.SetChannel({x, y}, 0, avg); } apertureImage = std::move(mono); } } if (apertureImage) { // Normalize it so that brightness matches a circular aperture Float sum = 0; for (int y = 0; y < apertureImage.Resolution().y; ++y) for (int x = 0; x < apertureImage.Resolution().x; ++x) sum += apertureImage.GetChannel({x, y}, 0); Float avg = sum / (apertureImage.Resolution().x * apertureImage.Resolution().y); Float scale = (Pi / 4) / avg; for (int y = 0; y < apertureImage.Resolution().y; ++y) for (int x = 0; x < apertureImage.Resolution().x; ++x) apertureImage.SetChannel({x, y}, 0, apertureImage.GetChannel({x, y}, 0) * scale); } } return alloc.new_object( cameraTransform, shutteropen, shutterclose, apertureDiameter, focusDistance, dispersionFactor, lensData, scale, film, medium, std::move(apertureImage), alloc); } } // namespace pbrt