public (float, float) Pdf(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath) { if (ShadingSpace.SameHemisphere(outDir, inDir)) { return(0, 0); } return(ComputeOneDir(outDir, inDir), ComputeOneDir(inDir, outDir)); }
public RgbColor Evaluate(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath) { if (ShadingSpace.SameHemisphere(outDir, inDir)) { return(RgbColor.Black); // transmission only } float cosThetaO = ShadingSpace.CosTheta(outDir); float cosThetaI = ShadingSpace.CosTheta(inDir); if (cosThetaI == 0 || cosThetaO == 0) { return(RgbColor.Black); } // Compute the half vector float eta = ShadingSpace.CosTheta(outDir) > 0 ? (insideIOR / outsideIOR) : (outsideIOR / insideIOR); Vector3 wh = Vector3.Normalize(outDir + inDir * eta); if (ShadingSpace.CosTheta(wh) < 0) { wh = -wh; } if (Vector3.Dot(outDir, wh) * Vector3.Dot(inDir, wh) > 0) { return(RgbColor.Black); } var F = new RgbColor(FresnelDielectric.Evaluate(Vector3.Dot(outDir, wh), outsideIOR, insideIOR)); float sqrtDenom = Vector3.Dot(outDir, wh) + eta * Vector3.Dot(inDir, wh); float factor = isOnLightSubpath ? (1 / eta) : 1; var numerator = distribution.NormalDistribution(wh) * distribution.MaskingShadowing(outDir, inDir); numerator *= eta * eta * Math.Abs(Vector3.Dot(inDir, wh)) * Math.Abs(Vector3.Dot(outDir, wh)); numerator *= factor * factor; var denom = (cosThetaI * cosThetaO * sqrtDenom * sqrtDenom); Debug.Assert(float.IsFinite(denom)); return((RgbColor.White - F) * transmittance * Math.Abs(numerator / denom)); }
public (float, float) Pdf(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath) { if (!ShadingSpace.SameHemisphere(outDir, inDir)) { return(0, 0); } var halfVector = outDir + inDir; // catch NaN causing corner cases if (halfVector == Vector3.Zero) { return(0, 0); } halfVector = Vector3.Normalize(halfVector); var pdfForward = distribution.Pdf(outDir, halfVector) / Math.Abs(4 * Vector3.Dot(outDir, halfVector)); var pdfReverse = distribution.Pdf(inDir, halfVector) / Math.Abs(4 * Vector3.Dot(inDir, halfVector)); return(pdfForward, pdfReverse); }
public Vector3?Sample(Vector3 outDir, bool isOnLightSubpath, Vector2 primarySample) { if (outDir.Z == 0) { return(null); } var halfVector = distribution.Sample(outDir, primarySample); if (Vector3.Dot(halfVector, outDir) < 0) { return(null); } var inDir = ShadingSpace.Reflect(outDir, halfVector); if (!ShadingSpace.SameHemisphere(outDir, inDir)) { return(null); } return(inDir); }
public RgbColor Evaluate(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath) { if (!ShadingSpace.SameHemisphere(outDir, inDir)) { return(RgbColor.Black); } float cosThetaO = ShadingSpace.AbsCosTheta(outDir); float cosThetaI = ShadingSpace.AbsCosTheta(inDir); Vector3 halfVector = inDir + outDir; // Handle degenerate cases for microfacet reflection if (cosThetaI == 0 || cosThetaO == 0) { return(RgbColor.Black); } if (halfVector.X == 0 && halfVector.Y == 0 && halfVector.Z == 0) { return(RgbColor.Black); } // For the Fresnel call, make sure that wh is in the same hemisphere // as the surface normal, so that total internal reflection is handled correctly. halfVector = Vector3.Normalize(halfVector); if (ShadingSpace.CosTheta(halfVector) < 0) { halfVector = -halfVector; } var cosine = Vector3.Dot(inDir, halfVector); var f = fresnel.Evaluate(cosine); var nd = distribution.NormalDistribution(halfVector); var ms = distribution.MaskingShadowing(outDir, inDir); return(tint * nd * ms * f / (4 * cosThetaI * cosThetaO)); }