static Color3 finalGathering(KdTree<Photon> *map , Scene& scene , 
							 Intersection& inter , RNG& rng , const Vector3& wo ,
                             int gatherSamples , int knn , Real maxSqrDis)
{
    Color3 res = Color3(0.0 , 0.0 , 0.0);
    for (int i = 0; i < gatherSamples; i++)
    {
		Real pdf;
        Vector3 wi = sampleCosHemisphere(rng.randVector3() , &pdf);
        Ray ray = Ray(inter.p + wi * EPS , wi);

        Intersection _inter;
        
        Geometry *_g = scene.intersect(ray , _inter);

        if (_g == NULL)
            continue;
        
        Color3 tmp = estimate(map , 0 , knn , scene , _inter , -wi , maxSqrDis);

		BSDF bsdf(wi , _inter , scene);
		Real cosine , bsdfPdf;
        Color3 brdf = bsdf.f(scene , wo , cosine , &bsdfPdf);
		if (brdf.isBlack())
			continue;
		pdf *= bsdfPdf;
        res = res + (tmp | brdf) * (cosine / pdf);
    }
    res = res / gatherSamples;
    return res;
}
void InstantRadiosityRenderer::processPixel(uint32_t threadID, uint32_t tileID, const Vec2u& pixel) const {
    PixelSensor pixelSensor(getSensor(), pixel, getFramebufferSize());
    RaySample raySample;
    float positionPdf, directionPdf;
    auto We = pixelSensor.sampleExitantRay(getScene(), getFloat2(threadID), getFloat2(threadID), raySample, positionPdf, directionPdf);

    auto L = zero<Vec3f>();

    if(We != zero<Vec3f>() && raySample.pdf) {
        auto I = getScene().intersect(raySample.value);
        BSDF bsdf(-raySample.value.dir, I, getScene());
        if(I) {
            // Direct illumination
            for(auto& vpl: m_EmissionVPLBuffer) {
                RaySample shadowRay;
                auto Le = vpl.pLight->sampleDirectIllumination(getScene(), vpl.emissionVertexSample, I, shadowRay);

                if(shadowRay.pdf) {
                    auto contrib = We * Le * bsdf.eval(shadowRay.value.dir) * abs(dot(I.Ns, shadowRay.value.dir)) /
                            (vpl.lightPdf * shadowRay.pdf * m_nLightPathCount * raySample.pdf);

                    if(contrib != zero<Vec3f>()) {
                        if(!getScene().occluded(shadowRay.value)) {
                            L += contrib;
                        }
                    }
                }
            }

            // Indirect illumination
            for(auto& vpl: m_SurfaceVPLBuffer) {
                auto dirToVPL = vpl.intersection.P - I.P;
                auto dist = length(dirToVPL);

                if(dist > 0.f) {
                    dirToVPL /= dist;

                    auto fs1 = bsdf.eval(dirToVPL);
                    auto fs2 = vpl.bsdf.eval(-dirToVPL);
                    auto geometricFactor = abs(dot(I.Ns, dirToVPL)) * abs(dot(vpl.intersection.Ns, -dirToVPL)) / sqr(dist);

                    auto contrib = We * vpl.power * fs1 * fs2 * geometricFactor / raySample.pdf;
                    if(contrib != zero<Vec3f>()) {
                        Ray shadowRay(I, vpl.intersection, dirToVPL, dist);
                        if(!getScene().occluded(shadowRay)) {
                            L += contrib;
                        }
                    }
                }
            }
        }
    }

    accumulate(0, getPixelIndex(pixel.x, pixel.y), Vec4f(L, 1.f));
}
Example #3
0
bool UniversalMaterial::coverageLessThan(const float alphaThreshold, const Point2& texCoord) const {
    const Component4& lambertian = bsdf()->lambertian();

    if (lambertian.min().a > alphaThreshold) {
        // Opaque pixel
        return false;
    }

    const Image4::Ref& image = lambertian.image();
    if (isNull(image)) {
        return lambertian.constant().a <= alphaThreshold;
    }
    const Point2& t = texCoord * Vector2(float(image->width()), float(image->height()));

    return (image->nearest(t).a * lambertian.constant().a < alphaThreshold);
}
Example #4
0
UniversalBSDF::Ref UniversalBSDF::create
(const Component4& lambertian,
 const Component4& glossy,
 const Component3& transmissive,
 float             eta_t,
 const Color3&     extinction_t,
 float             eta_r,
 const Color3&     extinction_r) {

    UniversalBSDF::Ref bsdf(new UniversalBSDF());

    bsdf->m_lambertian      = lambertian;
    bsdf->m_glossy          = glossy;
    bsdf->m_transmissive    = transmissive;
    bsdf->m_eta_t           = eta_t;
    bsdf->m_extinction_t    = extinction_t;
    bsdf->m_eta_r           = eta_r;
    bsdf->m_extinction_r    = extinction_r;

    return bsdf;
}
static Color3 directIllumination(Scene& scene , Intersection& inter ,
                           RNG& rng , const Vector3& visionDir)
{
    Color3 res = Color3(0.0 , 0.0 , 0.0);
    Vector3 lightDir;
    Real cosine;
    Color3 brdf;
    Ray ray;
    
	BSDF bsdf(visionDir , inter , scene);

	int N = 1;
    for (int i = 0; i < N; i++)
    {
        int k = rand() % scene.lights.size();
		AbstractLight *l = scene.lights[k];
		
		Vector3 dirToLight;
		Real dist , directPdf;

		Color3 illu = l->illuminance(scene.sceneSphere , inter.p , rng.randVector3() ,
			dirToLight , dist , directPdf);

		illu = illu / (directPdf / scene.lights.size());

        if (scene.occluded(inter.p + dirToLight * EPS , dirToLight ,
			inter.p + dirToLight * (inter.t - EPS)))
            continue;
        
		Real bsdfPdf;
        brdf = bsdf.f(scene , dirToLight , cosine , &bsdfPdf);
		if (brdf.isBlack())
			continue;
        res = res + (illu | brdf) * (cosine / bsdfPdf);
    }
    res = res / N;
    return res;
}
Example #6
0
void test_d_bsdf() {
    Vector3f d{0.5, 0.4, 0.3};
    Vector2f uv_scale{1, 1};
    Texture3 diffuse{&d[0], -1, -1, -1, &uv_scale[0]};
    Vector3f s{0.2, 0.3, 0.4};
    Texture3 specular{&s[0], -1, -1, -1, &uv_scale[0]};
    float r = 0.5;
    Texture1 roughness{&r, -1, -1, -1, &uv_scale[0]};
    Material m{diffuse, specular, roughness,false};
    DTexture3 d_diffuse_tex;
    DTexture3 d_specular_tex;
    DTexture1 d_roughness_tex;
    SurfacePoint p{Vector3{0, 0, 0},
                   Vector3{0, 1, 0},
                   Frame(Vector3{0, 1, 0}),
                   Vector2{0.5, 0.5}};
    auto wi = normalize(Vector3{0.5, 1.0, 0.5});
    auto wo = normalize(Vector3{-0.5, 1.0, -0.5});
    auto min_roughness = Real(0);
    auto d_p = SurfacePoint::zero();
    auto d_wi = Vector3{0, 0, 0};
    auto d_wo = Vector3{0, 0, 0};

    d_bsdf(m, p, wi, wo, min_roughness, Vector3{1, 1, 1},
           d_diffuse_tex, d_specular_tex, d_roughness_tex,
           d_p, d_wi, d_wo);

    // Check diffuse derivatives
    auto finite_delta = Real(1e-6);
    for (int i = 0; i < 3; i++) {
        auto delta_m = m;
        delta_m.diffuse_reflectance.texels[i] += finite_delta;
        auto positive = bsdf(delta_m, p, wi, wo, min_roughness);
        delta_m.diffuse_reflectance.texels[i] -= 2 * finite_delta;
        auto negative = bsdf(delta_m, p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_diffuse_tex.t000[i]);
    }

    // Check specular derivatives
    for (int i = 0; i < 3; i++) {
        auto delta_m = m;
        delta_m.specular_reflectance.texels[i] += finite_delta;
        auto positive = bsdf(delta_m, p, wi, wo, min_roughness);
        delta_m.specular_reflectance.texels[i] -= 2 * finite_delta;
        auto negative = bsdf(delta_m, p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_specular_tex.t000[i]);
    }

    // Check roughness derivatives
    {
        auto delta_m = m;
        delta_m.roughness.texels[0] += finite_delta;
        auto positive = bsdf(delta_m, p, wi, wo, min_roughness);
        delta_m.roughness.texels[0] -= 2 * finite_delta;
        auto negative = bsdf(delta_m, p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_roughness_tex.t000);
    }

    // Check surface point derivatives
    equal_or_error<Real>(__FILE__, __LINE__, Vector3{0, 0, 0}, d_p.position);
    equal_or_error<Real>(__FILE__, __LINE__, Vector3{0, 0, 0}, d_p.geom_normal);
    // Shading frame x
    for (int i = 0; i < 3; i++) {
        auto delta_p = p;
        delta_p.shading_frame.x[i] += finite_delta;
        auto positive = bsdf(m, delta_p, wi, wo, min_roughness);
        delta_p.shading_frame.x[i] -= 2 * finite_delta;
        auto negative = bsdf(m, delta_p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_p.shading_frame.x[i]);
    }
    // Shading frame y
    for (int i = 0; i < 3; i++) {
        auto delta_p = p;
        delta_p.shading_frame.y[i] += finite_delta;
        auto positive = bsdf(m, delta_p, wi, wo, min_roughness);
        delta_p.shading_frame.y[i] -= 2 * finite_delta;
        auto negative = bsdf(m, delta_p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_p.shading_frame.y[i]);
    }
    // Shading frame n
    for (int i = 0; i < 3; i++) {
        auto delta_p = p;
        delta_p.shading_frame.n[i] += finite_delta;
        auto positive = bsdf(m, delta_p, wi, wo, min_roughness);
        delta_p.shading_frame.n[i] -= 2 * finite_delta;
        auto negative = bsdf(m, delta_p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_p.shading_frame.n[i]);
    }
    // uv
    for (int i = 0; i < 2; i++) {
        auto delta_p = p;
        delta_p.uv[i] += finite_delta;
        auto positive = bsdf(m, delta_p, wi, wo, min_roughness);
        delta_p.uv[i] -= 2 * finite_delta;
        auto negative = bsdf(m, delta_p, wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_p.uv[i]);
    }

    // Check wi, wo
    for (int i = 0; i < 3; i++) {
        auto delta_wi = wi;
        delta_wi[i] += finite_delta;
        auto positive = bsdf(m, p, delta_wi, wo, min_roughness);
        delta_wi[i] -= 2 * finite_delta;
        auto negative = bsdf(m, p, delta_wi, wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_wi[i]);
    }
    for (int i = 0; i < 3; i++) {
        auto delta_wo = wo;
        delta_wo[i] += finite_delta;
        auto positive = bsdf(m, p, wi, delta_wo, min_roughness);
        delta_wo[i] -= 2 * finite_delta;
        auto negative = bsdf(m, p, wi, delta_wo, min_roughness);
        auto diff = sum(positive - negative) / (2 * finite_delta);
        equal_or_error(__FILE__, __LINE__, diff, d_wo[i]);
    }
}
void PhotonIntegrator::buildPhotonMap(Scene& scene)
{
    if (scene.lights.size() <= 0)
        return;

    causticPhotons.clear();
    indirectPhotons.clear();
    
    bool causticDone = 0 , indirectDone = 0;
    int nShot = 0;

    bool specularPath;
    int nIntersections;
    Intersection inter;
    Real lightPickProb = 1.f / scene.lights.size();

    while (!causticDone || !indirectDone)
    {
        nShot++;

        /* generate initial photon ray */
        int k = (int)(rng.randFloat() * scene.lights.size());

		Vector3 lightPos , lightDir;
		Real emissionPdf , directPdfArea , cosAtLight;
		Color3 alpha;

		AbstractLight *l = scene.lights[k];
		alpha = l->emit(scene.sceneSphere , rng.randVector3() , rng.randVector3() ,
			lightPos , lightDir , emissionPdf , &directPdfArea ,
			&cosAtLight);

		alpha = alpha * cosAtLight / (emissionPdf * lightPickProb);
        
		Ray photonRay(lightPos , lightDir);

        if (!alpha.isBlack())
        {
            specularPath = 1;
            nIntersections = 0;
            Geometry *g = scene.intersect(photonRay , inter);
            while (g != NULL && inter.matId > 0)
            {
				BSDF bsdf(-photonRay.dir , inter , scene);

                nIntersections++;
                bool hasNonSpecular = (cmp(bsdf.componentProb.diffuseProb) > 0 ||
                                      cmp(bsdf.componentProb.glossyProb) > 0);
                if (hasNonSpecular)
                {
                    Photon photon(inter.p , -photonRay.dir , alpha);
                    if (specularPath && nIntersections > 1)
                    {
                        if (!causticDone)
                        {
                            causticPhotons.push_back(photon);
                            if (causticPhotons.size() == nCausticPhotons)
                            {
                                causticDone = 1;
                                nCausticPaths = nShot;
                                causticMap = new KdTree<Photon>(causticPhotons);
                            }
                        }
                    }
                    else
                    {
                        if (nIntersections > 1 && !indirectDone)
                        {
                            indirectPhotons.push_back(photon);
                            if (indirectPhotons.size() == nIndirectPhotons)
                            {
                                indirectDone = 1;
                                nIndirectPaths = nShot;
                                indirectMap = new KdTree<Photon>(indirectPhotons);
                            }
                        }
                    }
                }
                if (nIntersections > maxPathLength)
                    break;

                /* find new photon ray direction */
                /* handle specular reflection and transmission first */
				Real pdf , cosWo;
				int sampledType;

				Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() ,
					photonRay.dir , pdf , cosWo , &sampledType);

				if (bsdfFactor.isBlack())
					break;

				if (sampledType & BSDF_NON_SPECULAR)
					specularPath = 0;

				// Russian Roulette
				Real contProb = bsdf.continueProb;
				
				if (cmp(contProb - 1.f) < 0)
				{
					if (cmp(rng.randFloat() - contProb) > 0)
						break;
					pdf *= contProb;
				}

				alpha = (alpha | bsdfFactor) * (cosWo / pdf);

				photonRay.origin = inter.p + photonRay.dir * EPS;
				photonRay.tmin = 0.f; photonRay.tmax = INF;

                g = scene.intersect(photonRay , inter);
            }
        }
    }
}
Color3 PhotonIntegrator::raytracing(const Ray& ray , int dep)
{
    Color3 res = Color3(0.0 , 0.0 , 0.0);

    if (dep > 2)
        return res;

    Geometry *g = NULL;
	Intersection inter;
    Ray reflectRay , transRay;

    g = scene.intersect(ray , inter);

    if (g == NULL)
        return res;

	if (inter.matId < 0)
	{
		AbstractLight *l = scene.lights[-inter.matId - 1];
		return l->getIntensity() * 100.f;
	}
    
    res = res + directIllumination(scene , inter , rng , -ray.dir);
      
    res = res + estimate(indirectMap , nIndirectPaths , knnPhotons , scene , inter , -ray.dir , maxSqrDis);

	// final gathering is too slow!

    //res = res + finalGathering(indirectMap , scene , inter , rng , -ray.dir , 50 , knnPhotons , maxSqrDis);
    
    res = res + estimate(causticMap , nCausticPaths , knnPhotons , scene , inter , -ray.dir , maxSqrDis);

	BSDF bsdf(-ray.dir , inter , scene);

	Real pdf , cosWo;
	int sampledType;
	Ray newRay;

	Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() ,
		newRay.dir , pdf , cosWo , &sampledType);

	if (bsdfFactor.isBlack())
		return res;

	// Russian Roulette
	Real contProb = bsdf.continueProb;

	if (cmp(contProb - 1.f) < 0)
	{
		if (cmp(rng.randFloat() - contProb) > 0)
			return res;
		pdf *= contProb;
	}
    
	newRay.origin = inter.p + newRay.dir * EPS;
	newRay.tmin = 0; newRay.tmax = INF;

	Color3 contrib = raytracing(newRay , dep + 1);

	res = res + (contrib | bsdfFactor) * (cosWo / pdf);

    return res;
}
static Color3 estimate(KdTree<Photon> *map , int nPaths , int knn ,
                                  Scene& scene , Intersection& inter , 
								  const Vector3& wo , Real maxSqrDis)
{
    Color3 res = Color3(0.0 , 0.0 , 0.0);
    
    if (map == NULL)
        return res;
    
    Photon photon;
    photon.pos = inter.p;

	ClosePhotonQuery query(knn , 0);

    Real searchSqrDis = maxSqrDis;
    Real msd; /* max square distance */
    while (query.kPhotons.size() < knn)
    {
        msd = searchSqrDis;
        query.init(msd);
        map->searchKnn(0 , photon.pos , query);
        searchSqrDis *= 2.0;
    }
    
    int nFoundPhotons = query.kPhotons.size();

    if (nFoundPhotons == 0)
        return res;
    
    Vector3 nv;
    if (cmp(wo ^ inter.n) < 0)
        nv = -inter.n;
    else
        nv = inter.n;

    Real scale = 1.0 / (PI * msd * nFoundPhotons);
    
	BSDF bsdf(wo , inter , scene);
	Real cosTerm , pdf;
    for (int i = 0; i < nFoundPhotons; i++)
    {
        /*
        Real k = kernel(kPhotons[i].photon , p , msd);
        k = 1.0 / PI;
        Real scale = k / (nPaths * msd);
        */
        if (cmp(nv ^ query.kPhotons[i].photon->wi) > 0)
        {
            Color3 brdf = bsdf.f(scene , query.kPhotons[i].photon->wi , 
				cosTerm , &pdf);
			if (brdf.isBlack())
				continue;
            res = res + (brdf | query.kPhotons[i].photon->alpha) * 
				(cosTerm * scale / pdf);
        }
        else
        {
			Vector3 wi(query.kPhotons[i].photon->wi);
			wi.z *= -1.f;
			Color3 brdf = bsdf.f(scene , wi , cosTerm , &pdf);
			if (brdf.isBlack())
				continue;
			res = res + (brdf | query.kPhotons[i].photon->alpha) * 
				(cosTerm * scale / pdf);
        }
    }
    return res;
}
void BidirPathTracing::runIteration(int iter)
{
	lightPathNum = height * width;
	cameraPathNum = lightPathNum;

	lightStateIndex.resize(lightPathNum);
	memset(&lightStateIndex[0] , 0 , lightStateIndex.size() * sizeof(int));

	lightStates.reserve(lightPathNum);
	lightStates.clear();

	cameraStates.reserve(cameraPathNum);
	cameraStates.clear();

	// generating light paths
	for (int pathIndex = 0; pathIndex < lightPathNum; pathIndex++)
	{
		BidirPathState lightState;

		bool deltaLight;
		generateLightSample(lightState);

		int s = 1;

		for (;; lightState.pathLength++ , s++)
		{
			Ray ray(lightState.origin + lightState.dir * EPS ,
				lightState.dir);
			Intersection inter;
			if (scene.intersect(ray , inter) == NULL)
				break;

			Vector3 hitPos = inter.p;

			BSDF bsdf(-ray.dir , inter , scene);
			if (!bsdf.isValid())
				break;

			lightState.pos = hitPos;
			lightState.bsdf = bsdf;

			if (lightState.pathLength > 1 || lightState.isFiniteLight)
			{
				lightState.dVCM *= mis(SQR(inter.t));
			}
			lightState.dVCM /= mis(std::abs(bsdf.cosWi()));
			lightState.dVC /= mis(std::abs(bsdf.cosWi()));
			
			if (!bsdf.isDelta)
				lightStates.push_back(lightState);

			// connect to camera
			if (!bsdf.isDelta)
			{
				if (lightState.pathLength + 1 >= minPathLength 
					&& lightState.pathLength + 1 == controlLength)
				{
					Vector3 imagePos = scene.camera.worldToRaster.tPoint(hitPos);
					if (scene.camera.checkRaster(imagePos.x , imagePos.y))
					{
						Color3 res = connectToCamera(lightState , hitPos , bsdf);

						// weight
						//Real weight = 1.f / (lightState.pathLength + 1 - lightState.specularVertexNum);

						film->addColor((int)imagePos.x , (int)imagePos.y , res);
					}
				}
			}

			if (lightState.pathLength + 2 > maxPathLength)
				break;

			if (!sampleScattering(bsdf , hitPos , lightState))
				break;
		}

		lightStateIndex[pathIndex] = (int)lightStates.size();
	}

	// generating camera paths
	for (int index = 0; index < cameraPathNum; index++)
	{
		int pathIndex = index % (height * width);

		if (pathIndex == 111 * 512 + 364)
		{
			int flag = 1;
		}

		BidirPathState cameraState;
		Vector3 screenSample = generateCameraSample(pathIndex , cameraState);

		Color3 color(0);

		for (;; cameraState.pathLength++)
		{
			Ray ray(cameraState.origin + cameraState.dir * EPS ,
				cameraState.dir);

			Intersection inter;

			if (scene.intersect(ray , inter) == NULL)
			{
				/*
				if (scene.background != NULL)
				{
					if (cameraState.pathLength >= minPathLength)
					{
						// weight
						Real weight = 1.f / (cameraState.pathLength - cameraState.specularVertexNum);

						color = color + (cameraState.throughput |
							getLightRadiance(scene.background ,
							cameraState , Vector3(0) , ray.dir)) * weight;
					}
				}
				*/
				break;
			}

			Vector3 hitPos = inter.p;

			BSDF bsdf(-ray.dir , inter , scene);
			if (!bsdf.isValid())
				break;

			cameraState.dVCM *= mis(SQR(inter.t));
			cameraState.dVCM /= mis(std::abs(bsdf.cosWi()));
			cameraState.dVC /= mis(std::abs(bsdf.cosWi()));

			if (inter.matId < 0)
			{
				AbstractLight *light = scene.lights[-inter.matId - 1];

				if (cameraState.pathLength >= minPathLength
					&& cameraState.pathLength == controlLength)
				{
					// weight
					//Real weight = 1.f / (cameraState.pathLength - cameraState.specularVertexNum);

					color = color + (cameraState.throughput |
						getLightRadiance(light , cameraState , 
						hitPos , ray.dir));
				}
				break;
			}

			if (cameraState.pathLength >= maxPathLength)
				break;
            
			// vertex connection: connect to light source
			if (!bsdf.isDelta)
			{
				if (cameraState.pathLength + 1 >= minPathLength
					&& cameraState.pathLength + 1 == controlLength)
				{
					// weight
					Real weight = 1.f / (cameraState.pathLength + 1.f - cameraState.specularVertexNum);

					color = color + (cameraState.throughput |
						getDirectIllumination(cameraState , hitPos , bsdf)) *
						weight;
				}
			}

			// vertex connection: connect to light vertices
			if (!bsdf.isDelta)
			{
				int st , ed;
				if (pathIndex == 0)
					st = 0;
				else
					st = lightStateIndex[pathIndex - 1];
				ed = lightStateIndex[pathIndex];

				for (int i = st; i < ed; i++)
				{
					BidirPathState& lightState = lightStates[i];

					if (lightState.pathLength + 1 + 
						cameraState.pathLength < minPathLength)
						continue;

					if (lightState.pathLength + 1 +
						cameraState.pathLength > maxPathLength)
						break;
					
					if (lightState.bsdf.isDelta)
						continue;

					Color3 tmp = connectVertices(lightState ,
						bsdf , hitPos , cameraState);

					// weight
					Real weight = 1.f / (lightState.pathLength + 1.f +
						cameraState.pathLength - lightState.specularVertexNum - 
						cameraState.specularVertexNum);

					if (lightState.pathLength + 1 + cameraState.pathLength ==
						controlLength)
					color = color + (cameraState.throughput |
						lightState.throughput | tmp) * weight;
				}
			}

			if (!sampleScattering(bsdf , hitPos , cameraState))
				break;
		}

		film->addColor((int)screenSample.x , (int)screenSample.y , color);
	}
}