Spectrum FresnelSpecular::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &u, Float *pdf, BxDFType *sampledType) const { Float F = FrDielectric(CosTheta(wo), etaA, etaB); if (u[0] < F) { // Compute specular reflection for _FresnelSpecular_ // Compute perfect specular reflection direction *wi = Vector3f(-wo.x, -wo.y, wo.z); if (sampledType) *sampledType = BxDFType(BSDF_SPECULAR | BSDF_REFLECTION); *pdf = F; return F * R / AbsCosTheta(*wi); } else { // Compute specular transmission for _FresnelSpecular_ // Figure out which $\eta$ is incident and which is transmitted bool entering = CosTheta(wo) > 0; Float etaI = entering ? etaA : etaB; Float etaT = entering ? etaB : etaA; // Compute ray direction for specular transmission if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi)) return 0; Spectrum ft = T * (1 - F); // Account for non-symmetry with transmission to different medium if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT); if (sampledType) *sampledType = BxDFType(BSDF_SPECULAR | BSDF_TRANSMISSION); *pdf = 1 - F; return ft / AbsCosTheta(*wi); } }
Spectrum DiffusePRTIntegrator::Li(const Scene *scene, const Renderer *, const RayDifferential &ray, const Intersection &isect, const Sample *sample, MemoryArena &arena) const { Spectrum L = 0.f; Vector wo = -ray.d; // Compute emitted light if ray hit an area light source L += isect.Le(wo); // Evaluate BSDF at hit point BSDF *bsdf = isect.GetBSDF(ray, arena); const Point &p = bsdf->dgShading.p; const Normal &n = bsdf->dgShading.nn; // Compute reflected radiance using diffuse PRT // Project diffuse transfer function at point to SH Spectrum *c_transfer = arena.Alloc<Spectrum>(SHTerms(lmax)); SHComputeDiffuseTransfer(p, Faceforward(n, wo), isect.rayEpsilon, scene, *sample->rng, nSamples, lmax, c_transfer); // Compute integral of product of incident radiance and transfer function Spectrum LT = 0.f; for (int i = 0; i < SHTerms(lmax); ++i) LT += c_in[i] * c_transfer[i]; // Compute reflectance at point for diffuse transfer const int sqrtRhoSamples = 6; float rhoRSamples[2*sqrtRhoSamples*sqrtRhoSamples]; StratifiedSample2D(rhoRSamples, sqrtRhoSamples, sqrtRhoSamples, *sample->rng); Spectrum Kd = bsdf->rho(wo, sqrtRhoSamples*sqrtRhoSamples, rhoRSamples, BSDF_ALL_REFLECTION) * INV_PI; return L + Kd * LT.Clamp(); }
Spectrum IrradianceCacheIntegrator::indirectLo(const Point &p, const Normal &ng, float pixelSpacing, const Vector &wo, float rayEpsilon, BSDF *bsdf, BxDFType flags, RNG &rng, const Scene *scene, const Renderer *renderer, MemoryArena &arena) const { if (bsdf->NumComponents(flags) == 0) return Spectrum(0.); Spectrum E; Vector wi; // Get irradiance _E_ and average incident direction _wi_ at point _p_ if (!interpolateE(scene, p, ng, &E, &wi)) { // Compute irradiance at current point PBRT_IRRADIANCE_CACHE_STARTED_COMPUTING_IRRADIANCE(const_cast<Point *>(&p), const_cast<Normal *>(&ng)); uint32_t scramble[2] = { rng.RandomUInt(), rng.RandomUInt() }; float minHitDistance = INFINITY; Vector wAvg(0,0,0); Spectrum LiSum = 0.f; for (int i = 0; i < nSamples; ++i) { // Sample direction for irradiance estimate ray float u[2]; Sample02(i, scramble, u); Vector w = CosineSampleHemisphere(u[0], u[1]); RayDifferential r(p, bsdf->LocalToWorld(w), rayEpsilon); r.d = Faceforward(r.d, ng); // Trace ray to sample radiance for irradiance estimate PBRT_IRRADIANCE_CACHE_STARTED_RAY(&r); Spectrum L = pathL(r, scene, renderer, rng, arena); LiSum += L; wAvg += r.d * L.y(); minHitDistance = min(minHitDistance, r.maxt); PBRT_IRRADIANCE_CACHE_FINISHED_RAY(&r, r.maxt, &L); } E = (M_PI / float(nSamples)) * LiSum; PBRT_IRRADIANCE_CACHE_FINISHED_COMPUTING_IRRADIANCE(const_cast<Point *>(&p), const_cast<Normal *>(&ng)); // Add computed irradiance value to cache // Compute irradiance sample's contribution extent and bounding box float maxDist = maxSamplePixelSpacing * pixelSpacing; float minDist = minSamplePixelSpacing * pixelSpacing; float contribExtent = Clamp(minHitDistance / 2.f, minDist, maxDist); BBox sampleExtent(p); sampleExtent.Expand(contribExtent); PBRT_IRRADIANCE_CACHE_ADDED_NEW_SAMPLE(const_cast<Point *>(&p), const_cast<Normal *>(&ng), contribExtent, &E, &wAvg, pixelSpacing); // Allocate _IrradianceSample_, get write lock, add to octree IrradianceSample *sample = new IrradianceSample(E, p, ng, wAvg, contribExtent); RWMutexLock lock(*mutex, WRITE); octree->Add(sample, sampleExtent); wi = wAvg; } // Compute reflected radiance due to irradiance and BSDF if (wi.LengthSquared() == 0.f) return Spectrum(0.); return bsdf->f(wo, Normalize(wi), flags) * E; }
Spectrum LPhoton(KdTree<Photon> *map, int nPaths, int nLookup, MemoryArena &arena, BSDF *bsdf, RNG &rng, const Intersection &isect, const Vector &wo, float maxDistSquared) { Spectrum L(0.); BxDFType nonSpecular = BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_DIFFUSE | BSDF_GLOSSY); if (map && bsdf->NumComponents(nonSpecular) > 0) { PBRT_PHOTON_MAP_STARTED_LOOKUP(const_cast<DifferentialGeometry *>(&isect.dg)); // Do photon map lookup at intersection point PhotonProcess proc(nLookup, arena.Alloc<ClosePhoton>(nLookup)); map->Lookup(isect.dg.p, proc, maxDistSquared); // Estimate reflected radiance due to incident photons ClosePhoton *photons = proc.photons; int nFound = proc.nFound; Normal Nf = Faceforward(bsdf->dgShading.nn, wo); if (bsdf->NumComponents(BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_GLOSSY)) > 0) { // Compute exitant radiance from photons for glossy surface for (int i = 0; i < nFound; ++i) { const Photon *p = photons[i].photon; float k = kernel(p, isect.dg.p, maxDistSquared); L += (k / (nPaths * maxDistSquared)) * bsdf->f(wo, p->wi) * p->alpha; } } else { // Compute exitant radiance from photons for diffuse surface Spectrum Lr(0.), Lt(0.); for (int i = 0; i < nFound; ++i) { if (Dot(Nf, photons[i].photon->wi) > 0.f) { float k = kernel(photons[i].photon, isect.dg.p, maxDistSquared); Lr += (k / (nPaths * maxDistSquared)) * photons[i].photon->alpha; } else { float k = kernel(photons[i].photon, isect.dg.p, maxDistSquared); Lt += (k / (nPaths * maxDistSquared)) * photons[i].photon->alpha; } } const int sqrtRhoSamples = 4; float rhoRSamples[2*sqrtRhoSamples*sqrtRhoSamples]; StratifiedSample2D(rhoRSamples, sqrtRhoSamples, sqrtRhoSamples, rng); float rhoTSamples[2*sqrtRhoSamples*sqrtRhoSamples]; StratifiedSample2D(rhoTSamples, sqrtRhoSamples, sqrtRhoSamples, rng); L += Lr * bsdf->rho(wo, sqrtRhoSamples*sqrtRhoSamples, rhoRSamples, BSDF_ALL_REFLECTION) * INV_PI + Lt * bsdf->rho(wo, sqrtRhoSamples*sqrtRhoSamples, rhoTSamples, BSDF_ALL_TRANSMISSION) * INV_PI; } PBRT_PHOTON_MAP_FINISHED_LOOKUP(const_cast<DifferentialGeometry *>(&isect.dg), proc.nFound, proc.nLookup, &L); } return L; }
void SurfaceInteraction::SetShadingGeometry(const Vector3f &dpdus, const Vector3f &dpdvs, const Normal3f &dndus, const Normal3f &dndvs, bool orientationIsAuthoritative) { // Compute _shading.n_ for _SurfaceInteraction_ shading.n = Normalize((Normal3f)Cross(dpdus, dpdvs)); if (shape && (shape->reverseOrientation ^ shape->transformSwapsHandedness)) shading.n = -shading.n; if (orientationIsAuthoritative) n = Faceforward(n, shading.n); else shading.n = Faceforward(shading.n, n); // Initialize _shading_ partial derivative values shading.dpdu = dpdus; shading.dpdv = dpdvs; shading.dndu = dndus; shading.dndv = dndvs; }
Spectrum SpecularTransmission::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample, Float *pdf, BxDFType *sampledType) const { // Figure out which $\eta$ is incident and which is transmitted bool entering = CosTheta(wo) > 0; Float etaI = entering ? etaA : etaB; Float etaT = entering ? etaB : etaA; // Compute ray direction for specular transmission if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi)) return 0; *pdf = 1; Spectrum ft = T * (Spectrum(1.) - fresnel.Evaluate(CosTheta(*wi))); // Account for non-symmetry with transmission to different medium if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT); return ft / AbsCosTheta(*wi); }
// AmbientOcclusionIntegrator Method Definitions Spectrum AmbientOcclusionIntegrator::Li(const Scene *scene, const Renderer *renderer, const RayDifferential &ray, const Intersection &isect, const Sample *sample, RNG &rng, MemoryArena &arena, int wavelength) const { BSDF *bsdf = isect.GetBSDF(ray, arena, wavelength); const Point &p = bsdf->dgShading.p; Normal n = Faceforward(isect.dg.nn, -ray.d); uint32_t scramble[2] = { rng.RandomUInt(), rng.RandomUInt() }; float u[2]; int nClear = 0; for (int i = 0; i < nSamples; ++i) { Sample02(i, scramble, u); Vector w = UniformSampleSphere(u[0], u[1]); if (Dot(w, n) < 0.) w = -w; Ray r(p, w, .01f, maxDist); if (!scene->IntersectP(r)) ++nClear; } return Spectrum(float(nClear) / float(nSamples)); }
bool RealisticCamera::IntersectSphericalElement(Float radius, Float zCenter, const Ray &ray, Float *t, Normal3f *n) { // Compute _t0_ and _t1_ for ray--element intersection Point3f o = ray.o - Vector3f(0, 0, zCenter); Float A = ray.d.x * ray.d.x + ray.d.y * ray.d.y + ray.d.z * ray.d.z; Float B = 2 * (ray.d.x * o.x + ray.d.y * o.y + ray.d.z * o.z); Float C = o.x * o.x + o.y * o.y + o.z * o.z - radius * radius; Float t0, t1; if (!Quadratic(A, B, C, &t0, &t1)) return false; // Select intersection $t$ based on ray direction and element curvature bool useCloserT = (ray.d.z > 0) ^ (radius < 0); *t = useCloserT ? std::min(t0, t1) : std::max(t0, t1); if (*t < 0) return false; // Compute surface normal of element at ray intersection point *n = Normal3f(Vector3f(o + *t * ray.d)); *n = Faceforward(Normalize(*n), -ray.d); return true; }
void Material::Bump(const Reference<Texture<float> > &d, const DifferentialGeometry &dgGeom, const DifferentialGeometry &dgs, DifferentialGeometry *dgBump) { // Compute offset positions and evaluate displacement texture DifferentialGeometry dgEval = dgs; // Shift _dgEval_ _du_ in the $u$ direction float du = .5f * (fabsf(dgs.dudx) + fabsf(dgs.dudy)); if (du == 0.f) du = .01f; dgEval.p = dgs.p + du * dgs.dpdu; dgEval.u = dgs.u + du; dgEval.nn = Normalize((Normal)Cross(dgs.dpdu, dgs.dpdv) + du * dgs.dndu); float uDisplace = d->Evaluate(dgEval); // Shift _dgEval_ _dv_ in the $v$ direction float dv = .5f * (fabsf(dgs.dvdx) + fabsf(dgs.dvdy)); if (dv == 0.f) dv = .01f; dgEval.p = dgs.p + dv * dgs.dpdv; dgEval.u = dgs.u; dgEval.v = dgs.v + dv; dgEval.nn = Normalize((Normal)Cross(dgs.dpdu, dgs.dpdv) + dv * dgs.dndv); float vDisplace = d->Evaluate(dgEval); float displace = d->Evaluate(dgs); // Compute bump-mapped differential geometry *dgBump = dgs; dgBump->dpdu = dgs.dpdu + (uDisplace - displace) / du * Vector(dgs.nn) + displace * Vector(dgs.dndu); dgBump->dpdv = dgs.dpdv + (vDisplace - displace) / dv * Vector(dgs.nn) + displace * Vector(dgs.dndv); dgBump->nn = Normal(Normalize(Cross(dgBump->dpdu, dgBump->dpdv))); if (dgs.shape->ReverseOrientation ^ dgs.shape->TransformSwapsHandedness) dgBump->nn *= -1.f; // Orient shading normal to match geometric normal dgBump->nn = Faceforward(dgBump->nn, dgGeom.nn); }
Spectrum IrradianceCacheIntegrator::Li(const Scene *scene, const Renderer *renderer, const RayDifferential &ray, const Intersection &isect, const Sample *sample, RNG &rng, MemoryArena &arena) const { Spectrum L(0.); // Evaluate BSDF at hit point BSDF *bsdf = isect.GetBSDF(ray, arena); Vector wo = -ray.d; const Point &p = bsdf->dgShading.p; const Normal &n = bsdf->dgShading.nn; L += isect.Le(wo); // Compute direct lighting for irradiance cache L += UniformSampleAllLights(scene, renderer, arena, p, n, wo, isect.rayEpsilon, ray.time, bsdf, sample, rng, lightSampleOffsets, bsdfSampleOffsets); // Compute indirect lighting for irradiance cache if (ray.depth + 1 < maxSpecularDepth) { Vector wi; // Trace rays for specular reflection and refraction L += SpecularReflect(ray, bsdf, rng, isect, renderer, scene, sample, arena); L += SpecularTransmit(ray, bsdf, rng, isect, renderer, scene, sample, arena); } // Estimate indirect lighting with irradiance cache Normal ng = isect.dg.nn; ng = Faceforward(ng, wo); // Compute pixel spacing in world space at intersection point float pixelSpacing = sqrtf(Cross(isect.dg.dpdx, isect.dg.dpdy).Length()); BxDFType flags = BxDFType(BSDF_REFLECTION | BSDF_DIFFUSE | BSDF_GLOSSY); L += indirectLo(p, ng, pixelSpacing, wo, isect.rayEpsilon, bsdf, flags, rng, scene, renderer, arena); flags = BxDFType(BSDF_TRANSMISSION | BSDF_DIFFUSE | BSDF_GLOSSY); L += indirectLo(p, -ng, pixelSpacing, wo, isect.rayEpsilon, bsdf, flags, rng, scene, renderer, arena); return L; }
Spectrum UseRadianceProbes::Li(const Scene *scene, const Renderer *renderer, const RayDifferential &ray, const Intersection &isect, const Sample *sample, RNG &rng, MemoryArena &arena, int wavelength) const { Spectrum L(0.); Vector wo = -ray.d; // Compute emitted light if ray hit an area light source L += isect.Le(wo); // Evaluate BSDF at hit point BSDF *bsdf = isect.GetBSDF(ray, arena, wavelength); const Point &p = bsdf->dgShading.p; const Normal &n = bsdf->dgShading.nn; // Compute reflection for radiance probes integrator if (!includeDirectInProbes) L += UniformSampleAllLights(scene, renderer, arena, p, n, wo, isect.rayEpsilon, ray.time, bsdf, sample, rng, lightSampleOffsets, bsdfSampleOffsets); // Compute reflected lighting using radiance probes // Compute probe coordinates and offsets for lookup point Vector offset = bbox.Offset(p); float voxx = (offset.x * nProbes[0]) - 0.5f; float voxy = (offset.y * nProbes[1]) - 0.5f; float voxz = (offset.z * nProbes[2]) - 0.5f; int vx = Floor2Int(voxx), vy = Floor2Int(voxy), vz = Floor2Int(voxz); float dx = voxx - vx, dy = voxy - vy, dz = voxz - vz; // Get radiance probe coefficients around lookup point const Spectrum *b000 = c_inXYZ(lmax, vx, vy, vz); const Spectrum *b100 = c_inXYZ(lmax, vx+1, vy, vz); const Spectrum *b010 = c_inXYZ(lmax, vx, vy+1, vz); const Spectrum *b110 = c_inXYZ(lmax, vx+1, vy+1, vz); const Spectrum *b001 = c_inXYZ(lmax, vx, vy, vz+1); const Spectrum *b101 = c_inXYZ(lmax, vx+1, vy, vz+1); const Spectrum *b011 = c_inXYZ(lmax, vx, vy+1, vz+1); const Spectrum *b111 = c_inXYZ(lmax, vx+1, vy+1, vz+1); // Compute incident radiance from radiance probe coefficients Spectrum *c_inp = arena.Alloc<Spectrum>(SHTerms(lmax)); for (int i = 0; i < SHTerms(lmax); ++i) { // Do trilinear interpolation to compute SH coefficients at point Spectrum c00 = Lerp(dx, b000[i], b100[i]); Spectrum c10 = Lerp(dx, b010[i], b110[i]); Spectrum c01 = Lerp(dx, b001[i], b101[i]); Spectrum c11 = Lerp(dx, b011[i], b111[i]); Spectrum c0 = Lerp(dy, c00, c10); Spectrum c1 = Lerp(dy, c01, c11); c_inp[i] = Lerp(dz, c0, c1); } // Convolve incident radiance to compute irradiance function Spectrum *c_E = arena.Alloc<Spectrum>(SHTerms(lmax)); SHConvolveCosTheta(lmax, c_inp, c_E); // Evaluate irradiance function and accumulate reflection Spectrum rho = bsdf->rho(wo, rng, BSDF_ALL_REFLECTION); float *Ylm = ALLOCA(float, SHTerms(lmax)); SHEvaluate(Vector(Faceforward(n, wo)), lmax, Ylm); Spectrum E = 0.f; for (int i = 0; i < SHTerms(lmax); ++i) E += c_E[i] * Ylm[i]; L += rho * INV_PI * E.Clamp(); return L; }
Spectrum PhotonIntegrator::Li(const Scene *scene, const Renderer *renderer, const RayDifferential &ray, const Intersection &isect, const Sample *sample, RNG &rng, MemoryArena &arena) const { Spectrum L(0.); Vector wo = -ray.d; // Compute emitted light if ray hit an area light source L += isect.Le(wo); // Evaluate BSDF at hit pbrt::Point BSDF *bsdf = isect.GetBSDF(ray, arena); const pbrt::Point &p = bsdf->dgShading.p; const Normal &n = bsdf->dgShading.nn; L += UniformSampleAllLights(scene, renderer, arena, p, n, wo, isect.rayEpsilon, ray.time, bsdf, sample, rng, lightSampleOffsets, bsdfSampleOffsets); // Compute caustic lighting for photon map integrator ClosePhoton *lookupBuf = arena.Alloc<ClosePhoton>(nLookup); L += LPhoton(causticMap, nCausticPaths, nLookup, lookupBuf, bsdf, rng, isect, wo, maxDistSquared); // Compute indirect lighting for photon map integrator if (finalGather && indirectMap != NULL) { #if 1 // Do one-bounce final gather for photon map BxDFType nonSpecular = BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_DIFFUSE | BSDF_GLOSSY); if (bsdf->NumComponents(nonSpecular) > 0) { // Find indirect photons around point for importance sampling const uint32_t nIndirSamplePhotons = 50; PhotonProcess proc(nIndirSamplePhotons, arena.Alloc<ClosePhoton>(nIndirSamplePhotons)); float searchDist2 = maxDistSquared; while (proc.nFound < nIndirSamplePhotons) { float md2 = searchDist2; proc.nFound = 0; indirectMap->Lookup(p, proc, md2); searchDist2 *= 2.f; } // Copy photon directions to local array Vector *photonDirs = arena.Alloc<Vector>(nIndirSamplePhotons); for (uint32_t i = 0; i < nIndirSamplePhotons; ++i) photonDirs[i] = proc.photons[i].photon->wi; // Use BSDF to do final gathering Spectrum Li = 0.; for (int i = 0; i < gatherSamples; ++i) { // Sample random direction from BSDF for final gather ray Vector wi; float pdf; BSDFSample bsdfSample(sample, bsdfGatherSampleOffsets, i); Spectrum fr = bsdf->Sample_f(wo, &wi, bsdfSample, &pdf, BxDFType(BSDF_ALL & ~BSDF_SPECULAR)); if (fr.IsBlack() || pdf == 0.f) continue; Assert(pdf >= 0.f); // Trace BSDF final gather ray and accumulate radiance RayDifferential bounceRay(p, wi, ray, isect.rayEpsilon); Intersection gatherIsect; if (scene->Intersect(bounceRay, &gatherIsect)) { // Compute exitant radiance _Lindir_ using radiance photons Spectrum Lindir = 0.f; Normal nGather = gatherIsect.dg.nn; nGather = Faceforward(nGather, -bounceRay.d); RadiancePhotonProcess proc(nGather); float md2 = INFINITY; radianceMap->Lookup(gatherIsect.dg.p, proc, md2); if (proc.photon != NULL) Lindir = proc.photon->Lo; Lindir *= renderer->Transmittance(scene, bounceRay, NULL, rng, arena); // Compute MIS weight for BSDF-sampled gather ray // Compute PDF for photon-sampling of direction _wi_ float photonPdf = 0.f; float conePdf = UniformConePdf(cosGatherAngle); for (uint32_t j = 0; j < nIndirSamplePhotons; ++j) if (Dot(photonDirs[j], wi) > .999f * cosGatherAngle) photonPdf += conePdf; photonPdf /= nIndirSamplePhotons; float wt = PowerHeuristic(gatherSamples, pdf, gatherSamples, photonPdf); Li += fr * Lindir * (AbsDot(wi, n) * wt / pdf); } } L += Li / gatherSamples; // Use nearby photons to do final gathering Li = 0.; for (int i = 0; i < gatherSamples; ++i) { // Sample random direction using photons for final gather ray BSDFSample gatherSample(sample, indirGatherSampleOffsets, i); int photonNum = min((int)nIndirSamplePhotons - 1, Floor2Int(gatherSample.uComponent * nIndirSamplePhotons)); // Sample gather ray direction from _photonNum_ Vector vx, vy; CoordinateSystem(photonDirs[photonNum], &vx, &vy); Vector wi = UniformSampleCone(gatherSample.uDir[0], gatherSample.uDir[1], cosGatherAngle, vx, vy, photonDirs[photonNum]); // Trace photon-sampled final gather ray and accumulate radiance Spectrum fr = bsdf->f(wo, wi); if (fr.IsBlack()) continue; RayDifferential bounceRay(p, wi, ray, isect.rayEpsilon); Intersection gatherIsect; PBRT_PHOTON_MAP_STARTED_GATHER_RAY(&bounceRay); if (scene->Intersect(bounceRay, &gatherIsect)) { // Compute exitant radiance _Lindir_ using radiance photons Spectrum Lindir = 0.f; Normal nGather = gatherIsect.dg.nn; nGather = Faceforward(nGather, -bounceRay.d); RadiancePhotonProcess proc(nGather); float md2 = INFINITY; radianceMap->Lookup(gatherIsect.dg.p, proc, md2); if (proc.photon != NULL) Lindir = proc.photon->Lo; Lindir *= renderer->Transmittance(scene, bounceRay, NULL, rng, arena); // Compute PDF for photon-sampling of direction _wi_ float photonPdf = 0.f; float conePdf = UniformConePdf(cosGatherAngle); for (uint32_t j = 0; j < nIndirSamplePhotons; ++j) if (Dot(photonDirs[j], wi) > .999f * cosGatherAngle) photonPdf += conePdf; photonPdf /= nIndirSamplePhotons; // Compute MIS weight for photon-sampled gather ray float bsdfPdf = bsdf->Pdf(wo, wi); float wt = PowerHeuristic(gatherSamples, photonPdf, gatherSamples, bsdfPdf); Li += fr * Lindir * AbsDot(wi, n) * wt / photonPdf; } PBRT_PHOTON_MAP_FINISHED_GATHER_RAY(&bounceRay); } L += Li / gatherSamples; } #else // for debugging / examples: use the photon map directly Normal nn = Faceforward(n, -ray.d); RadiancePhotonProcess proc(nn); float md2 = INFINITY; radianceMap->Lookup(p, proc, md2); if (proc.photon) L += proc.photon->Lo; #endif } else L += LPhoton(indirectMap, nIndirectPaths, nLookup, lookupBuf, bsdf, rng, isect, wo, maxDistSquared); if (ray.depth+1 < maxSpecularDepth) { Vector wi; // Trace rays for specular reflection and refraction L += SpecularReflect(ray, bsdf, rng, isect, renderer, scene, sample, arena); L += SpecularTransmit(ray, bsdf, rng, isect, renderer, scene, sample, arena); } return L; }
void PhotonShootingTask::Run() { // Declare local variables for _PhotonShootingTask_ MemoryArena arena; RNG rng(31 * taskNum); vector<Photon> localDirectPhotons, localIndirectPhotons, localCausticPhotons; vector<RadiancePhoton> localRadiancePhotons; uint32_t totalPaths = 0; bool causticDone = (integrator->nCausticPhotonsWanted == 0); bool indirectDone = (integrator->nIndirectPhotonsWanted == 0); PermutedHalton halton(6, rng); vector<Spectrum> localRpReflectances, localRpTransmittances; while (true) { // Follow photon paths for a block of samples const uint32_t blockSize = 4096; for (uint32_t i = 0; i < blockSize; ++i) { float u[6]; halton.Sample(++totalPaths, u); // Choose light to shoot photon from float lightPdf; int lightNum = lightDistribution->SampleDiscrete(u[0], &lightPdf); const Light *light = scene->lights[lightNum]; // Generate _photonRay_ from light source and initialize _alpha_ RayDifferential photonRay; float pdf; LightSample ls(u[1], u[2], u[3]); Normal Nl; Spectrum Le = light->Sample_L(scene, ls, u[4], u[5], time, &photonRay, &Nl, &pdf); if (pdf == 0.f || Le.IsBlack()) continue; Spectrum alpha = (AbsDot(Nl, photonRay.d) * Le) / (pdf * lightPdf); if (!alpha.IsBlack()) { // Follow photon path through scene and record intersections PBRT_PHOTON_MAP_STARTED_RAY_PATH(&photonRay, &alpha); bool specularPath = true; Intersection photonIsect; int nIntersections = 0; while (scene->Intersect(photonRay, &photonIsect)) { ++nIntersections; // Handle photon/surface intersection alpha *= renderer->Transmittance(scene, photonRay, NULL, rng, arena); BSDF *photonBSDF = photonIsect.GetBSDF(photonRay, arena); BxDFType specularType = BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_SPECULAR); bool hasNonSpecular = (photonBSDF->NumComponents() > photonBSDF->NumComponents(specularType)); Vector wo = -photonRay.d; if (hasNonSpecular) { // Deposit photon at surface Photon photon(photonIsect.dg.p, alpha, wo); bool depositedPhoton = false; if (specularPath && nIntersections > 1) { if (!causticDone) { PBRT_PHOTON_MAP_DEPOSITED_CAUSTIC_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localCausticPhotons.push_back(photon); } } else { // Deposit either direct or indirect photon // stop depositing direct photons once indirectDone is true; don't // want to waste memory storing too many if we're going a long time // trying to get enough caustic photons desposited. if (nIntersections == 1 && !indirectDone && integrator->finalGather) { PBRT_PHOTON_MAP_DEPOSITED_DIRECT_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localDirectPhotons.push_back(photon); } else if (nIntersections > 1 && !indirectDone) { PBRT_PHOTON_MAP_DEPOSITED_INDIRECT_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localIndirectPhotons.push_back(photon); } } // Possibly create radiance photon at photon intersection point if (depositedPhoton && integrator->finalGather && rng.RandomFloat() < .125f) { Normal n = photonIsect.dg.nn; n = Faceforward(n, -photonRay.d); localRadiancePhotons.push_back(RadiancePhoton(photonIsect.dg.p, n)); Spectrum rho_r = photonBSDF->rho(rng, BSDF_ALL_REFLECTION); localRpReflectances.push_back(rho_r); Spectrum rho_t = photonBSDF->rho(rng, BSDF_ALL_TRANSMISSION); localRpTransmittances.push_back(rho_t); } } if (nIntersections >= integrator->maxPhotonDepth) break; // Sample new photon ray direction Vector wi; float pdf; BxDFType flags; Spectrum fr = photonBSDF->Sample_f(wo, &wi, BSDFSample(rng), &pdf, BSDF_ALL, &flags); if (fr.IsBlack() || pdf == 0.f) break; Spectrum anew = alpha * fr * AbsDot(wi, photonBSDF->dgShading.nn) / pdf; // Possibly terminate photon path with Russian roulette float continueProb = min(1.f, anew.y() / alpha.y()); if (rng.RandomFloat() > continueProb) break; alpha = anew / continueProb; specularPath &= ((flags & BSDF_SPECULAR) != 0); if (indirectDone && !specularPath) break; photonRay = RayDifferential(photonIsect.dg.p, wi, photonRay, photonIsect.rayEpsilon); } PBRT_PHOTON_MAP_FINISHED_RAY_PATH(&photonRay, &alpha); } arena.FreeAll(); } // Merge local photon data with data in _PhotonIntegrator_ { MutexLock lock(mutex); // Give up if we're not storing enough photons if (abortTasks) return; if (nshot > 500000 && (unsuccessful(integrator->nCausticPhotonsWanted, causticPhotons.size(), blockSize) || unsuccessful(integrator->nIndirectPhotonsWanted, indirectPhotons.size(), blockSize))) { Error("Unable to store enough photons. Giving up.\n"); causticPhotons.erase(causticPhotons.begin(), causticPhotons.end()); indirectPhotons.erase(indirectPhotons.begin(), indirectPhotons.end()); radiancePhotons.erase(radiancePhotons.begin(), radiancePhotons.end()); abortTasks = true; return; } progress.Update(localIndirectPhotons.size() + localCausticPhotons.size()); nshot += blockSize; // Merge indirect photons into shared array if (!indirectDone) { integrator->nIndirectPaths += blockSize; for (uint32_t i = 0; i < localIndirectPhotons.size(); ++i) indirectPhotons.push_back(localIndirectPhotons[i]); localIndirectPhotons.erase(localIndirectPhotons.begin(), localIndirectPhotons.end()); if (indirectPhotons.size() >= integrator->nIndirectPhotonsWanted) indirectDone = true; nDirectPaths += blockSize; for (uint32_t i = 0; i < localDirectPhotons.size(); ++i) directPhotons.push_back(localDirectPhotons[i]); localDirectPhotons.erase(localDirectPhotons.begin(), localDirectPhotons.end()); } // Merge direct, caustic, and radiance photons into shared array if (!causticDone) { integrator->nCausticPaths += blockSize; for (uint32_t i = 0; i < localCausticPhotons.size(); ++i) causticPhotons.push_back(localCausticPhotons[i]); localCausticPhotons.erase(localCausticPhotons.begin(), localCausticPhotons.end()); if (causticPhotons.size() >= integrator->nCausticPhotonsWanted) causticDone = true; } for (uint32_t i = 0; i < localRadiancePhotons.size(); ++i) radiancePhotons.push_back(localRadiancePhotons[i]); localRadiancePhotons.erase(localRadiancePhotons.begin(), localRadiancePhotons.end()); for (uint32_t i = 0; i < localRpReflectances.size(); ++i) rpReflectances.push_back(localRpReflectances[i]); localRpReflectances.erase(localRpReflectances.begin(), localRpReflectances.end()); for (uint32_t i = 0; i < localRpTransmittances.size(); ++i) rpTransmittances.push_back(localRpTransmittances[i]); localRpTransmittances.erase(localRpTransmittances.begin(), localRpTransmittances.end()); } // Exit task if enough photons have been found if (indirectDone && causticDone) break; } }
void CreateRadianceProbes::Render(const Scene *scene) { // Compute scene bounds and initialize probe integrators if (bbox.pMin.x > bbox.pMax.x) bbox = scene->WorldBound(); surfaceIntegrator->Preprocess(scene, camera, this); volumeIntegrator->Preprocess(scene, camera, this); Sample *origSample = new Sample(NULL, surfaceIntegrator, volumeIntegrator, scene); // Compute sampling rate in each dimension Vector delta = bbox.pMax - bbox.pMin; int nProbes[3]; for (int i = 0; i < 3; ++i) nProbes[i] = max(1, Ceil2Int(delta[i] / probeSpacing)); // Allocate SH coefficient vector pointers for sample points int count = nProbes[0] * nProbes[1] * nProbes[2]; Spectrum **c_in = new Spectrum *[count]; for (int i = 0; i < count; ++i) c_in[i] = new Spectrum[SHTerms(lmax)]; // Compute random points on surfaces of scene // Create scene bounding sphere to catch rays that leave the scene Point sceneCenter; float sceneRadius; scene->WorldBound().BoundingSphere(&sceneCenter, &sceneRadius); Transform ObjectToWorld(Translate(sceneCenter - Point(0,0,0))); Transform WorldToObject(Inverse(ObjectToWorld)); Reference<Shape> sph = new Sphere(&ObjectToWorld, &WorldToObject, true, sceneRadius, -sceneRadius, sceneRadius, 360.f); Reference<Material> nullMaterial = Reference<Material>(NULL); GeometricPrimitive sphere(sph, nullMaterial, NULL); vector<Point> surfacePoints; uint32_t nPoints = 32768, maxDepth = 32; surfacePoints.reserve(nPoints + maxDepth); Point pCamera = camera->CameraToWorld(camera->shutterOpen, Point(0, 0, 0)); surfacePoints.push_back(pCamera); RNG rng; while (surfacePoints.size() < nPoints) { // Generate random path from camera and deposit surface points Point pray = pCamera; Vector dir = UniformSampleSphere(rng.RandomFloat(), rng.RandomFloat()); float rayEpsilon = 0.f; for (uint32_t i = 0; i < maxDepth; ++i) { Ray ray(pray, dir, rayEpsilon, INFINITY, time); Intersection isect; if (!scene->Intersect(ray, &isect) && !sphere.Intersect(ray, &isect)) break; surfacePoints.push_back(ray(ray.maxt)); DifferentialGeometry &hitGeometry = isect.dg; pray = isect.dg.p; rayEpsilon = isect.rayEpsilon; hitGeometry.nn = Faceforward(hitGeometry.nn, -ray.d); dir = UniformSampleSphere(rng.RandomFloat(), rng.RandomFloat()); dir = Faceforward(dir, hitGeometry.nn); } } // Launch tasks to compute radiance probes at sample points vector<Task *> tasks; ProgressReporter prog(count, "Radiance Probes"); for (int i = 0; i < count; ++i) tasks.push_back(new CreateRadProbeTask(i, nProbes, time, bbox, lmax, includeDirectInProbes, includeIndirectInProbes, nIndirSamples, prog, origSample, surfacePoints, scene, this, c_in[i])); EnqueueTasks(tasks); WaitForAllTasks(); for (uint32_t i = 0; i < tasks.size(); ++i) delete tasks[i]; prog.Done(); // Write radiance probe coefficients to file FILE *f = fopen(filename.c_str(), "w"); if (f) { if (fprintf(f, "%d %d %d\n", lmax, includeDirectInProbes?1:0, includeIndirectInProbes?1:0) < 0 || fprintf(f, "%d %d %d\n", nProbes[0], nProbes[1], nProbes[2]) < 0 || fprintf(f, "%f %f %f %f %f %f\n", bbox.pMin.x, bbox.pMin.y, bbox.pMin.z, bbox.pMax.x, bbox.pMax.y, bbox.pMax.z) < 0) { Error("Error writing radiance file \"%s\" (%s)", filename.c_str(), strerror(errno)); exit(1); } for (int i = 0; i < nProbes[0] * nProbes[1] * nProbes[2]; ++i) { for (int j = 0; j < SHTerms(lmax); ++j) { fprintf(f, " "); if (c_in[i][j].Write(f) == false) { Error("Error writing radiance file \"%s\" (%s)", filename.c_str(), strerror(errno)); exit(1); } fprintf(f, "\n"); } fprintf(f, "\n"); } fclose(f); } for (int i = 0; i < nProbes[0] * nProbes[1] * nProbes[2]; ++i) delete[] c_in[i]; delete[] c_in; delete origSample; }
void SurfacePointTask::Run() { // Declare common variables for _SurfacePointTask::Run()_ RNG rng(37 * taskNum); MemoryArena arena; vector<SurfacePoint> candidates; while (true) { int pathsTraced, raysTraced = 0; for (pathsTraced = 0; pathsTraced < 20000; ++pathsTraced) { // Follow ray path and attempt to deposit candidate sample points Vector dir = UniformSampleSphere(rng.RandomFloat(), rng.RandomFloat()); Ray ray(origin, dir, 0.f, INFINITY, time); while (ray.depth < 30) { // Find ray intersection with scene geometry or bounding sphere ++raysTraced; bool hitOnSphere = false; auto optIsect = scene.Intersect(ray); if (!optIsect) { optIsect = sphere.Intersect(ray); if (!optIsect) break; hitOnSphere = true; } DifferentialGeometry &hitGeometry = optIsect->dg; hitGeometry.nn = Faceforward(hitGeometry.nn, -ray.d); // Store candidate sample point at ray intersection if appropriate if (!hitOnSphere && ray.depth >= 3 && optIsect->GetBSSRDF(RayDifferential(ray), arena) != NULL) { float area = M_PI * (minSampleDist / 2.f) * (minSampleDist / 2.f); candidates.push_back(SurfacePoint(hitGeometry.p, hitGeometry.nn, area, optIsect->rayEpsilon)); } // Generate random ray from intersection point Vector dir = UniformSampleSphere(rng.RandomFloat(), rng.RandomFloat()); dir = Faceforward(dir, hitGeometry.nn); ray = Ray(hitGeometry.p, dir, ray, optIsect->rayEpsilon); } arena.FreeAll(); } // Make first pass through candidate points with reader lock vector<bool> candidateRejected; candidateRejected.reserve(candidates.size()); RWMutexLock lock(mutex, READ); for (uint32_t i = 0; i < candidates.size(); ++i) { PoissonCheck check(minSampleDist, candidates[i].p); octree.Lookup(candidates[i].p, check); candidateRejected.push_back(check.failed); } // Make second pass through points with writer lock and update octree lock.UpgradeToWrite(); if (repeatedFails >= maxFails) return; totalPathsTraced += pathsTraced; totalRaysTraced += raysTraced; int oldMaxRepeatedFails = maxRepeatedFails; for (uint32_t i = 0; i < candidates.size(); ++i) { if (candidateRejected[i]) { // Update for rejected candidate point ++repeatedFails; maxRepeatedFails = max(maxRepeatedFails, repeatedFails); if (repeatedFails >= maxFails) return; } else { // Recheck candidate point and possibly add to octree SurfacePoint &sp = candidates[i]; PoissonCheck check(minSampleDist, sp.p); octree.Lookup(sp.p, check); if (check.failed) { // Update for rejected candidate point ++repeatedFails; maxRepeatedFails = max(maxRepeatedFails, repeatedFails); if (repeatedFails >= maxFails) return; } else { ++numPointsAdded; repeatedFails = 0; Vector delta(minSampleDist, minSampleDist, minSampleDist); octree.Add(sp, BBox(sp.p-delta, sp.p+delta)); PBRT_SUBSURFACE_ADDED_POINT_TO_OCTREE(&sp, minSampleDist); surfacePoints.push_back(sp); } } } // Stop following paths if not finding new points if (repeatedFails > oldMaxRepeatedFails) { int delta = repeatedFails - oldMaxRepeatedFails; prog.Update(delta); } if (totalPathsTraced > 50000 && numPointsAdded == 0) { Warning("There don't seem to be any objects with BSSRDFs " "in this scene. Giving up."); return; } candidates.erase(candidates.begin(), candidates.end()); } }
bool Triangle::Intersect(const Ray &ray, Float *tHit, SurfaceInteraction *isect, bool testAlphaTexture) const { ProfilePhase p(Prof::TriIntersect); ++nTests; // Get triangle vertices in _p0_, _p1_, and _p2_ const Point3f &p0 = mesh->p[v[0]]; const Point3f &p1 = mesh->p[v[1]]; const Point3f &p2 = mesh->p[v[2]]; // Perform ray--triangle intersection test // Transform triangle vertices to ray coordinate space // Translate vertices based on ray origin Point3f p0t = p0 - Vector3f(ray.o); Point3f p1t = p1 - Vector3f(ray.o); Point3f p2t = p2 - Vector3f(ray.o); // Permute components of triangle vertices and ray direction int kz = MaxDimension(Abs(ray.d)); int kx = kz + 1; if (kx == 3) kx = 0; int ky = kx + 1; if (ky == 3) ky = 0; Vector3f d = Permute(ray.d, kx, ky, kz); p0t = Permute(p0t, kx, ky, kz); p1t = Permute(p1t, kx, ky, kz); p2t = Permute(p2t, kx, ky, kz); // Apply shear transformation to translated vertex positions Float Sx = -d.x / d.z; Float Sy = -d.y / d.z; Float Sz = 1.f / d.z; p0t.x += Sx * p0t.z; p0t.y += Sy * p0t.z; p1t.x += Sx * p1t.z; p1t.y += Sy * p1t.z; p2t.x += Sx * p2t.z; p2t.y += Sy * p2t.z; // Compute edge function coefficients _e0_, _e1_, and _e2_ Float e0 = p1t.x * p2t.y - p1t.y * p2t.x; Float e1 = p2t.x * p0t.y - p2t.y * p0t.x; Float e2 = p0t.x * p1t.y - p0t.y * p1t.x; // Fall back to double precision test at triangle edges if (sizeof(Float) == sizeof(float) && (e0 == 0.0f || e1 == 0.0f || e2 == 0.0f)) { double p2txp1ty = (double)p2t.x * (double)p1t.y; double p2typ1tx = (double)p2t.y * (double)p1t.x; e0 = (float)(p2typ1tx - p2txp1ty); double p0txp2ty = (double)p0t.x * (double)p2t.y; double p0typ2tx = (double)p0t.y * (double)p2t.x; e1 = (float)(p0typ2tx - p0txp2ty); double p1txp0ty = (double)p1t.x * (double)p0t.y; double p1typ0tx = (double)p1t.y * (double)p0t.x; e2 = (float)(p1typ0tx - p1txp0ty); } // Perform triangle edge and determinant tests if ((e0 < 0 || e1 < 0 || e2 < 0) && (e0 > 0 || e1 > 0 || e2 > 0)) return false; Float det = e0 + e1 + e2; if (det == 0) return false; // Compute scaled hit distance to triangle and test against ray $t$ range p0t.z *= Sz; p1t.z *= Sz; p2t.z *= Sz; Float tScaled = e0 * p0t.z + e1 * p1t.z + e2 * p2t.z; if (det < 0 && (tScaled >= 0 || tScaled < ray.tMax * det)) return false; else if (det > 0 && (tScaled <= 0 || tScaled > ray.tMax * det)) return false; // Compute barycentric coordinates and $t$ value for triangle intersection Float invDet = 1 / det; Float b0 = e0 * invDet; Float b1 = e1 * invDet; Float b2 = e2 * invDet; Float t = tScaled * invDet; // Ensure that computed triangle $t$ is conservatively greater than zero // Compute $\delta_z$ term for triangle $t$ error bounds Float maxZt = MaxComponent(Abs(Vector3f(p0t.z, p1t.z, p2t.z))); Float deltaZ = gamma(3) * maxZt; // Compute $\delta_x$ and $\delta_y$ terms for triangle $t$ error bounds Float maxXt = MaxComponent(Abs(Vector3f(p0t.x, p1t.x, p2t.x))); Float maxYt = MaxComponent(Abs(Vector3f(p0t.y, p1t.y, p2t.y))); Float deltaX = gamma(5) * (maxXt + maxZt); Float deltaY = gamma(5) * (maxYt + maxZt); // Compute $\delta_e$ term for triangle $t$ error bounds Float deltaE = 2 * (gamma(2) * maxXt * maxYt + deltaY * maxXt + deltaX * maxYt); // Compute $\delta_t$ term for triangle $t$ error bounds and check _t_ Float maxE = MaxComponent(Abs(Vector3f(e0, e1, e2))); Float deltaT = 3 * (gamma(3) * maxE * maxZt + deltaE * maxZt + deltaZ * maxE) * std::abs(invDet); if (t <= deltaT) return false; // Compute triangle partial derivatives Vector3f dpdu, dpdv; Point2f uv[3]; GetUVs(uv); // Compute deltas for triangle partial derivatives Vector2f duv02 = uv[0] - uv[2], duv12 = uv[1] - uv[2]; Vector3f dp02 = p0 - p2, dp12 = p1 - p2; Float determinant = duv02[0] * duv12[1] - duv02[1] * duv12[0]; if (determinant == 0) { // Handle zero determinant for triangle partial derivative matrix CoordinateSystem(Normalize(Cross(p2 - p0, p1 - p0)), &dpdu, &dpdv); } else { Float invdet = 1 / determinant; dpdu = (duv12[1] * dp02 - duv02[1] * dp12) * invdet; dpdv = (-duv12[0] * dp02 + duv02[0] * dp12) * invdet; } // Compute error bounds for triangle intersection Float xAbsSum = (std::abs(b0 * p0.x) + std::abs(b1 * p1.x) + std::abs(b2 * p2.x)); Float yAbsSum = (std::abs(b0 * p0.y) + std::abs(b1 * p1.y) + std::abs(b2 * p2.y)); Float zAbsSum = (std::abs(b0 * p0.z) + std::abs(b1 * p1.z) + std::abs(b2 * p2.z)); Vector3f pError = gamma(7) * Vector3f(xAbsSum, yAbsSum, zAbsSum); // Interpolate $(u,v)$ parametric coordinates and hit point Point3f pHit = b0 * p0 + b1 * p1 + b2 * p2; Point2f uvHit = b0 * uv[0] + b1 * uv[1] + b2 * uv[2]; // Test intersection against alpha texture, if present if (testAlphaTexture && mesh->alphaMask) { SurfaceInteraction isectLocal(pHit, Vector3f(0, 0, 0), uvHit, -ray.d, dpdu, dpdv, Normal3f(0, 0, 0), Normal3f(0, 0, 0), ray.time, this); if (mesh->alphaMask->Evaluate(isectLocal) == 0) return false; } // Fill in _SurfaceInteraction_ from triangle hit *isect = SurfaceInteraction(pHit, pError, uvHit, -ray.d, dpdu, dpdv, Normal3f(0, 0, 0), Normal3f(0, 0, 0), ray.time, this); // Override surface normal in _isect_ for triangle isect->n = isect->shading.n = Normal3f(Normalize(Cross(dp02, dp12))); if (mesh->n || mesh->s) { // Initialize _Triangle_ shading geometry // Compute shading normal _ns_ for triangle Normal3f ns; if (mesh->n) { ns = (b0 * mesh->n[v[0]] + b1 * mesh->n[v[1]] + b2 * mesh->n[v[2]]); if (ns.LengthSquared() > 0) ns = Normalize(ns); else ns = isect->n; } else ns = isect->n; // Compute shading tangent _ss_ for triangle Vector3f ss; if (mesh->s) { ss = (b0 * mesh->s[v[0]] + b1 * mesh->s[v[1]] + b2 * mesh->s[v[2]]); if (ss.LengthSquared() > 0) ss = Normalize(ss); else ss = Normalize(isect->dpdu); } else ss = Normalize(isect->dpdu); // Compute shading bitangent _ts_ for triangle and adjust _ss_ Vector3f ts = Cross(ss, ns); if (ts.LengthSquared() > 0.f) { ts = Normalize(ts); ss = Cross(ts, ns); } else CoordinateSystem((Vector3f)ns, &ss, &ts); // Compute $\dndu$ and $\dndv$ for triangle shading geometry Normal3f dndu, dndv; if (mesh->n) { // Compute deltas for triangle partial derivatives of normal Vector2f duv02 = uv[0] - uv[2]; Vector2f duv12 = uv[1] - uv[2]; Normal3f dn1 = mesh->n[v[0]] - mesh->n[v[2]]; Normal3f dn2 = mesh->n[v[1]] - mesh->n[v[2]]; Float determinant = duv02[0] * duv12[1] - duv02[1] * duv12[0]; if (determinant == 0) dndu = dndv = Normal3f(0, 0, 0); else { Float invDet = 1 / determinant; dndu = (duv12[1] * dn1 - duv02[1] * dn2) * invDet; dndv = (-duv12[0] * dn1 + duv02[0] * dn2) * invDet; } } else dndu = dndv = Normal3f(0, 0, 0); isect->SetShadingGeometry(ss, ts, dndu, dndv, true); } // Ensure correct orientation of the geometric normal if (mesh->n) isect->n = Faceforward(isect->n, isect->shading.n); else if (reverseOrientation ^ transformSwapsHandedness) isect->n = isect->shading.n = -isect->n; *tHit = t; ++nHits; return true; }