bool MultiQuadLight::sampleDirect(uint32 threadIndex, const Vec3f &p, PathSampleGenerator &pathSampler, LightSample &sample) const { float xi = pathSampler.next1D(EmitterSample); ThreadlocalSampleInfo &sampler = *_samplers[threadIndex]; std::pair<int, float> result = _sampleBvh->sampleLight(p, &sampler.sampleWeights[0], &sampler.insideIds[0], xi, [&](uint32 id) { return approximateQuadContribution(p, id); }); if (result.first == -1) return false; int idx = pathSampler.next1D(EmitterSample) < 0.5f ? result.first*2 : result.first*2 + 1; const QuadGeometry::TriangleInfo &t = _geometry.triangle(idx); Vec3f q = SampleWarp::uniformTriangle(pathSampler.next2D(EmitterSample), t.p0, t.p1, t.p2); Vec3f L = q - p; float rSq = L.lengthSq(); sample.dist = std::sqrt(rSq); sample.d = L/sample.dist; float cosTheta = -(t.Ng.dot(sample.d)); if (cosTheta <= 0.0f) return false; sample.pdf = result.second*0.5f*rSq/(cosTheta*_triangleAreas[idx]); return true; }
bool Cube::samplePosition(PathSampleGenerator &sampler, PositionSample &sample) const { float u = sampler.next1D(); int dim = sampleFace(u); float s = (dim + 1) % 3; float t = (dim + 2) % 3; Vec2f xi = sampler.next2D(); Vec3f n(0.0f); n[dim] = u < 0.5f ? -1.0f : 1.0f; Vec3f p(0.0f); p[dim] = n[dim]*_scale[dim]; p[s] = (xi.x()*2.0f - 1.0f)*_scale[s]; p[t] = (xi.y()*2.0f - 1.0f)*_scale[t]; sample.p = _rot*p + _pos; sample.pdf = _invArea; sample.uv = xi; sample.weight = PI*_area*(*_emission)[sample.uv]; sample.Ng = _rot*n; return true; }
bool IsotropicPhaseFunction::sample(PathSampleGenerator &sampler, const Vec3f &/*wi*/, PhaseSample &sample) const { sample.w = SampleWarp::uniformSphere(sampler.next2D()); sample.weight = Vec3f(1.0f); sample.pdf = SampleWarp::uniformSpherePdf(); return true; }
bool Cube::sampleDirection(PathSampleGenerator &sampler, const PositionSample &point, DirectionSample &sample) const { Vec3f d = SampleWarp::cosineHemisphere(sampler.next2D()); sample.d = TangentFrame(point.Ng).toGlobal(d); sample.weight = Vec3f(1.0f); sample.pdf = SampleWarp::cosineHemispherePdf(d); return true; }
bool Quad::sampleDirection(PathSampleGenerator &sampler, const PositionSample &/*point*/, DirectionSample &sample) const { Vec3f d = SampleWarp::cosineHemisphere(sampler.next2D(EmitterSample)); sample.d = _frame.toGlobal(d); sample.weight = Vec3f(1.0f); sample.pdf = SampleWarp::cosineHemispherePdf(d); return true; }
bool ExponentialMedium::sampleDistance(PathSampleGenerator &sampler, const Ray &ray, MediumState &state, MediumSample &sample) const { if (state.bounce > _maxBounce) return false; float x = _falloffScale*(ray.pos() - _unitPoint).dot(_unitFalloffDirection); float dx = _falloffScale*ray.dir().dot(_unitFalloffDirection); float maxT = ray.farT(); if (_absorptionOnly) { if (maxT == Ray::infinity() && dx <= 0.0f) return false; sample.t = maxT; sample.weight = std::exp(-_sigmaT*densityIntegral(x, dx, ray.farT())); sample.pdf = 1.0f; sample.exited = true; } else { int component = sampler.nextDiscrete(3); float sigmaTc = _sigmaT[component]; float xi = 1.0f - sampler.next1D(); float logXi = std::log(xi); float t = inverseOpticalDepth(x, dx, sigmaTc, logXi); sample.t = min(t, maxT); sample.weight = std::exp(-_sigmaT*densityIntegral(x, dx, sample.t)); sample.exited = (t >= maxT); if (sample.exited) { sample.pdf = sample.weight.avg(); } else { float rho = density(x, dx, sample.t); sample.pdf = (rho*_sigmaT*sample.weight).avg(); sample.weight *= rho*_sigmaS; } sample.weight /= sample.pdf; state.advance(); } sample.p = ray.pos() + sample.t*ray.dir(); sample.phase = _phaseFunction.get(); return true; }
float ErlangTransmittance::sampleSurface(PathSampleGenerator &sampler) const { float xi = sampler.next1D(); float x = 0.5f; for (int i = 0; i < 10; ++i) { x += (xi - (1.0f - surfaceSurface(Vec3f(x))[0]))/surfaceMedium(Vec3f(x))[0]; x = max(x, 0.0f); } return x; }
bool AtmosphericMedium::sampleDistance(PathSampleGenerator &sampler, const Ray &ray, MediumState &state, MediumSample &sample) const { if (state.bounce > _maxBounce) return false; Vec3f p = (ray.pos() - _center); float t0 = p.dot(ray.dir()); float h = (p - t0*ray.dir()).length(); float maxT = ray.farT() + t0; if (_absorptionOnly) { sample.t = ray.farT(); sample.weight = std::exp(-_sigmaT*densityIntegral(h, t0, maxT)); sample.pdf = 1.0f; sample.exited = true; } else { int component = sampler.nextDiscrete(3); float sigmaTc = _sigmaT[component]; float xi = 1.0f - sampler.next1D(); float t = inverseOpticalDepth(h, t0, sigmaTc, xi); sample.t = min(t, maxT); sample.weight = std::exp(-_sigmaT*densityIntegral(h, t0, sample.t)); sample.exited = (t >= maxT); if (sample.exited) { sample.pdf = sample.weight.avg(); } else { float rho = density(h, sample.t); sample.pdf = (rho*_sigmaT*sample.weight).avg(); sample.weight *= rho*_sigmaS; } sample.weight /= sample.pdf; sample.t -= t0; state.advance(); } sample.p = ray.pos() + sample.t*ray.dir(); sample.phase = _phaseFunction.get(); return true; }
bool Quad::samplePosition(PathSampleGenerator &sampler, PositionSample &sample) const { Vec2f xi = sampler.next2D(EmitterSample); sample.p = _base + xi.x()*_edge0 + xi.y()*_edge1; sample.pdf = _invArea; sample.uv = xi; sample.weight = PI*_area*(*_emission)[sample.uv]; sample.Ng = _frame.normal; return true; }
bool EquirectangularCamera::sampleDirection(PathSampleGenerator &sampler, const PositionSample &/*point*/, Vec2u pixel, DirectionSample &sample) const { float pdf; Vec2f uv = (Vec2f(pixel) + 0.5f + _filter.sample(sampler.next2D(), pdf))*_pixelSize; float sinTheta; sample.d = uvToDirection(uv, sinTheta); sample.weight = Vec3f(1.0f); sample.pdf = INV_PI*INV_TWO_PI/sinTheta; return true; }
bool HomogeneousMedium::sampleDistance(PathSampleGenerator &sampler, const Ray &ray, MediumState &state, MediumSample &sample) const { if (state.bounce > _maxBounce) return false; float maxT = ray.farT(); if (_absorptionOnly) { if (maxT == Ray::infinity()) return false; sample.t = maxT; sample.weight = std::exp(-_sigmaT*maxT); sample.pdf = 1.0f; sample.exited = true; } else { int component = sampler.nextDiscrete(3); float sigmaTc = _sigmaT[component]; float t = -std::log(1.0f - sampler.next1D())/sigmaTc; sample.t = min(t, maxT); sample.weight = std::exp(-sample.t*_sigmaT); sample.exited = (t >= maxT); if (sample.exited) { sample.pdf = sample.weight.avg(); } else { sample.pdf = (_sigmaT*sample.weight).avg(); sample.weight *= _sigmaS; } sample.weight /= sample.pdf; state.advance(); } sample.p = ray.pos() + sample.t*ray.dir(); sample.phase = _phaseFunction.get(); return true; }
bool Quad::sampleDirect(uint32 /*threadIndex*/, const Vec3f &p, PathSampleGenerator &sampler, LightSample &sample) const { if (_frame.normal.dot(p - _base) <= 0.0f) return false; Vec2f xi = sampler.next2D(EmitterSample); Vec3f q = _base + xi.x()*_edge0 + xi.y()*_edge1; sample.d = q - p; float rSq = sample.d.lengthSq(); sample.dist = std::sqrt(rSq); sample.d /= sample.dist; float cosTheta = -_frame.normal.dot(sample.d); sample.pdf = rSq/(cosTheta*_area); return true; }
bool PinholeCamera::sampleDirection(PathSampleGenerator &sampler, const PositionSample &/*point*/, Vec2u pixel, DirectionSample &sample) const { float pdf; Vec2f uv = _filter.sample(sampler.next2D(CameraSample), pdf); Vec3f localD = Vec3f( -1.0f + (float(pixel.x()) + 0.5f + uv.x())*2.0f*_pixelSize.x(), _ratio - (float(pixel.y()) + 0.5f + uv.y())*2.0f*_pixelSize.x(), _planeDist ).normalized(); sample.d = _transform.transformVector(localD); sample.weight = Vec3f(1.0f); sample.pdf = _invPlaneArea/cube(localD.z()); return true; }
bool CubemapCamera::sampleDirection(PathSampleGenerator &sampler, const PositionSample &/*point*/, Vec2u pixel, DirectionSample &sample) const { Vec2f uv = (Vec2f(pixel) + 0.5f)*_pixelSize; int face; if (!uvToFace(uv, face)) return false; float filterPdf; uv += _filter.sample(sampler.next2D(CameraSample), filterPdf)*_pixelSize; sample.d = uvToDirection(face, uv, sample.pdf); sample.weight = Vec3f(1.0f); return true; }
bool HenyeyGreensteinPhaseFunction::sample(PathSampleGenerator &sampler, const Vec3f &wi, PhaseSample &sample) const { Vec2f xi = sampler.next2D(MediumPhaseSample); if (_g == 0.0f) { sample.w = SampleWarp::uniformSphere(xi); sample.weight = Vec3f(1.0f); sample.pdf = SampleWarp::uniformSpherePdf(); } else { float phi = xi.x()*TWO_PI; float cosTheta = (1.0f + _g*_g - sqr((1.0f - _g*_g)/(1.0f + _g*(xi.y()*2.0f - 1.0f))))/(2.0f*_g); float sinTheta = std::sqrt(max(1.0f - cosTheta*cosTheta, 0.0f)); sample.w = TangentFrame(wi).toGlobal(Vec3f( std::cos(phi)*sinTheta, std::sin(phi)*sinTheta, cosTheta )); sample.weight = Vec3f(1.0f); sample.pdf = henyeyGreenstein(cosTheta); } return true; }
Vec3f PhotonTracer::traceSample(Vec2u pixel, const KdTree<Photon> &surfaceTree, const KdTree<VolumePhoton> *mediumTree, PathSampleGenerator &sampler, float gatherRadius) { PositionSample point; if (!_scene->cam().samplePosition(sampler, point)) return Vec3f(0.0f); DirectionSample direction; if (!_scene->cam().sampleDirection(sampler, point, pixel, direction)) return Vec3f(0.0f); sampler.advancePath(); Vec3f throughput = point.weight*direction.weight; Ray ray(point.p, direction.d); ray.setPrimaryRay(true); IntersectionTemporary data; IntersectionInfo info; const Medium *medium = _scene->cam().medium().get(); Vec3f result(0.0f); int bounce = 0; bool didHit = _scene->intersect(ray, data, info); while ((medium || didHit) && bounce < _settings.maxBounces - 1) { if (medium) { if (mediumTree) { Vec3f beamEstimate(0.0f); mediumTree->beamQuery(ray.pos(), ray.dir(), ray.farT(), [&](const VolumePhoton &p, float t, float distSq) { Ray mediumQuery(ray); mediumQuery.setFarT(t); beamEstimate += (3.0f*INV_PI*sqr(1.0f - distSq/p.radiusSq))/p.radiusSq *medium->phaseFunction(p.pos)->eval(ray.dir(), -p.dir) *medium->transmittance(mediumQuery)*p.power; }); result += throughput*beamEstimate; } throughput *= medium->transmittance(ray); } if (!didHit) break; const Bsdf &bsdf = *info.bsdf; SurfaceScatterEvent event = makeLocalScatterEvent(data, info, ray, &sampler); Vec3f transparency = bsdf.eval(event.makeForwardEvent(), false); float transparencyScalar = transparency.avg(); Vec3f wo; if (sampler.nextBoolean(DiscreteTransparencySample, transparencyScalar)) { wo = ray.dir(); throughput *= transparency/transparencyScalar; } else { event.requestedLobe = BsdfLobes::SpecularLobe; if (!bsdf.sample(event, false)) break; wo = event.frame.toGlobal(event.wo); throughput *= event.weight; } bool geometricBackside = (wo.dot(info.Ng) < 0.0f); medium = info.primitive->selectMedium(medium, geometricBackside); ray = ray.scatter(ray.hitpoint(), wo, info.epsilon); if (std::isnan(ray.dir().sum() + ray.pos().sum())) break; if (std::isnan(throughput.sum())) break; sampler.advancePath(); bounce++; if (bounce < _settings.maxBounces) didHit = _scene->intersect(ray, data, info); } if (!didHit) { if (!medium && _scene->intersectInfinites(ray, data, info)) result += throughput*info.primitive->evalDirect(data, info); return result; } if (info.primitive->isEmissive()) result += throughput*info.primitive->evalDirect(data, info); int count = surfaceTree.nearestNeighbours(ray.hitpoint(), _photonQuery.get(), _distanceQuery.get(), _settings.gatherCount, gatherRadius); if (count == 0) return result; const Bsdf &bsdf = *info.bsdf; SurfaceScatterEvent event = makeLocalScatterEvent(data, info, ray, &sampler); Vec3f surfaceEstimate(0.0f); for (int i = 0; i < count; ++i) { event.wo = event.frame.toLocal(-_photonQuery[i]->dir); // Asymmetry due to shading normals already compensated for when storing the photon, // so we don't use the adjoint BSDF here surfaceEstimate += _photonQuery[i]->power*bsdf.eval(event, false)/std::abs(event.wo.z()); } float radiusSq = count == int(_settings.gatherCount) ? _distanceQuery[0] : gatherRadius*gatherRadius; result += throughput*surfaceEstimate*(INV_PI/radiusSq); return result; }
bool PinholeCamera::sampleDirection(PathSampleGenerator &sampler, const PositionSample &point, DirectionSample &sample) const { Vec2u pixel(sampler.next2D(CameraSample)*Vec2f(_res)); return sampleDirection(sampler, point, pixel, sample); }
void PhotonTracer::tracePhoton(SurfacePhotonRange &surfaceRange, VolumePhotonRange &volumeRange, PathSampleGenerator &sampler) { float lightPdf; const Primitive *light = chooseLightAdjoint(sampler, lightPdf); PositionSample point; if (!light->samplePosition(sampler, point)) return; DirectionSample direction; if (!light->sampleDirection(sampler, point, direction)) return; sampler.advancePath(); Ray ray(point.p, direction.d); Vec3f throughput(point.weight*direction.weight/lightPdf); SurfaceScatterEvent event; IntersectionTemporary data; IntersectionInfo info; Medium::MediumState state; state.reset(); Vec3f emission(0.0f); const Medium *medium = nullptr; // TODO: Media int bounce = 0; bool wasSpecular = true; bool hitSurface = true; bool didHit = _scene->intersect(ray, data, info); while ((didHit || medium) && bounce < _settings.maxBounces - 1) { if (medium) { MediumSample mediumSample; if (!medium->sampleDistance(sampler, ray, state, mediumSample)) break; throughput *= mediumSample.weight; hitSurface = mediumSample.exited; if (!hitSurface) { if (!volumeRange.full()) { VolumePhoton &p = volumeRange.addPhoton(); p.pos = mediumSample.p; p.dir = ray.dir(); p.power = throughput; } PhaseSample phaseSample; if (!mediumSample.phase->sample(sampler, ray.dir(), phaseSample)) break; ray = ray.scatter(mediumSample.p, phaseSample.w, 0.0f); ray.setPrimaryRay(false); throughput *= phaseSample.weight; } } if (hitSurface) { if (!info.bsdf->lobes().isPureSpecular() && !surfaceRange.full()) { Photon &p = surfaceRange.addPhoton(); p.pos = info.p; p.dir = ray.dir(); p.power = throughput*std::abs(info.Ns.dot(ray.dir())/info.Ng.dot(ray.dir())); } } if (volumeRange.full() && surfaceRange.full()) break; if (hitSurface) { event = makeLocalScatterEvent(data, info, ray, &sampler); if (!handleSurface(event, data, info, medium, bounce, true, false, ray, throughput, emission, wasSpecular, state)) break; } if (throughput.max() == 0.0f) break; if (std::isnan(ray.dir().sum() + ray.pos().sum())) break; if (std::isnan(throughput.sum())) break; sampler.advancePath(); bounce++; if (bounce < _settings.maxBounces) didHit = _scene->intersect(ray, data, info); } }
void LightTracer::traceSample(PathSampleGenerator &sampler) { float lightPdf; const Primitive *light = chooseLightAdjoint(sampler, lightPdf); const Medium *medium = light->extMedium().get(); PositionSample point; if (!light->samplePosition(sampler, point)) return; DirectionSample direction; if (!light->sampleDirection(sampler, point, direction)) return; sampler.advancePath(); Vec3f throughput(point.weight/lightPdf); LensSample splat; if (_settings.minBounces == 0 && !light->isInfinite() && _scene->cam().sampleDirect(point.p, sampler, splat)) { Ray shadowRay(point.p, splat.d); shadowRay.setFarT(splat.dist); Vec3f transmission = generalizedShadowRay(shadowRay, medium, nullptr, 0); if (transmission != 0.0f) { Vec3f value = throughput*transmission*splat.weight *light->evalDirectionalEmission(point, DirectionSample(splat.d)); _splatBuffer->splatFiltered(splat.pixel, value); } } sampler.advancePath(); Ray ray(point.p, direction.d); throughput *= direction.weight; VolumeScatterEvent volumeEvent; SurfaceScatterEvent surfaceEvent; IntersectionTemporary data; IntersectionInfo info; Medium::MediumState state; state.reset(); Vec3f emission(0.0f); int bounce = 0; bool wasSpecular = true; bool didHit = _scene->intersect(ray, data, info); while ((didHit || medium) && bounce < _settings.maxBounces - 1) { bool hitSurface = true; if (medium) { volumeEvent = VolumeScatterEvent(&sampler, throughput, ray.pos(), ray.dir(), ray.farT()); if (!medium->sampleDistance(volumeEvent, state)) break; throughput *= volumeEvent.weight; volumeEvent.weight = Vec3f(1.0f); hitSurface = volumeEvent.t == volumeEvent.maxT; if (hitSurface && !didHit) break; } if (hitSurface) { surfaceEvent = makeLocalScatterEvent(data, info, ray, &sampler); Vec3f weight; Vec2f pixel; if (surfaceLensSample(_scene->cam(), surfaceEvent, medium, bounce + 1, ray, weight, pixel)) _splatBuffer->splatFiltered(pixel, weight*throughput); if (!handleSurface(surfaceEvent, data, info, medium, bounce, true, false, ray, throughput, emission, wasSpecular, state)) break; } else { volumeEvent.p += volumeEvent.t*volumeEvent.wi; Vec3f weight; Vec2f pixel; if (volumeLensSample(_scene->cam(), volumeEvent, medium, bounce + 1, ray, weight, pixel)) _splatBuffer->splatFiltered(pixel, weight*throughput); if (!handleVolume(volumeEvent, medium, bounce, true, false, ray, throughput, emission, wasSpecular, state)) break; } if (throughput.max() == 0.0f) break; if (std::isnan(ray.dir().sum() + ray.pos().sum())) break; if (std::isnan(throughput.sum())) break; sampler.advancePath(); bounce++; if (bounce < _settings.maxBounces) didHit = _scene->intersect(ray, data, info); } }
Vec2f VdbGrid::inverseOpticalDepth(PathSampleGenerator &sampler, Vec3f p, Vec3f w, float t0, float t1, float sigmaT, float xi) const { auto accessor = _grid->getConstAccessor(); if (_sampleMethod == SampleMethod::ExactNearest) { VdbRaymarcher<openvdb::FloatGrid::TreeType, 3> dda; float integral = 0.0f; Vec2f result(t1, 0.0f); bool exited = !dda.march(DdaRay(p + 0.5f, w), t0, t1, accessor, [&](openvdb::Coord voxel, float ta, float tb) { float v = accessor.getValue(voxel); float delta = v*sigmaT*(tb - ta); if (integral + delta >= xi) { result = Vec2f(ta + (tb - ta)*(xi - integral)/delta, v); return true; } integral += delta; return false; }); return exited ? Vec2f(t1, integral) : result; } else if (_sampleMethod == SampleMethod::ExactLinear) { VdbRaymarcher<openvdb::FloatGrid::TreeType, 3> dda; float integral = 0.0f; float fa = gridAt(accessor, p + w*t0); Vec2f result(t1, 0.0f); bool exited = !dda.march(DdaRay(p + 0.5f, w), t0, t1, accessor, [&](openvdb::Coord /*voxel*/, float ta, float tb) { float fb = gridAt(accessor, p + tb*w); float delta = (fb + fa)*0.5f*sigmaT*(tb - ta); if (integral + delta >= xi) { float a = (fb - fa)*sigmaT; float b = fa*sigmaT; float c = (integral - xi)/(tb - ta); float mantissa = max(b*b - 2.0f*a*c, 0.0f); float x1 = (-b + std::sqrt(mantissa))/a; result = Vec2f(ta + (tb - ta)*x1, fa + (fb - fa)*x1); return true; } integral += delta; fa = fb; return false; }); return exited ? Vec2f(t1, integral) : result; } else { float ta = t0; float fa = gridAt(accessor, p + w*t0); float integral = 0.0f; float dT = sampler.next1D()*_stepSize; do { float tb = min(ta + dT, t1); float fb = gridAt(accessor, p + w*tb); float delta = (fa + fb)*sigmaT*0.5f*(tb - ta); if (integral + delta >= xi) { float a = (fb - fa)*sigmaT; float b = fa*sigmaT; float c = (integral - xi)/(tb - ta); float mantissa = max(b*b - 2.0f*a*c, 0.0f); float x1 = (-b + std::sqrt(mantissa))/a; return Vec2f(ta + (tb - ta)*x1, fa + (fb - fa)*x1); } integral += delta; ta = tb; fa = fb; dT = _stepSize; } while (ta < t1); return Vec2f(t1, integral); } }
Vec3f VdbGrid::transmittance(PathSampleGenerator &sampler, Vec3f p, Vec3f w, float t0, float t1, Vec3f sigmaT) const { auto accessor = _grid->getConstAccessor(); if (_integrationMethod == IntegrationMethod::ExactNearest) { VdbRaymarcher<openvdb::FloatGrid::TreeType, 3> dda; float integral = 0.0f; dda.march(DdaRay(p + 0.5f, w), t0, t1, accessor, [&](openvdb::Coord voxel, float ta, float tb) { integral += accessor.getValue(voxel)*(tb - ta); return false; }); return std::exp(-integral*sigmaT); } else if (_integrationMethod == IntegrationMethod::ExactLinear) { VdbRaymarcher<openvdb::FloatGrid::TreeType, 3> dda; float integral = 0.0f; float fa = gridAt(accessor, p + w*t0); dda.march(DdaRay(p, w), t0, t1, accessor, [&](openvdb::Coord /*voxel*/, float ta, float tb) { float fb = gridAt(accessor, p + w*tb); integral += (fa + fb)*0.5f*(tb - ta); fa = fb; return false; }); return std::exp(-integral*sigmaT); } else if (_integrationMethod == IntegrationMethod::ResidualRatio) { VdbRaymarcher<Vec2fGrid::TreeType, 3> dda; float scale = _supergridSubsample; float invScale = 1.0f/scale; sigmaT *= scale; float sigmaTc = sigmaT.max(); auto superAccessor = _superGrid->getConstAccessor(); UniformSampler &generator = sampler.uniformGenerator(); float controlIntegral = 0.0f; Vec3f Tr(1.0f); dda.march(DdaRay(p*invScale + 0.5f, w), t0*invScale, t1*invScale, superAccessor, [&](openvdb::Coord voxel, float ta, float tb) { openvdb::Vec2s v = superAccessor.getValue(voxel); float muC = v.x(); float muR = v.y(); muR *= sigmaTc; controlIntegral += muC*(tb - ta); while (true) { ta -= BitManip::normalizedLog(generator.nextI())/muR; if (ta >= tb) break; Tr *= 1.0f - sigmaT*((gridAt(accessor, p + w*ta*scale) - muC)/muR); } return false; }); return std::exp(-controlIntegral*sigmaT)*Tr; } else { float ta = t0; float fa = gridAt(accessor, p + w*t0); float integral = 0.0f; float dT = sampler.next1D()*_stepSize; do { float tb = min(ta + dT, t1); float fb = gridAt(accessor, p + w*tb); integral += (fa + fb)*0.5f*(tb - ta); ta = tb; fa = fb; dT = _stepSize; } while (ta < t1); return std::exp(-integral*sigmaT); } }
float ErlangTransmittance::sampleMedium(PathSampleGenerator &sampler) const { return -1.0f/_lambda*std::log(sampler.next1D()*sampler.next1D()); }