Path tracing SSS Albedo Remapping — Unconditional G Correction Possible Bug

The question: Is the unconditional G correction intentionally applied to the TIR path as well, or is this a bug? Applying the Imageworks anisotropy correction on top of the Disney/Hyperion fit seems incorrect since the two models have different derivations and assumptions.

#if SSS_USE_TIR
	float3 Albedo = 1 - exp(SSS.Color * (-11.43 + SSS.Color * (15.38 - 13.91 * SSS.Color)));
#else
	// Van de-Hulst inverse mapping
	// https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf (Slide 44)
	// http://www.eugenedeon.com/project/a-hitchhikers-guide-to-multiple-scattering/ (Section 7.5.3 of v0.1.3)
	float3 Albedo = 1 - Pow2(4.09712 + 4.20863 * SSS.Color - sqrt(9.59217 + SSS.Color * (41.6808 + 17.7126 * SSS.Color)));
	SSS.Radius *= 2.0; // roughly match parameterization above
#endif

	// Subsurface guiding is implemented following the Dwivedi random walk technique described here:
	// https://cgg.mff.cuni.cz/~jaroslav/papers/2014-zerovar/
	// http://www.eugenedeon.com/project/zerovar2020/
	// A thin-slab approximation is used to improve the guiding in thin regions as well as described in the video presentation (slides 37-39).
#define SSS_USE_DWIVEDI 1
#define SSS_USE_DWIVEDI_USE_THIN_SLABS 1 // Probe the geometry to have an estimate of thickness - and use this to guide toward front or backside, depending on which is closer

	// Revisiting Physically Based Shading at Imageworks.
	// https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf
	float G = SSS.G;
	Albedo = Albedo / (1 - G * (1 - Albedo));



This is intentional, the correction for ‘G’ is independent of the albedo inversion method used (both of which are derived for an isotropic medium).

You can derive this by starting from first order similarity theory.

Did you notice any rendering issues that you believe are connected to this?

In all our tests the mapping we currently have implemented seems to be working fairly well in terms of preserving albedo. Have you seen a case where it doesn’t?

The main thing to realize is that like I said, any isotropic mapping can account for anisotropic phase functions as a second step. In other words, it is mostly independent of the boundary condition used.

This means that both the van-de-hulst mapping or the hyperion mapping are “compatible” with the phase function correction.

Is there a particular rendering issue you are trying to solve?

Its worth remembering that these albedo mappings are derived for regions where the material is mostly opaque (ie: they assume a semi-infinite half-space). In thin regions, the mapping is not expected to work perfectly as the effect of translucency starts to become more important, so some fine-tuning may be necessary.

I wonder about the formula from The Design and Evolution of Disney’s Hyperion Renderer (https://media.disneyanimation.com/uploads/production/publication\_asset/177/asset/a.pdf), which is derived from Practical and Controllable Subsurface Scattering for Production Path Tracing.

The Disney approximation is based on isotropic scattering, which can be found in the paper (specifically, the assumption: “we assume isotropic scattering with a diffuse interface”). However, Xie Tiantian’s feature assumes an anisotropic scattering, and therefore this formula cannot be applied directly.

SHA-1: a19d0bbb1ae8d06e2893ae3aa143eb5bb69a12ba

* Add anisotropic subsurface scattering for path tracing.

1. Add a new node Subsurface Medium with two input MeanFreePath and ScatteringDistribution. If MeanFreePath is not connected, fallback to the old behavior (e.g., use the derived MFP for subsurface profile shading model), If ScatteringDistribution is not connected, fallback to zero.

2. It can be used by Subsurface, eye and subsurface profile/Preintegrated shading model.

#jira UE-173642

#preflight 63d00f38f2318350a286737d

#rb chris.kulla

#preflight 63d01522f2318350a289ab71

[CL 23833874 by Tiantian Xie in ue5-main branch]

Another part of my confusion comes from the OpenPBR implementation. (https://github.com/adobe/openpbr-bsdf/blob/main/impl/openpbr_volume_utils.h)

The author says exactly what I want to ask about here:

    // This is the Hyperion mapping:
    //         const vec3 A = subsurface_color;
    //         const vec3 AA = A * A;
    //         const vec3 AAA = AA * A;
    //         const vec3 AAAA = AA * AA;
    //         const vec3 single_scattering_albedo = -expm1(-11.43f * A + 15.38f * AA - 13.91f * AAA);
    // TODO(sss): The anisotropy would need to be taken into account if this were to be used.

albedo.png(1.23 MB)

There is no specific rendering issue to solve right now, but thin translucent geometry with high forward scattering (g > 0.7), like ear lobes or leaf edges, where the transmitted color may appearing too saturated/dark compared to reference renders. The albedo magnitude seems preserved as you say, but the spatial falloff profile of the scattering may appears narrower than expected.

The question was simply to understand whether using the Hyperion mapping in anisotropic cases is an intentional design decision or an oversight.

Thank you for the answer.

Thanks for the suggestion about fine-tuning albedo mappings for thin surfaces.

Beside albedo mapping, I suppose the Dwivedi random walk perfectly solves thin surface transmission.

Thanks for the reply again! If I have more questions, I’ll be sure to reach out.

#if SSS_USE_DWIVEDI_USE_THIN_SLABS
		if (bDoSlabSearch)
		{
			FRayDesc ProbeRay;
			ProbeRay.Origin = Ray.Origin;
			ProbeRay.Direction = -DwivediSlabNormal;
			ProbeRay.TMin = 0.0;
			ProbeRay.TMax = 10 * max3(SSS.Radius.x, SSS.Radius.y, SSS.Radius.z);
			int ProbeInterfaceCounter = InterfaceCounter;
			FProbeResult Result = TraceSSSProbeRay(ProbeRay, ProbeInterfaceCounter);
			if (Result.IsMiss())
			{
				// didn't find a hit, register missing slab
				SlabThickness = -1.0;
			}
			else
			{
				// got a valid hit -- use it as our thickness
				SlabThickness = Result.HitT;
			}
			bDoSlabSearch = false;
		}
 
		// Instead of only guiding towards the slab front (reflection), also guide toward the slack back (tranmission) when the surface is thin
		// The heuristic to choose between guiding front or back is determined by the following probability, given in [2] on slide 37 of the video
		// Note that the depths in the video presentation are optical depths, so have to be multiplied by SigmaT
		float SlabZ = clamp(dot(DwivediSlabOrigin - Ray.Origin, DwivediSlabNormal), 0.0, SlabThickness);
		float3 ProbT = SlabThickness > 0.0 ? rcp(1 + exp(SigmaT * (SlabThickness - 2 * SlabZ) / DwivediScale)) : 0.0;
#else
		float3 ProbT = 0.0;
#endif