using System ;
#if UNITY_EDITOR
using UnityEditor ;
using UnityEditor ;
[GenerateHLSL]
public class SssConstants
{
public const int SSS_N_PROFILES = 8 ; // Max. number of profiles, including the slot taken by the neutral profile
public const int SSS_NEUTRAL_PROFILE_ID = SSS_N_PROFILES - 1 ; // Does not result in blurring
public const int SSS_N_SAMPLES_NEAR_FIELD = 6 4 ; // Used for extreme close ups; must be a power of 2
public const int SSS_N_SAMPLES_FAR_FIELD = 3 2 ; // Used at a regular distance; must be a power of 2
public const int SSS_TRSM_MODE_NONE = 0 ;
public const int SSS_TRSM_MODE_THIN = 1 ;
}
public enum TexturingMode : int { PreAndPostScatter = 0 , PostScatter = 1 } ;
public enum TexturingMode : uint { PreAndPostScatter = 0 , PostScatter = 1 } ;
public enum TransmissionMode : uint { None = SssConstants . SSS_TRSM_MODE_NONE , ThinObject = SssConstants . SSS_TRSM_MODE_THIN , Regular } ;
public const int numSamples = 1 1 ; // Must be an odd number
public const float distanceScale = 3 ; // SSS distance units per centimeter
[ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
public Color scatterDistance1 ;
[ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
public Color scatterDistance2 ;
public float lerpWeight ;
public TexturingMode texturingMode ;
public bool enableTransmission ;
public Color tintColor ;
public Vector2 thicknessRemap ;
public Color surfaceAlbedo ; // Color, 0 to 1
public float scatteringDistance ; // Effective radius (in millimeters)
public TexturingMode texturingMode ;
public TransmissionMode transmissionMode ;
public Vector2 thicknessRemap ; // X = min, Y = max (in millimeters)
public int settingsIndex ;
public int settingsIndex ;
Vector4 [ ] m_FilterKernel ;
Vector3 m_S ; // RGB shape parameter: S = 1 / D
Vector3 [ ] m_HalfRcpVariances ;
Vector4 [ ] m_FilterKernelNearField ; // RGB = weights, A = radial distance (in millimeters)
Vector4 m_HalfRcpWeightedVariances ;
Vector4 [ ] m_FilterKernelFarField ; // RGB = weights, A = radial distance (in millimeters)
scatterDistance1 = new Color ( 0.3f , 0.3f , 0.3f , 0.0f ) ;
scatterDistance2 = new Color ( 0.5f , 0.5f , 0.5f , 0.0f ) ;
lerpWeight = 1.0f ;
surfaceAlbedo = Color . white ;
scatteringDistance = 5.0f ;
enableTransmission = false ;
tintColor = Color . white ;
thicknessRemap = new Vector2 ( 0 , 0.5f ) ;
settingsIndex = SubsurfaceScatteringSettings . neutralProfileID ; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
UpdateKernelAndVarianceData ( ) ;
}
public Vector4 [ ] filterKernel
{
// Set via UpdateKernelAndVarianceData().
get { return m_FilterKernel ; }
}
public Vector3 [ ] halfRcpVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpVariances ; }
}
transmissionMode = TransmissionMode . None ;
thicknessRemap = new Vector2 ( 0.0f , 5.0f ) ;
settingsIndex = SssConstants . SSS_NEUTRAL_PROFILE_ID ; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
public Vector4 halfRcpWeightedVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpWeightedVariances ; }
BuildKernel ( ) ;
public void UpdateKernelAndVarianceData ( )
// Ref: Approximate Reflectance Profiles for Efficient Subsurface Scattering by Pixar.
public void BuildKernel ( )
if ( m_FilterKernel = = null | | m_FilterKernel . Length ! = numSamples )
if ( m_FilterKernelNearField = = null | | m_FilterKernelNearField . Length ! = SssConstants . SSS_N_SAMPLES_NEAR_FIELD )
m_FilterKernel = new Vector4 [ numSamples ] ;
m_FilterKernelNearField = new Vector4 [ SssConstants . SSS_N_SAMPLES_NEAR_FIELD ] ;
if ( m_HalfRcpVariances = = null )
if ( m_FilterKernelFarField = = null | | m_FilterKernelFarField . Length ! = SssConstants . SSS_N_SAMPLES_FAR_FIELD )
m_HalfRcpVariances = new Vector3 [ 2 ] ;
m_FilterKernelFarField = new Vector4 [ SssConstants . SSS_N_SAMPLES_FAR_FIELD ] ;
// Apply the three-sigma rule, and rescale.
Color stdDev1 = ( ( 1.0f / 3.0f ) * distanceScale ) * scatterDistance1 ;
Color stdDev2 = ( ( 1.0f / 3.0f ) * distanceScale ) * scatterDistance2 ;
m_S = new Vector3 ( ) ;
// Our goal is to blur the image using a filter which is represented
// as a product of a linear combination of two normalized 1D Gaussians
// as suggested by Jimenez et al. in "Separable Subsurface Scattering".
// A normalized (i.e. energy-preserving) 1D Gaussian with the mean of 0
// is defined as follows: G1(x, v) = exp(-x * x / (2 * v)) / sqrt(2 * Pi * v),
// where 'v' is variance and 'x' is the radial distance from the origin.
// Using the weight 'w', our 1D and the resulting 2D filters are given as:
// A1(v1, v2, w, x) = G1(x, v1) * (1 - w) + G1(r, v2) * w,
// A2(v1, v2, w, x, y) = A1(v1, v2, w, x) * A1(v1, v2, w, y).
// The resulting filter function is a non-Gaussian PDF.
// It is separable by design, but generally not radially symmetric.
// Evaluate the fit for diffuse surface transmission.
m_S . x = FindFitForS ( surfaceAlbedo . r ) ;
m_S . y = FindFitForS ( surfaceAlbedo . g ) ;
m_S . z = FindFitForS ( surfaceAlbedo . b ) ;
// Compute the length of the volume mean free path (in millimeters).
float L = 0.1f * scatteringDistance ;
// N.b.: our scattering distance is rather limited. Therefore, in order to allow
// for a greater range of standard deviation values for flatter profiles,
// we rescale the world using 'distanceScale', effectively reducing the SSS
// distance units from centimeters to (1 / distanceScale).
// Compute { 1 / D = S / L } as you can substitute s = 1 / d in all formulas.
m_S . x * = 1.0f / L ;
m_S . y * = 1.0f / L ;
m_S . z * = 1.0f / L ;
// Find the widest Gaussian across 3 color channels.
float maxStdDev1 = Mathf . Max ( stdDev1 . r , stdDev1 . g , stdDev1 . b ) ;
float maxStdDev2 = Mathf . Max ( stdDev2 . r , stdDev2 . g , stdDev2 . b ) ;
// We importance sample the color channel with the highest albedo value,
// since higher albedo values result in scattering over a larger distance.
// S(A) is a monotonically decreasing function.
float s = Mathf . Min ( m_S . x , m_S . y , m_S . z ) ;
// Importance sample the normalized diffusion profile for the computed value of 's'.
// ------------------------------------------------------------------------------------
// R(r, s) = s * (Exp[-r * s] + Exp[-r * s / 3]) / (8 * Pi * r)
// PDF(r, s) = s * (Exp[-r * s] + Exp[-r * s / 3]) / 4
// CDF(r, s) = 1 - 1/4 * Exp[-r * s] - 3/4 * Exp[-r * s / 3]
// ------------------------------------------------------------------------------------
float step = 1.0f / ( numSamples - 1 ) ;
// Importance sample the linear combination of two Gaussians.
for ( int i = 0 ; i < numSamples ; i + + )
// Importance sample the near field kernel.
for ( int i = 0 ; i < SssConstants . SSS_N_SAMPLES_NEAR_FIELD ; i + + )
// Generate 'u' on (0, 0.5] and (0.5, 1).
float u = ( i < = numSamples / 2 ) ? 0.5f - i * step // The center and to the left
: i * step ; // From the center to the right
float p = i * ( 1.0f / SssConstants . SSS_N_SAMPLES_NEAR_FIELD ) ;
float r = KernelCdfInverse ( p , s ) ;
u = Mathf . Clamp ( u , 0.001f , 0.999f ) ;
float pos = GaussianCombinationCdfInverse ( u , maxStdDev1 , maxStdDev2 , lerpWeight ) ;
float pdf = GaussianCombination ( pos , maxStdDev1 , maxStdDev2 , lerpWeight ) ;
float pdf = KernelPdf ( r , s ) ;
val . x = GaussianCombination ( pos , stdDev1 . r , stdDev2 . r , lerpWeight ) ;
val . y = GaussianCombination ( pos , stdDev1 . g , stdDev2 . g , lerpWeight ) ;
val . z = GaussianCombination ( pos , stdDev1 . b , stdDev2 . b , lerpWeight ) ;
// N.b.: we multiply by the surface albedo of the actual geometry during shading.
val . x = KernelValCircle ( r , m_S . x ) ;
val . y = KernelValCircle ( r , m_S . y ) ;
val . z = KernelValCircle ( r , m_S . z ) ;
// float a = (2.0f * Mathf.PI) * VanDerCorputBase2(i);
// m_FilterKernelNearFieldPositions[i] = new Vector2(r * Mathf.Cos(a), r * Mathf.Sin(a));
// We do not divide by 'numSamples' since we will renormalize, anyway.
m_FilterKernel [ i ] . x = val . x * ( 1 / pdf ) ;
m_FilterKernel [ i ] . y = val . y * ( 1 / pdf ) ;
m_FilterKernel [ i ] . z = val . z * ( 1 / pdf ) ;
m_FilterKernel [ i ] . w = pos ;
// We do not divide by 'NUM_SAMPLES_NEAR_FIELD' due to the subsequent weight normalization.
m_FilterKernelNearField [ i ] . x = val . x * ( 1.0f / pdf ) ;
m_FilterKernelNearField [ i ] . y = val . y * ( 1.0f / pdf ) ;
m_FilterKernelNearField [ i ] . z = val . z * ( 1.0f / pdf ) ;
m_FilterKernelNearField [ i ] . w = r ;
weightSum . x + = m_FilterKernel [ i ] . x ;
weightSum . y + = m_FilterKernel [ i ] . y ;
weightSum . z + = m_FilterKernel [ i ] . z ;
weightSum . x + = m_FilterKernelNearField [ i ] . x ;
weightSum . y + = m_FilterKernelNearField [ i ] . y ;
weightSum . z + = m_FilterKernelNearField [ i ] . z ;
// Renormalize the weights to conserve energy.
for ( int i = 0 ; i < numSamples ; i + + )
// As the first sample is in the center of the filter,
// we can use its radius slot to store the max radius of the kernel.
m_FilterKernelNearField [ 0 ] . w = m_FilterKernelNearField [ SssConstants . SSS_N_SAMPLES_NEAR_FIELD - 1 ] . w ;
// Normalize the weights to conserve energy.
for ( int i = 0 ; i < SssConstants . SSS_N_SAMPLES_NEAR_FIELD ; i + + )
m_FilterKernel [ i ] . x * = 1 / weightSum . x ;
m_FilterKernel [ i ] . y * = 1 / weightSum . y ;
m_FilterKernel [ i ] . z * = 1 / weightSum . z ;
m_FilterKernelNearField [ i ] . x * = 1.0f / weightSum . x ;
m_FilterKernelNearField [ i ] . y * = 1.0f / weightSum . y ;
m_FilterKernelNearField [ i ] . z * = 1.0f / weightSum . z ;
// Store (1 / (2 * Variance)) per color channel per Gaussian.
m_HalfRcpVariances [ 0 ] . x = 0.5f / ( stdDev1 . r * stdDev1 . r ) ;
m_HalfRcpVariances [ 0 ] . y = 0.5f / ( stdDev1 . g * stdDev1 . g ) ;
m_HalfRcpVariances [ 0 ] . z = 0.5f / ( stdDev1 . b * stdDev1 . b ) ;
m_HalfRcpVariances [ 1 ] . x = 0.5f / ( stdDev2 . r * stdDev2 . r ) ;
m_HalfRcpVariances [ 1 ] . y = 0.5f / ( stdDev2 . g * stdDev2 . g ) ;
m_HalfRcpVariances [ 1 ] . z = 0.5f / ( stdDev2 . b * stdDev2 . b ) ;
// TODO: far field.
}
Vector4 weightedStdDev ;
weightedStdDev . x = Mathf . Lerp ( stdDev1 . r , stdDev2 . r , lerpWeight ) ;
weightedStdDev . y = Mathf . Lerp ( stdDev1 . g , stdDev2 . g , lerpWeight ) ;
weightedStdDev . z = Mathf . Lerp ( stdDev1 . b , stdDev2 . b , lerpWeight ) ;
weightedStdDev . w = Mathf . Lerp ( maxStdDev1 , maxStdDev2 , lerpWeight ) ;
public Vector3 shapeParameter
{
// Set in BuildKernel().
get { return m_S ; }
}
// Store (1 / (2 * WeightedVariance)) per color channel.
m_HalfRcpWeightedVariances . x = 0.5f / ( weightedStdDev . x * weightedStdDev . x ) ;
m_HalfRcpWeightedVariances . y = 0.5f / ( weightedStdDev . y * weightedStdDev . y ) ;
m_HalfRcpWeightedVariances . z = 0.5f / ( weightedStdDev . z * weightedStdDev . z ) ;
m_HalfRcpWeightedVariances . w = 0.5f / ( weightedStdDev . w * weightedStdDev . w ) ;
public Vector4 [ ] filterKernelNearField
{
// Set in BuildKernel().
get { return m_FilterKernelNearField ; }
}
public Vector4 [ ] filterKernelFarField
{
// Set in BuildKernel().
get { return m_FilterKernelFarField ; }
static float Gaussian ( float x , float stdDev )
static float FindFitForS ( float A )
float variance = stdDev * stdDev ;
return Mathf . Exp ( - x * x / ( 2 * variance ) ) / Mathf . Sqrt ( 2 * Mathf . PI * variance ) ;
return 1.9f - A + 3.5f * ( A - 0.8f ) * ( A - 0.8f ) ;
static float GaussianCombination ( float x , float stdDev1 , float stdDev2 , float lerpWeight )
// Same formula for s = 1 / d.
static float KernelVal ( float r , float s )
return Mathf . Lerp ( Gaussian ( x , stdDev1 ) , Gaussian ( x , stdDev2 ) , lerpWeight ) ;
return s * ( Mathf . Exp ( - r * s ) + Mathf . Exp ( - r * s * ( 1.0f / 3.0f ) ) ) / ( 8.0f * Mathf . PI * r ) ;
static float RationalApproximation ( float t )
// Returns (2 * PI * r) * KernelVal(), which is useful for integration over a disk.
// Same formula for s = 1 / d.
static float KernelValCircle ( float r , float s )
// Abramowitz and Stegun formula 26.2.23.
// The absolute value of the error should be less than 4.5 e-4.
float [ ] c = { 2.515517f , 0.802853f , 0.010328f } ;
float [ ] d = { 1.432788f , 0.189269f , 0.001308f } ;
return t - ( ( c [ 2 ] * t + c [ 1 ] ) * t + c [ 0 ] ) / ( ( ( d [ 2 ] * t + d [ 1 ] ) * t + d [ 0 ] ) * t + 1.0f ) ;
return 0.25f * s * ( Mathf . Exp ( - r * s ) + Mathf . Exp ( - r * s * ( 1.0f / 3.0f ) ) ) ;
// Ref: https://www.johndcook.com/blog/csharp_phi_inverse/
static float NormalCdfInverse ( float p , float stdDev )
// Same formula for s = 1 / d.
static float KernelPdf ( float r , float s )
float x ;
return KernelValCircle ( r , s ) ;
}
if ( p < 0.5 )
{
// F^-1(p) = - G^-1(p)
x = - RationalApproximation ( Mathf . Sqrt ( - 2.0f * Mathf . Log ( p ) ) ) ;
}
else
{
// F^-1(p) = G^-1(1-p)
x = RationalApproximation ( Mathf . Sqrt ( - 2.0f * Mathf . Log ( 1.0f - p ) ) ) ;
}
// Same formula for s = 1 / d.
static float KernelCdf ( float r , float s )
{
return 1.0f - 0.25f * Mathf . Exp ( - r * s ) - 0.75f * Mathf . Exp ( - r * s * ( 1.0f / 3.0f ) ) ;
}
return x * stdDev ;
// Same formula for s = 1 / d.
static float KernelCdfDerivative1 ( float r , float s )
{
return 0.25f * s * Mathf . Exp ( - r * s ) * ( 1.0f + Mathf . Exp ( r * s * ( 2.0f / 3.0f ) ) ) ;
static float GaussianCombinationCdfInverse ( float p , float stdDev1 , float stdDev2 , float lerpWeight )
// Same formula for s = 1 / d.
static float KernelCdfDerivative2 ( float r , float s )
return Mathf . Lerp ( NormalCdfInverse ( p , stdDev1 ) , NormalCdfInverse ( p , stdDev2 ) , lerpWeight ) ;
return ( - 1.0f / 1 2.0f ) * s * s * Mathf . Exp ( - r * s ) * ( 3.0f + Mathf . Exp ( r * s * ( 2.0f / 3.0f ) ) ) ;
}
// The CDF is not analytically invertible, so we use Halley's Method of root finding.
// { f(r, s, p) = CDF(r, s) - p = 0 } with the initial guess { r = (10^p - 1) / s }.
// Same formula for s = 1 / d.
static float KernelCdfInverse ( float p , float s )
{
// Supply the initial guess.
float r = ( Mathf . Pow ( 1 0.0f , p ) - 1.0f ) / s ;
float t = float . MaxValue ;
while ( true )
{
float f0 = KernelCdf ( r , s ) - p ;
float f1 = KernelCdfDerivative1 ( r , s ) ;
float f2 = KernelCdfDerivative2 ( r , s ) ;
float dr = f0 / ( f1 * ( 1.0f - f0 * f2 / ( 2.0f * f1 * f1 ) ) ) ;
if ( Mathf . Abs ( dr ) < t )
{
r = r - dr ;
t = Mathf . Abs ( dr ) ;
}
else
{
// Converged to the best result.
break ;
}
}
return r ;
}
}
public const int maxNumProfiles = 8 ;
public const int neutralProfileID = maxNumProfiles - 1 ;
public int numProfiles ;
public int numProfiles ; // Excluding the neutral profile
[NonSerialized] public int texturingModeFlags ; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public int transmissionFlags ; // 1 bit/profile; 0 = inf. thick, 1 = supports transmission
[NonSerialized] public Vector4 [ ] tintColors ; // For transmission; alpha is unused
[NonSerialized] public float [ ] thicknessRemaps ; // Remap: 0 = start, 1 = end - start
[NonSerialized] public Vector4 [ ] halfRcpVariancesAndLerpWeights ;
[NonSerialized] public Vector4 [ ] halfRcpWeightedVariances ;
[NonSerialized] public Vector4 [ ] filterKernels ;
[NonSerialized] public uint texturingModeFlags ; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public uint transmissionFlags ; // 2 bit/profile; 0 = None, 1 = ThinObject, 2 = ThickObject
[NonSerialized] public float [ ] thicknessRemaps ; // Remap: 0 = start, 1 = end - start
[NonSerialized] public Vector4 [ ] shapeParameters ; // RGB: S = 1 / D; alpha is unused
[NonSerialized] public Vector4 [ ] filterKernelsNearField ; // RGB = weights, A = radial distance
[NonSerialized] public Vector4 [ ] filterKernelsFarField ; // RGB = weights, A = radial distance
numProfiles = 1 ;
profiles = new SubsurfaceScatteringProfile [ numProfiles ] ;
profiles [ 0 ] = null ;
texturingModeFlags = 0 ;
transmissionFlags = 0 ;
tintColors = null ;
thicknessRemaps = null ;
halfRcpVariancesAndLerpWeights = null ;
halfRcpWeightedVariances = null ;
filterKernels = null ;
numProfiles = 1 ;
profiles = new SubsurfaceScatteringProfile [ numProfiles ] ;
profiles [ 0 ] = null ;
texturingModeFlags = 0 ;
transmissionFlags = 0 ;
thicknessRemaps = null ;
shapeParameters = null ;
filterKernelsNearField = null ;
filterKernelsFarField = null ;
UpdateCache ( ) ;
}
// Reserve one slot for the neutral profile.
numProfiles = Math . Min ( profiles . Length , maxNumProfiles - 1 ) ;
numProfiles = Math . Min ( profiles . Length , SssConstants . SSS_N_PROFILES - 1 ) ;
if ( profiles . Length ! = numProfiles )
{
}
}
Color c = new Color ( ) ;
c . r = Mathf . Clamp ( profiles [ i ] . scatterDistance1 . r , 0.05f , 2.0f ) ;
c . g = Mathf . Clamp ( profiles [ i ] . scatterDistance1 . g , 0.05f , 2.0f ) ;
c . b = Mathf . Clamp ( profiles [ i ] . scatterDistance1 . b , 0.05f , 2.0f ) ;
c . a = 0.0f ;
profiles [ i ] . scatterDistance1 = c ;
c . r = Mathf . Clamp ( profiles [ i ] . scatterDistance2 . r , 0.05f , 2.0f ) ;
c . g = Mathf . Clamp ( profiles [ i ] . scatterDistance2 . g , 0.05f , 2.0f ) ;
c . b = Mathf . Clamp ( profiles [ i ] . scatterDistance2 . b , 0.05f , 2.0f ) ;
c . a = 0.0f ;
profiles [ i ] . scatterDistance2 = c ;
profiles [ i ] . lerpWeight = Mathf . Clamp01 ( profiles [ i ] . lerpWeight ) ;
profiles [ i ] . tintColor . r = Mathf . Clamp01 ( profiles [ i ] . tintColor . r ) ;
profiles [ i ] . tintColor . g = Mathf . Clamp01 ( profiles [ i ] . tintColor . g ) ;
profiles [ i ] . tintColor . b = Mathf . Clamp01 ( profiles [ i ] . tintColor . b ) ;
profiles [ i ] . tintColor . a = 1.0f ;
profiles [ i ] . thicknessRemap . y = Mathf . Max ( profiles [ i ] . thicknessRemap . y , 0 ) ;
profiles [ i ] . thicknessRemap . y = Mathf . Max ( profiles [ i ] . thicknessRemap . x , profiles [ i ] . thicknessRemap . y ) ;
profiles [ i ] . UpdateKernelAndVarianceData ( ) ;
profiles [ i ] . BuildKernel ( ) ;
}
UpdateCache ( ) ;
{
texturingModeFlags = 0 ;
transmissionFlags = 0 ;
texturingModeFlags = transmissionFlags = 0 ;
if ( tintColors = = null | | tintColors . Length ! = maxNumProfiles )
if ( thicknessRemaps = = null | | thicknessRemaps . Length ! = ( SssConstants . SSS_N_PROFILES * 2 ) )
tintColors = new Vector4 [ maxNumProfiles ] ;
thicknessRemaps = new float [ SssConstants . SSS_N_PROFILES * 2 ] ;
if ( thicknessRemaps = = null | | thicknessRemaps . Length ! = ( maxNumProfiles * 2 ) )
if ( shapeParameters = = null | | shapeParameters . Length ! = SssConstants . SSS_N_PROFILES )
thicknessRemaps = new float [ maxNumProfiles * 2 ] ;
}
if ( halfRcpVariancesAndLerpWeights = = null | | halfRcpVariancesAndLerpWeights . Length ! = ( maxNumProfiles * 2 ) )
{
halfRcpVariancesAndLerpWeights = new Vector4 [ maxNumProfiles * 2 ] ;
shapeParameters = new Vector4 [ SssConstants . SSS_N_PROFILES ] ;
if ( halfRcpWeightedVariances = = null | | halfRcpWeightedVariances . Length ! = maxNumProfiles )
if ( filterKernelsNearField = = null | | filterKernelsNearField . Length ! = SssConstants . SSS_N_PROFILES )
halfRcpWeightedVariances = new Vector4 [ maxNumProfiles ] ;
filterKernelsNearField = new Vector4 [ SssConstants . SSS_N_PROFILES * SssConstants . SSS_N_SAMPLES_NEAR_FIELD ] ;
if ( filterKernels = = null | | filterKernels . Length ! = ( maxNumProfiles * SubsurfaceScatteringProfile . numSamples ) )
if ( filterKernelsFarField = = null | | filterKernelsFarField . Length ! = SssConstants . SSS_N_PROFILES )
filterKernels = new Vector4 [ maxNumProfiles * SubsurfaceScatteringProfile . numSamples ] ;
filterKernelsFarField = new Vector4 [ SssConstants . SSS_N_PROFILES * SssConstants . SSS_N_SAMPLES_FAR_FIELD ] ;
}
for ( int i = 0 ; i < numProfiles ; i + + )
texturingModeFlags | = ( ( int ) profiles [ i ] . texturingMode ) < < i ;
transmissionFlags | = ( profiles [ i ] . enableTransmission ? 1 : 0 ) < < i ;
Debug . Assert ( numProfiles < 1 6 , "Transmission flags (32-bit integer) cannot support more than 16 profiles." ) ;
texturingModeFlags | = ( uint ) profiles [ i ] . texturingMode < < i ;
transmissionFlags | = ( uint ) profiles [ i ] . transmissionMode < < i * 2 ;
thicknessRemaps [ 2 * i ] = profiles [ i ] . thicknessRemap . x ;
thicknessRemaps [ 2 * i + 1 ] = profiles [ i ] . thicknessRemap . y - profiles [ i ] . thicknessRemap . x ;
shapeParameters [ i ] = profiles [ i ] . shapeParameter ;
tintColors [ i ] = profiles [ i ] . tintColor ;
thicknessRemaps [ 2 * i ] = profiles [ i ] . thicknessRemap . x ;
thicknessRemaps [ 2 * i + 1 ] = profiles [ i ] . thicknessRemap . y - profiles [ i ] . thicknessRemap . x ;
halfRcpVariancesAndLerpWeights [ 2 * i ] = profiles [ i ] . halfRcpVariances [ 0 ] ;
halfRcpVariancesAndLerpWeights [ 2 * i ] . w = 1.0f - profiles [ i ] . lerpWeight ;
halfRcpVariancesAndLerpWeights [ 2 * i + 1 ] = profiles [ i ] . halfRcpVariances [ 1 ] ;
halfRcpVariancesAndLerpWeights [ 2 * i + 1 ] . w = profiles [ i ] . lerpWeight ;
halfRcpWeightedVariances [ i ] = profiles [ i ] . halfRcpWeightedVariances ;
for ( int j = 0 , n = SssConstants . SSS_N_SAMPLES_NEAR_FIELD ; j < n ; j + + )
{
filterKernelsNearField [ n * i + j ] = profiles [ i ] . filterKernelNearField [ j ] ;
}
for ( int j = 0 , n = SubsurfaceScatteringProfile . numSamples ; j < n ; j + + )
for ( int j = 0 , n = SssConstants . SSS_N_SAMPLES_FAR_FIELD ; j < n ; j + + )
filterKernels [ n * i + j ] = profiles [ i ] . filterKernel [ j ] ;
filterKernelsFarField [ n * i + j ] = profiles [ i ] . filterKernelFarField [ j ] ;
int i = neutralProfileID ;
int i = SssConstants . SSS_NEUTRAL_PROFILE_ID ;
halfRcpWeightedVariances [ i ] = Vector4 . one ;
shapeParameters [ i ] = Vector4 . zero ; // Plays no role in this case (only used for bilateral filtering)
for ( int j = 0 , n = SubsurfaceScatteringProfile . numSamples ; j < n ; j + + )
for ( int j = 0 , n = SssConstants . SSS_N_SAMPLES_NEAR_FIELD ; j < n ; j + + )
filterKernels [ n * i + j ] = Vector4 . one ;
filterKernels [ n * i + j ] . w = 0.0f ;
filterKernelsNearField [ n * i + j ] = Vector3 . one ; // Radius = 0
}
for ( int j = 0 , n = SssConstants . SSS_N_SAMPLES_FAR_FIELD ; j < n ; j + + )
{
filterKernelsFarField [ n * i + j ] = Vector3 . one ; // Radius = 0
}
}
}
private class Styles
{
public readonly GUIContent sssProfilePreview0 = new GUIContent ( "Profile Preview" ) ;
public readonly GUIContent sssProfilePreview1 = new GUIContent ( "Shows the fraction of light scattered from the source as the radius increases to 1." ) ;
public readonly GUIContent sssProfilePreview2 = new GUIContent ( "Note that the intensity of the region in the center may be clamped." ) ;
public readonly GUIContent sssProfilePreview1 = new GUIContent ( "Shows the fraction of light scattered from the source (center)." ) ;
public readonly GUIContent sssProfilePreview2 = new GUIContent ( "The distance to the boundary of the image corresponds to the Scattering Distance." ) ;
public readonly GUIContent sssProfilePreview3 = new GUIContent ( "Note that the intensity of pixels around the center may be clipped." ) ;
public readonly GUIContent sssTransmittancePreview2 = new GUIContent ( "Can be thought of as a cross section of a slab of material illuminated by a white light from the left." ) ;
public readonly GUIContent sssProfileScatterDistance1 = new GUIContent ( "Scatter Distance #1" , "The radius (in centimeters) of the 1st Gaussian filter, one per color channel. Alpha is ignored. The blur is energy-preserving, so a wide filter results in a large area with small contributions of individual samples. Smaller values increase the sharpness." ) ;
public readonly GUIContent sssProfileScatterDistance2 = new GUIContent ( "Scatter Distance #2" , "The radius (in centimeters) of the 2nd Gaussian filter, one per color channel. Alpha is ignored. The blur is energy-preserving, so a wide filter results in a large area with small contributions of individual samples. Smaller values increase the sharpness." ) ;
public readonly GUIContent sssProfileLerpWeight = new GUIContent ( "Filter Interpolation" , "Controls linear interpolation between the two Gaussian filters." ) ;
public readonly GUIContent sssTransmittancePreview2 = new GUIContent ( "Can be viewed as a cross section of a slab of material illuminated by white light from the left." ) ;
public readonly GUIContent sssProfileSurfaceAlbedo = new GUIContent ( "Surface Albedo" , "A color which determines the shape of the profile." ) ;
public readonly GUIContent sssProfileScatterDistance = new GUIContent ( "Scattering Distance" , "Determines the effective radius of the filter. The blur is energy-preserving, so a wide filter results in a large area with small contributions of individual samples. Smaller values increase the sharpness of the result." ) ;
new GUIContent ( "Post-scatter" , "Texturing is performed only during the SSS pass. Effectively preserves the sharpness of the diffuse texture. Choose this mode if your diffuse texture already contains SSS lighting (e.g. a photo of skin)." )
new GUIContent ( "Post-scatter" , "Texturing is performed only during the SSS pass. Effectively preserves the sharpness of the diffuse texture. Choose this mode if your diffuse texture already contains SSS lighting (e.g. a photo of skin)." )
public readonly GUIContent sssProfileTransmission = new GUIContent ( "Enable Transmission" , "Toggles simulation of light passing through thin objects. Depends on the thickness of the material." ) ;
public readonly GUIContent sssProfileTintColor = new GUIContent ( "Transmission Tint Color" , "Tints transmitted light." ) ;
public readonly GUIContent sssProfileMinMaxThickness = new GUIContent ( "Min-Max Thickness" , "Shows the values of the thickness remap below (in centimeters)." ) ;
public readonly GUIContent sssProfileThicknessRemap = new GUIContent ( "Thickness Remap" , "Remaps the thickness parameter from [0, 1] to the desired range (in centimeters)." ) ;
public readonly GUIContent sssProfileTransmissionMode = new GUIContent ( "Transmission Mode" , "Configures the simulation of light passing through thin objects. Depends on the thickness value (which is applied in the normal direction)." ) ;
public readonly GUIContent [ ] sssTransmissionModeOptions = new GUIContent [ 3 ]
{
new GUIContent ( "None" , "Disables transmission. Choose this mode for completely opaque, or very thick translucent objects." ) ,
new GUIContent ( "Thin Object" , "Choose this mode for thin objects, such as paper or leaves. Transmitted light reuses the shadowing state of the surface." ) ,
new GUIContent ( "Regular" , "Choose this mode for moderately thick objects. For performance reasons, transmitted light ignores occlusion (shadows)." )
} ;
public readonly GUIContent sssProfileMinMaxThickness = new GUIContent ( "Min-Max Thickness" , "Shows the values of the thickness remap below (in millimeters)." ) ;
public readonly GUIContent sssProfileThicknessRemap = new GUIContent ( "Thickness Remap" , "Remaps the thickness parameter from [0, 1] to the desired range (in millimeters)." ) ;
public readonly GUIStyle centeredMiniBoldLabel = new GUIStyle ( GUI . skin . label ) ;
private RenderTexture m_ProfileImage , m_TransmittanceImage ;
private Material m_ProfileMaterial , m_TransmittanceMaterial ;
private SerializedProperty m_ScatterDistance1 , m_ScatterDistance2 , m_LerpWeight , m_TintColor ,
m_TexturingMode , m_Transmission , m_ThicknessRemap ;
private SerializedProperty m_ScatteringDistance , m_SurfaceAlbedo , m_S ,
m_TexturingMode , m_TransmissionMode , m_ThicknessRemap ;
m_ScatterDistance1 = serializedObject . FindProperty ( "scatterDistance1" ) ;
m_ScatterDistance2 = serializedObject . FindProperty ( "scatterDistance2" ) ;
m_LerpWeight = serializedObject . FindProperty ( "lerpWeight" ) ;
m_TexturingMode = serializedObject . FindProperty ( "texturingMode" ) ;
m_Transmission = serializedObject . FindProperty ( "enableTransmission" ) ;
m_TintColor = serializedObject . FindProperty ( "tintColor" ) ;
m_ThicknessRemap = serializedObject . FindProperty ( "thicknessRemap" ) ;
m_SurfaceAlbedo = serializedObject . FindProperty ( "surfaceAlbedo" ) ;
m_ScatteringDistance = serializedObject . FindProperty ( "scatteringDistance" ) ;
m_S = serializedObject . FindProperty ( "m_S" ) ;
m_TexturingMode = serializedObject . FindProperty ( "texturingMode" ) ;
m_TransmissionMode = serializedObject . FindProperty ( "transmissionMode" ) ;
m_ThicknessRemap = serializedObject . FindProperty ( "thicknessRemap" ) ;
m_ProfileMaterial = Utilities . CreateEngineMaterial ( "Hidden/HDRenderPipeline/DrawGaussianProfile" ) ;
m_ProfileMaterial = Utilities . CreateEngineMaterial ( "Hidden/HDRenderPipeline/DrawSssProfile" ) ;
m_TransmittanceImage = new RenderTexture ( 1 6 , 2 5 6 , 0 , RenderTextureFormat . DefaultHDR ) ;
m_TransmittanceImage = new RenderTexture ( 1 6 , 2 5 6 , 0 , RenderTextureFormat . DefaultHDR ) ;
}
public override void OnInspectorGUI ( )
EditorGUI . BeginChangeCheck ( ) ;
{
EditorGUILayout . PropertyField ( m_ScatterDistance1 , styles . sssProfileScatterDistance1 ) ;
EditorGUILayout . PropertyField ( m_ScatterDistance2 , styles . sssProfileScatterDistance2 ) ;
EditorGUILayout . PropertyField ( m_LerpWeight , styles . sssProfileLerpWeight ) ;
m_TexturingMode . intValue = EditorGUILayout . Popup ( styles . sssTexturingMode , m_TexturingMode . intValue , styles . sssTexturingModeOptions ) ;
EditorGUILayout . PropertyField ( m_Transmission , styles . sssProfileTransmission ) ;
EditorGUILayout . PropertyField ( m_TintColor , styles . sssProfileTintColor ) ;
EditorGUILayout . PropertyField ( m_SurfaceAlbedo , styles . sssProfileSurfaceAlbedo ) ;
m_ScatteringDistance . floatValue = EditorGUILayout . Slider ( styles . sssProfileScatterDistance , m_ScatteringDistance . floatValue , 0.1f , 1 0.0f ) ;
m_TexturingMode . intValue = EditorGUILayout . Popup ( styles . sssTexturingMode , m_TexturingMode . intValue , styles . sssTexturingModeOptions ) ;
m_TransmissionMode . intValue = EditorGUILayout . Popup ( styles . sssProfileTransmissionMode , m_TransmissionMode . intValue , styles . sssTransmissionModeOptions ) ;
EditorGUILayout . MinMaxSlider ( styles . sssProfileThicknessRemap , ref thicknessRemap . x , ref thicknessRemap . y , 0 , 1 0 ) ;
EditorGUILayout . MinMaxSlider ( styles . sssProfileThicknessRemap , ref thicknessRemap . x , ref thicknessRemap . y , 0.0f , 5 0.0f ) ;
m_ThicknessRemap . vector2Value = thicknessRemap ;
EditorGUILayout . Space ( ) ;
EditorGUILayout . LabelField ( styles . sssProfilePreview3 , EditorStyles . centeredGreyMiniLabel ) ;
// Apply the three-sigma rule, and rescale.
float s = ( 1.0f / 3.0f ) * SubsurfaceScatteringProfile . distanceScale ;
Vector4 stdDev1 = new Vector4 ( s * m_ScatterDistance1 . colorValue . r , s * m_ScatterDistance1 . colorValue . g , s * m_ScatterDistance1 . colorValue . b ) ;
Vector4 stdDev2 = new Vector4 ( s * m_ScatterDistance2 . colorValue . r , s * m_ScatterDistance2 . colorValue . g , s * m_ScatterDistance2 . colorValue . b ) ;
Vector4 tintCol = new Vector4 ( m_TintColor . colorValue . r , m_TintColor . colorValue . g , m_TintColor . colorValue . b ) ;
float d = m_ScatteringDistance . floatValue ;
Vector4 A = m_SurfaceAlbedo . colorValue ;
Vector4 S = m_S . vector3Value ;
Vector2 R = m_ThicknessRemap . vector2Value ;
m_ProfileMaterial . SetVector ( "_StdDev1" , stdDev1 ) ;
m_ProfileMaterial . SetVector ( "_StdDev2" , stdDev2 ) ;
m_ProfileMaterial . SetFloat ( "_LerpWeight" , m_LerpWeight . floatValue ) ;
m_ProfileMaterial . SetFloat ( "_ScatteringDistance" , d ) ;
m_ProfileMaterial . SetVector ( "_SurfaceAlbedo" , A ) ;
m_ProfileMaterial . SetVector ( "_ShapeParameter" , S ) ;
EditorGUILayout . Space ( ) ;
EditorGUILayout . LabelField ( styles . sssTransmittancePreview0 , styles . centeredMiniBoldLabel ) ;
EditorGUILayout . LabelField ( styles . sssTransmittancePreview1 , EditorStyles . centeredGreyMiniLabel ) ;
bool transmissionEnabled = m_TransmissionMode . intValue ! = ( int ) SubsurfaceScatteringProfile . TransmissionMode . None ;
m_TransmittanceMaterial . SetVector ( "_StdDev1" , stdDev1 ) ;
m_TransmittanceMaterial . SetVector ( "_StdDev2" , stdDev2 ) ;
m_TransmittanceMaterial . SetFloat ( "_LerpWeight" , m_LerpWeight . floatValue ) ;
m_TransmittanceMaterial . SetVector ( "_ThicknessRemap" , m_ThicknessRemap . vector2Value ) ;
m_TransmittanceMaterial . SetVector ( "_TintColor" , tintCol ) ;
m_TransmittanceMaterial . SetFloat ( "_ScatteringDistance" , d ) ;
m_TransmittanceMaterial . SetVector ( "_SurfaceAlbedo" , transmissionEnabled ? A : Vector4 . zero ) ;
m_TransmittanceMaterial . SetVector ( "_ShapeParameter" , S ) ;
m_TransmittanceMaterial . SetVector ( "_ThicknessRemap" , R ) ;
EditorGUI . DrawPreviewTexture ( GUILayoutUtility . GetRect ( 1 6 , 1 6 ) , m_TransmittanceImage , m_TransmittanceMaterial , ScaleMode . ScaleToFit , 1 6.0f ) ;
serializedObject . ApplyModifiedProperties ( ) ;
}
}
#endif
}
}