Address path tracer merge leftover comments#991
Conversation
|
|
| return pdf * bilinear.backwardPdf(u); | ||
| } | ||
|
|
||
| scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) |
There was a problem hiding this comment.
| NBL_UNROLL for (int m=1; m<=2; ++m) | ||
| { | ||
| Cm *= r123s; | ||
| Sm = hlsl::promote<vector_type>(2.0) * evalSensitivity(hlsl::promote<vector_type>(scalar_type(m))*D, hlsl::promote<vector_type>(scalar_type(m)) *(phi23s+phi21s)); |
There was a problem hiding this comment.
at least awrite a TODO about hoisting this common subexpression
There was a problem hiding this comment.
only the sampling::SphericalTriangle is needed #966 (comment)
There was a problem hiding this comment.
most parameters should be members #966 (comment)
There was a problem hiding this comment.
Also #966 (comment)
| return retval; | ||
| } | ||
|
|
||
| vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) |
There was a problem hiding this comment.
why is this returning vector4_type instead of sampling::bilinear
| static ProjectedSphericalTriangle<T> create(NBL_CONST_REF_ARG(shapes::SphericalTriangle<T>) tri) | ||
| { | ||
| ProjectedSphericalTriangle<T> retval; | ||
| retval.tri = tri; | ||
| return retval; | ||
| } |
There was a problem hiding this comment.
remove, see #966 (comment)
| vector3_type cos_vertices, sin_vertices; | ||
| const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); | ||
| const scalar_type solidAngle = tri.solidAngle(cos_vertices, sin_vertices); |
There was a problem hiding this comment.
should be precomputed and stored as members #966 (comment)
There was a problem hiding this comment.
again function with bazillion parameters that are really local members
| using matrix3x3_type = matrix<Scalar, 3, 3>; | ||
|
|
||
| static SphericalRectangle<scalar_type> create(const vector3_type observer, const vector3_type rectangleOrigin, const matrix3x3_type basis) | ||
| static SphericalRectangle<Scalar> create(const vector3_type rectangleOrigin, const vector3_type right, const vector3_type up) |
There was a problem hiding this comment.
why are you not taking CompressedSphericalRectangle as input instead of 3 variables ?
| cos_vertices[2] = hlsl::dot<vector3_type>(awayFromEdgePlane0, awayFromEdgePlane1); | ||
| // TODO: above dot products are in the wrong order, either work out which is which, or try all 6 permutations till it works | ||
| cos_vertices = hlsl::clamp<vector3_type>((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote<vector3_type>(-1.0), hlsl::promote<vector3_type>(1.0)); | ||
| cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote<vector3_type>(-1.0), hlsl::promote<vector3_type>(1.0)); |
There was a problem hiding this comment.
this should be in a common private method, its same as above
| retval.extents = vector2_type(hlsl::length(right), hlsl::length(up)); | ||
| retval.basis[0] = right / retval.extents[0]; | ||
| retval.basis[1] = up / retval.extents[1]; | ||
| retval.basis[2] = hlsl::normalize(hlsl::cross(retval.basis[0], retval.basis[1])); |
There was a problem hiding this comment.
add assert that the absolute value of dot product of cross product args is >0.f
| // The matrix should be a matrix<scalar_type,3,4> where the last column is the translation, a 3x3 matrix with a pre-transform translation (worldSpace rectangle origin to be subtracted). | ||
|
|
||
| // You can compute it from an OBB matrix (as given by/to imguizmo to position a [0,1]^2 rectangle mesh where Z+ is the front face. | ||
|
|
There was a problem hiding this comment.
add the code snippet to ocomments
There was a problem hiding this comment.
There was a problem hiding this comment.
the observer should be passed during create because while we can have our shape::SphericalRectangle and query its solid angle from multiple observes, the sole purpose of sampling::SphericalRectangle is to draw multiple samples
As much as possible should be precomputed, like the cosGamma etc,
There was a problem hiding this comment.
There was a problem hiding this comment.
| // We don't allow non watertight transmitters in this renderer | ||
| bool validPath = nee_sample.getNdotL() > numeric_limits<scalar_type>::min && nee_sample.isValid(); |
There was a problem hiding this comment.
this is to implement in your NEE system
| bxdf::fresnel::OrientedEtas<monochrome_type> orientedEta = bxdf::fresnel::OrientedEtas<monochrome_type>::create(interaction.getNdotV(), hlsl::promote<monochrome_type>(monochromeEta)); | ||
| anisocache_type _cache = anisocache_type::template create<anisotropic_interaction_type, sample_type>(interaction, nee_sample, orientedEta); | ||
| validPath = validPath && _cache.getAbsNdotH() >= 0.0; | ||
| bxdf.params.eta = monochromeEta; |
There was a problem hiding this comment.
this should be done in your NEE system, and return a pdf thats NaN or 0
There was a problem hiding this comment.
probably 0 so we dont have issues with MIS weights
| validPath = validPath && _cache.getAbsNdotH() >= 0.0; | ||
| bxdf.params.eta = monochromeEta; | ||
|
|
||
| if (neeContrib_pdf.pdf < numeric_limits<scalar_type>::max) |
There was a problem hiding this comment.
this seems off, you should be checking for pdf>0.f
| quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, nee_sample, interaction.isotropic, _cache.iso_cache); | ||
| neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; |
There was a problem hiding this comment.
why is there an albedo thing going on?
This should literally be nee.quotient *= materialSystem.eval*rcpChoiceProb;
There was a problem hiding this comment.
you definitely don't want to call quotient, because that picks up contributions and pdfs from dirac lobes which you shouldn't be able to pick up with other samplers
| const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; | ||
| const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; | ||
| neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic |
There was a problem hiding this comment.
This is completely wrong, the MIS weight should be a ratio of PDFs
generator^2/(generator^2+notGenerator^2)
which is
1/(1+(notGenerator/generator)^2)
You've used the BSDF's PDF as the GENERATOR! The MIS weights are wrong.
in the code this should have the form
const scalar_type otherGenOverLightAndChoice = bsdf.pdf * rcpChoiceProb/ neeContrib.pdf;
nee.quotient /= (1.f+otherGenOverLightAndChoice*otherGenOverLightAndChoice);
and only get applied you can always apply, its numerically stableif (neeContrib_pdf.pdf < numeric_limits<scalar_type>::max) because when it -> INF you'll get the MIS weight going to 1.f
| ray_type nee_ray; | ||
| nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance<scalar_type>::getStart(depth); | ||
| nee_ray.direction = nee_sample.getL().getDirection(); | ||
| nee_ray.intersectionT = t; |
There was a problem hiding this comment.
can package inside intersector.traceShadowRay
| nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance<scalar_type>::getStart(depth); | ||
| nee_ray.direction = nee_sample.getL().getDirection(); | ||
| nee_ray.intersectionT = t; | ||
| if (bsdf_quotient_pdf.pdf < numeric_limits<scalar_type>::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && intersector_type::traceRay(nee_ray, scene).id == -1) |
There was a problem hiding this comment.
you shouldn't be checking bsdf_quotient_pdf.pdf < numeric_limits<scalar_type>::max here... eval should never return a huge pdf really (dirac lobes should be skipped both for eval and pdf of eval - they differ with quotient), you could assert it
There was a problem hiding this comment.
btw the traceRay should be a traceShadowRay and it should take the objectID of what you intend to hit and return a floating point "fuzzy" shadow (because I can make it attenuate NEE rays when there are diffuse translucents or thindielectrics)
| // example only uses isotropic bxdfs | ||
| // the value of the bsdf divided by the probability of the sample being generated | ||
| quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, bsdf_sample, interaction.isotropic, _cache.iso_cache); | ||
| throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; |
There was a problem hiding this comment.
what's the albedo doing here!? its literally part of the material!
| const float lumaThroughputThreshold = lumaContributionThreshold; | ||
| if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) | ||
| { | ||
| ray.payload.throughput = throughput; |
There was a problem hiding this comment.
shouldn't be touching a member directly you should have a method to multiply in a new factor
| // trace new ray | ||
| ray.origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance<scalar_type>::getStart(depth); | ||
| ray.direction = bxdfSample; | ||
| NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) | ||
| { | ||
| ray.normalAtOrigin = interaction.getN(); | ||
| ray.wasBSDFAtOrigin = isBSDF; | ||
| } |
There was a problem hiding this comment.
this needs to be tucked inside your ray as a method or something similar
| scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch | ||
| ray.payload.otherTechniqueHeuristic = otherTechniqueHeuristic * otherTechniqueHeuristic; |
There was a problem hiding this comment.
do as a method to set it, something with namign that connects with with foundEmissionMIS
|
Merge #989 into this PR |
|
|
||
| const scalar_type LdotH = hlsl::mix(VdotH, ieee754::copySign(hlsl::sqrt(rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]), -VdotH), transmitted); | ||
| if (hlsl::isnan(LdotH)) | ||
| return sample_type::createInvalid(); |
There was a problem hiding this comment.
do it like this then
scalar_type LdotH;
if (transmitted)
{
float det = rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0];
if (det<0.f)
return sample_type::createInvalid();
LdotH = ieee754::copySign(hlsl::sqrt(det), -VdotH);
}
else
LdotH = VdotH;| // We don't allow non watertight transmitters in this renderer | ||
| bool validPath = nee_sample.getNdotL() > numeric_limits<scalar_type>::min && nee_sample.isValid(); | ||
| // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself | ||
| bxdf::fresnel::OrientedEtas<monochrome_type> orientedEta = bxdf::fresnel::OrientedEtas<monochrome_type>::create(interaction.getNdotV(), hlsl::promote<monochrome_type>(monochromeEta)); | ||
| anisocache_type _cache = anisocache_type::template create<anisotropic_interaction_type, sample_type>(interaction, nee_sample, orientedEta); | ||
| validPath = validPath && _cache.getAbsNdotH() >= 0.0; | ||
| bxdf.params.eta = monochromeEta; | ||
|
|
||
| if (neeContrib_pdf.pdf < numeric_limits<scalar_type>::max) |
There was a problem hiding this comment.
shouldn't this be tucked inside the if statement here?
Also you can just assert the nee_sample.isValid() if the neeContrib.pdf>0.f
There was a problem hiding this comment.
drop the _pdf suffix
| measure_type emissive = nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput; | ||
| scalar_type _pdfSq = hlsl::mix(_pdf, _pdf * _pdf, _pdf < numeric_limits<scalar_type>::max); | ||
| emissive /= (1.0 + _pdfSq * ray.payload.otherTechniqueHeuristic); | ||
| ray.payload.accumulation += emissive; |
There was a problem hiding this comment.
I think the reason you got NaN is because _pdfSq was Max*Max giving you Inf, and otherTechniqueHeuristic was 0
Its a bit weird that your Area Light's PDF is near Max or INF, because this can only happen when Solid Angle or Projected Solid Angle is absolutely tiny, or when during area sampling the distance is huge or the angle of the ray to emitter normal is near orthogonal.
Because you only support 2 generators in this Path Tracer, I can allow you to not square your PDFs until you compue their ratios (square the ratio at the end), this way you'll have more numerical precision.
If you had 4 generators in your MIS weight then you'd have one MUL extra than doing it the way we did it until now.
Because the BxDF sample was generated and we've arrived here, it must be that the BxDF PDF was larger than 0.f, so no division by 0.
The other technique heuristic is neeProbability / bxdfPdf .
What could have gone wrong ? Your BxDF could have been a delta ditribution or close to, so the PDF would have been huge.
Would that have mattered? Probably not because neeProbability would be between 0 and 1, so you're divising a finite number by a non-zero number, so you can get between 0 and not infinity.
However what can happen is that the otherTechniqnueHeuristic is 0 due to delta distribution or zero NEE probability, then in the code I gave #991 (comment)
the MIS condition should be
if (otherTechniqueHeuristic>min)
if (const uint lightID=scene.getLightID(objectID); lightID)instead of just
if (const uint lightID=scene.getLightID(objectID); lightID)meaning there was nothing to MIS in the first place
There was a problem hiding this comment.
you should also probably assert that the _pdf of the light is not zero outside of the if-statement
| if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) | ||
| { | ||
| ray.payload.throughput = throughput; | ||
| scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch |
There was a problem hiding this comment.
assert that otherTechniqueHeuristic is finite (less than infinity)
| const scalar_type neeProbability = bxdf.getNEEProb(); | ||
| scalar_type rcpChoiceProb; | ||
| sampling::PartitionRandVariable<scalar_type> partitionRandVariable; | ||
| partitionRandVariable.leftProb = neeProbability; |
There was a problem hiding this comment.
assert neeProbability is between 0 and 1 inclusive
Makes changes left from #966