return sqrt(2.0 / (variance + 2.0));
}
float FilterPerceptualSmoothness(float perceptualSmoothness, float variance, float threshold)
{
float roughness = PerceptualSmoothnessToRoughness(perceptualSmoothness);
float squaredRoughness = saturate(roughness * roughness + min(2.0 * variance, threshold * threshold)); // threshold can be really low, square the value for easier control
return 1.0 - RoughnessToPerceptualRoughness(sqrt(squaredRoughness));
}
// take perceptualSmoothness and return modified perceptualSmoothness
float GeometricFilterPerceptualSmoothness(float perceptualSmoothness, float3 geometricNormalWS, float screenSpaceVariance, float threshold)
// Specular antialiasing for geometry-induced normal (and NDF) variations: Tokuyoshi / Kaplanyan et al.'s method.
// This is the deferred approximation, which works reasonably well so we keep it for forward too for now.
// screenSpaceVariance should be at most 0.5^2 = 0.25, as that corresponds to considering
// a gaussian pixel reconstruction kernel with a standard deviation of 0.5 of a pixel, thus 2 sigma covering the whole pixel.
float GeometricFilterVariance(float3 geometricNormalWS, float screenSpaceVariance)
float variance = screenSpaceVariance * (dot(deltaU, deltaU) + dot(deltaV, deltaV));
return screenSpaceVariance * (dot(deltaU, deltaU) + dot(deltaV, deltaV));
}
float roughness = PerceptualSmoothnessToRoughness(perceptualSmoothness);
float squaredRoughness = saturate(roughness * roughness + min(2.0 * variance, threshold * threshold)); // threshold can be really low, square the value for easier control
float GeometricFilterPerceptualSmoothness(float perceptualSmoothness, float3 geometricNormalWS, float screenSpaceVariance, float threshold)
{
float variance = GeometricFilterVariance(geometricNormalWS, screenSpaceVariance);
return FilterPerceptualSmoothness(perceptualSmoothness, variance, threshold);
}
return 1.0 - RoughnessToPerceptualRoughness(sqrt(squaredRoughness));
// Normal map filtering based on The Order : 1886 SIGGRAPH course notes implementation.
// Basically Toksvig with an intermediate single vMF lobe induced dispersion (Han et al. 2007)
//
// This returns 2 times the variance of the induced "mesoNDF" lobe (an NDF induced from a section of
// the normal map) from the level 0 mip normals covered by the "current texel".
//
// avgNormalLength gives the dispersion information for the covered normals.
//
// Note that hw filtering on the normal map should be trilinear to be conservative, while anisotropic
// risk underfiltering. Could also compute average normal on the fly with a proper normal map format,
// like Toksvig.
float TextureFilteringVariance(float avgNormalLength)
{
if (avgNormalLength < 1.0)
{
float avgNormLen2 = avgNormalLength * avgNormalLength;
float kappa = (3.0 * avgNormalLength - avgNormalLength * avgNormLen2) / (1.0 - avgNormLen2);
// Relationship between gaussian lobe and vMF lobe is 2 * variance = 1 / (2 * kappa) = roughness^2
// so to get variance we must use variance = 1 / (4 * kappa)
return 0.25 * kappa;
}
return 0.0f;
}
float TextureFilterPerceptualSmoothness(float perceptualSmoothness, float avgNormalLength, float threshold)
{
float variance = TextureFilteringVariance(avgNormalLength);
return FilterPerceptualSmoothness(perceptualSmoothness, variance, threshold);
}
// ----------------------------------------------------------------------------