您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

838 行
42 KiB

using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering.HDPipeline
{
[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 = 55; // Used for extreme close ups; must be a Fibonacci number
public const int SSS_N_SAMPLES_FAR_FIELD = 21; // Used at a regular distance; must be a Fibonacci number
public const int SSS_LOD_THRESHOLD = 4; // The LoD threshold of the near-field kernel (in pixels)
public const int SSS_TRSM_MODE_NONE = 0;
public const int SSS_TRSM_MODE_THIN = 1;
// Old SSS Model >>>
public const int SSS_BASIC_N_SAMPLES = 11; // Must be an odd number
public const int SSS_BASIC_DISTANCE_SCALE = 3; // SSS distance units per centimeter
// <<< Old SSS Model
}
[Serializable]
public class SubsurfaceScatteringProfile : ScriptableObject
{
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 };
[ColorUsage(false, true, 0f, 8f, 0.125f, 3f)]
public Color scatteringDistance; // Per color channel (no meaningful units)
[ColorUsage(false)]
public Color transmissionTint; // Color, 0 to 1
public TexturingMode texturingMode;
public TransmissionMode transmissionMode;
public Vector2 thicknessRemap; // X = min, Y = max (in millimeters)
public float worldScale; // Size of the world unit in meters
[HideInInspector]
public int settingsIndex; // SubsurfaceScatteringSettings.profiles[i]
[SerializeField]
Vector3 m_ShapeParam; // RGB = shape parameter: S = 1 / D
[SerializeField]
float m_MaxRadius; // In millimeters
[SerializeField]
Vector2[] m_FilterKernelNearField; // X = radius, Y = reciprocal of the PDF
[SerializeField]
Vector2[] m_FilterKernelFarField; // X = radius, Y = reciprocal of the PDF
// Old SSS Model >>>
[ColorUsage(false, true, 0f, 8f, 0.125f, 3f)]
public Color scatterDistance1;
[ColorUsage(false, true, 0f, 8f, 0.125f, 3f)]
public Color scatterDistance2;
[Range(0f, 1f)]
public float lerpWeight;
[SerializeField]
Vector4 m_HalfRcpWeightedVariances;
[SerializeField]
Vector4[] m_FilterKernelBasic;
// <<< Old SSS Model
// --- Public Methods ---
public SubsurfaceScatteringProfile()
{
scatteringDistance = Color.grey;
transmissionTint = Color.white;
texturingMode = TexturingMode.PreAndPostScatter;
transmissionMode = TransmissionMode.None;
thicknessRemap = new Vector2(0.0f, 5.0f);
worldScale = 1.0f;
settingsIndex = SssConstants.SSS_NEUTRAL_PROFILE_ID; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
// Old SSS Model >>>
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;
// <<< Old SSS Model
BuildKernel();
}
// Ref: Approximate Reflectance Profiles for Efficient Subsurface Scattering by Pixar.
public void BuildKernel()
{
if (m_FilterKernelNearField == null || m_FilterKernelNearField.Length != SssConstants.SSS_N_SAMPLES_NEAR_FIELD)
{
m_FilterKernelNearField = new Vector2[SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
}
if (m_FilterKernelFarField == null || m_FilterKernelFarField.Length != SssConstants.SSS_N_SAMPLES_FAR_FIELD)
{
m_FilterKernelFarField = new Vector2[SssConstants.SSS_N_SAMPLES_FAR_FIELD];
}
// Clamp to avoid artifacts.
m_ShapeParam = new Vector3();
m_ShapeParam.x = 1.0f / Mathf.Max(0.001f, scatteringDistance.r);
m_ShapeParam.y = 1.0f / Mathf.Max(0.001f, scatteringDistance.g);
m_ShapeParam.z = 1.0f / Mathf.Max(0.001f, scatteringDistance.b);
// We importance sample the color channel with the widest scattering distance.
float s = Mathf.Min(m_ShapeParam.x, m_ShapeParam.y, m_ShapeParam.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]
// ------------------------------------------------------------------------------------
// Importance sample the near field kernel.
for (int i = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; i < n; i++)
{
float p = (i + 0.5f) * (1.0f / n);
float r = KernelCdfInverse(p, s);
// N.b.: computation of normalized weights, and multiplication by the surface albedo
// of the actual geometry is performed at runtime (in the shader).
m_FilterKernelNearField[i].x = r;
m_FilterKernelNearField[i].y = 1.0f / KernelPdf(r, s);
}
// Importance sample the far field kernel.
for (int i = 0, n = SssConstants.SSS_N_SAMPLES_FAR_FIELD; i < n; i++)
{
float p = (i + 0.5f) * (1.0f / n);
float r = KernelCdfInverse(p, s);
// N.b.: computation of normalized weights, and multiplication by the surface albedo
// of the actual geometry is performed at runtime (in the shader).
m_FilterKernelFarField[i].x = r;
m_FilterKernelFarField[i].y = 1.0f / KernelPdf(r, s);
}
m_MaxRadius = m_FilterKernelFarField[SssConstants.SSS_N_SAMPLES_FAR_FIELD - 1].x;
// Old SSS Model >>>
UpdateKernelAndVarianceData();
// <<< Old SSS Model
}
// Old SSS Model >>>
public void UpdateKernelAndVarianceData()
{
const int numSamples = SssConstants.SSS_BASIC_N_SAMPLES;
const int distanceScale = SssConstants.SSS_BASIC_DISTANCE_SCALE;
if (m_FilterKernelBasic == null || m_FilterKernelBasic.Length != numSamples)
{
m_FilterKernelBasic = new Vector4[numSamples];
}
// Apply the three-sigma rule, and rescale.
Color stdDev1 = ((1.0f / 3.0f) * distanceScale) * scatterDistance1;
Color stdDev2 = ((1.0f / 3.0f) * distanceScale) * scatterDistance2;
// 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.
// 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).
// 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);
Vector3 weightSum = new Vector3(0, 0, 0);
float step = 1.0f / (numSamples - 1);
// Importance sample the linear combination of two Gaussians.
for (int i = 0; i < numSamples; 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
u = Mathf.Clamp(u, 0.001f, 0.999f);
float pos = GaussianCombinationCdfInverse(u, maxStdDev1, maxStdDev2, lerpWeight);
float pdf = GaussianCombination(pos, maxStdDev1, maxStdDev2, lerpWeight);
Vector3 val;
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);
// We do not divide by 'numSamples' since we will renormalize, anyway.
m_FilterKernelBasic[i].x = val.x * (1 / pdf);
m_FilterKernelBasic[i].y = val.y * (1 / pdf);
m_FilterKernelBasic[i].z = val.z * (1 / pdf);
m_FilterKernelBasic[i].w = pos;
weightSum.x += m_FilterKernelBasic[i].x;
weightSum.y += m_FilterKernelBasic[i].y;
weightSum.z += m_FilterKernelBasic[i].z;
}
// Renormalize the weights to conserve energy.
for (int i = 0; i < numSamples; i++)
{
m_FilterKernelBasic[i].x *= 1 / weightSum.x;
m_FilterKernelBasic[i].y *= 1 / weightSum.y;
m_FilterKernelBasic[i].z *= 1 / weightSum.z;
}
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);
// 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);
}
// <<< Old SSS Model
public Vector3 shapeParameter
{
// Set in BuildKernel().
get { return m_ShapeParam; }
}
public float maxRadius
{
// Set in BuildKernel().
get { return m_MaxRadius; }
}
public Vector2[] filterKernelNearField
{
// Set in BuildKernel().
get { return m_FilterKernelNearField; }
}
public Vector2[] filterKernelFarField
{
// Set in BuildKernel().
get { return m_FilterKernelFarField; }
}
// Old SSS Model >>>
public Vector4[] filterKernelBasic
{
// Set via UpdateKernelAndVarianceData().
get { return m_FilterKernelBasic; }
}
public Vector4 halfRcpWeightedVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpWeightedVariances; }
}
// <<< Old SSS Model
// --- Private Methods ---
static float KernelVal(float r, float s)
{
return s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f))) / (8.0f * Mathf.PI * r);
}
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
static float KernelValCircle(float r, float s)
{
return 0.25f * s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f)));
}
static float KernelPdf(float r, float s)
{
return KernelValCircle(r, s);
}
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));
}
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 KernelCdfDerivative2(float r, float s)
{
return (-1.0f / 12.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 }.
static float KernelCdfInverse(float p, float s)
{
// Supply the initial guess.
float r = (Mathf.Pow(10.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;
}
// Old SSS Model >>>
static float Gaussian(float x, float stdDev)
{
float variance = stdDev * stdDev;
return Mathf.Exp(-x * x / (2 * variance)) / Mathf.Sqrt(2 * Mathf.PI * variance);
}
static float GaussianCombination(float x, float stdDev1, float stdDev2, float lerpWeight)
{
return Mathf.Lerp(Gaussian(x, stdDev1), Gaussian(x, stdDev2), lerpWeight);
}
static float RationalApproximation(float t)
{
// 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);
}
// Ref: https://www.johndcook.com/blog/csharp_phi_inverse/
static float NormalCdfInverse(float p, float stdDev)
{
float x;
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)));
}
return x * stdDev;
}
static float GaussianCombinationCdfInverse(float p, float stdDev1, float stdDev2, float lerpWeight)
{
return Mathf.Lerp(NormalCdfInverse(p, stdDev1), NormalCdfInverse(p, stdDev2), lerpWeight);
}
// <<< Old SSS Model
}
[Serializable]
public class SubsurfaceScatteringSettings : ISerializationCallbackReceiver
{
public int numProfiles; // Excluding the neutral profile
public SubsurfaceScatteringProfile[] profiles;
// Below are the cached values. TODO: uncomment when SSS profile asset serialization is fixed.
/*[NonSerialized]*/ public int texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
/*[NonSerialized]*/ public int transmissionFlags; // 2 bit/profile; 0 = inf. thick, 1 = thin, 2 = regular
/*[NonSerialized]*/ public Vector4[] thicknessRemaps; // Remap: 0 = start, 1 = end - start
/*[NonSerialized]*/ public Vector4[] worldScales; // Size of the world unit in meters (only the X component is used)
/*[NonSerialized]*/ public Vector4[] shapeParams; // RGB = S = 1 / D, A = filter radius
/*[NonSerialized]*/ public Vector4[] transmissionTints; // RGB = color, A = unused
/*[NonSerialized]*/ public Vector4[] filterKernels; // XY = near field, ZW = far field; 0 = radius, 1 = reciprocal of the PDF
// Old SSS Model >>>
public bool useDisneySSS;
/*[NonSerialized]*/ public Vector4[] halfRcpWeightedVariances;
/*[NonSerialized]*/ public Vector4[] halfRcpVariancesAndWeights;
/*[NonSerialized]*/ public Vector4[] filterKernelsBasic;
// <<< Old SSS Model
// --- Public Methods ---
public SubsurfaceScatteringSettings()
{
numProfiles = 1;
profiles = new SubsurfaceScatteringProfile[numProfiles];
profiles[0] = null;
texturingModeFlags = 0;
transmissionFlags = 0;
thicknessRemaps = null;
worldScales = null;
shapeParams = null;
transmissionTints = null;
filterKernels = null;
// Old SSS Model >>>
useDisneySSS = true;
halfRcpWeightedVariances = null;
halfRcpVariancesAndWeights = null;
filterKernelsBasic = null;
// <<< Old SSS Model
UpdateCache();
}
public void OnValidate()
{
// Reserve one slot for the neutral profile.
numProfiles = Math.Min(profiles.Length, SssConstants.SSS_N_PROFILES - 1);
if (profiles.Length != numProfiles)
{
Array.Resize(ref profiles, numProfiles);
}
for (int i = 0; i < numProfiles; i++)
{
if (profiles[i] != null)
{
// Assign the profile IDs.
profiles[i].settingsIndex = i;
}
}
for (int i = 0; i < numProfiles; i++)
{
// Skip unassigned profiles.
if (profiles[i] == null) continue;
profiles[i].thicknessRemap.y = Mathf.Max(profiles[i].thicknessRemap.y, 0);
profiles[i].thicknessRemap.x = Mathf.Clamp(profiles[i].thicknessRemap.x, 0, profiles[i].thicknessRemap.y);
profiles[i].worldScale = Mathf.Max(profiles[i].worldScale, 0.001f);
// Old SSS Model >>>
Color c = new Color();
c.r = Mathf.Max(0.05f, profiles[i].scatterDistance1.r);
c.g = Mathf.Max(0.05f, profiles[i].scatterDistance1.g);
c.b = Mathf.Max(0.05f, profiles[i].scatterDistance1.b);
c.a = 0.0f;
profiles[i].scatterDistance1 = c;
c.r = Mathf.Max(0.05f, profiles[i].scatterDistance2.r);
c.g = Mathf.Max(0.05f, profiles[i].scatterDistance2.g);
c.b = Mathf.Max(0.05f, profiles[i].scatterDistance2.b);
c.a = 0.0f;
profiles[i].scatterDistance2 = c;
// <<< Old SSS Model
profiles[i].BuildKernel();
}
UpdateCache();
}
public void UpdateCache()
{
texturingModeFlags = transmissionFlags = 0;
if (thicknessRemaps == null || thicknessRemaps.Length != SssConstants.SSS_N_PROFILES)
{
thicknessRemaps = new Vector4[SssConstants.SSS_N_PROFILES];
}
if (worldScales == null || worldScales.Length != SssConstants.SSS_N_PROFILES)
{
worldScales = new Vector4[SssConstants.SSS_N_PROFILES];
}
if (shapeParams == null || shapeParams.Length != SssConstants.SSS_N_PROFILES)
{
shapeParams = new Vector4[SssConstants.SSS_N_PROFILES];
}
if (transmissionTints == null || transmissionTints.Length != SssConstants.SSS_N_PROFILES)
{
transmissionTints = new Vector4[SssConstants.SSS_N_PROFILES];
}
const int filterKernelsNearFieldLen = SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD;
if (filterKernels == null || filterKernels.Length != filterKernelsNearFieldLen)
{
filterKernels = new Vector4[filterKernelsNearFieldLen];
}
// Old SSS Model >>>
if (halfRcpWeightedVariances == null || halfRcpWeightedVariances.Length != SssConstants.SSS_N_PROFILES)
{
halfRcpWeightedVariances = new Vector4[SssConstants.SSS_N_PROFILES];
}
if (halfRcpVariancesAndWeights == null || halfRcpVariancesAndWeights.Length != 2 * SssConstants.SSS_N_PROFILES)
{
halfRcpVariancesAndWeights = new Vector4[2 * SssConstants.SSS_N_PROFILES];
}
const int filterKernelsLen = SssConstants.SSS_N_PROFILES * SssConstants.SSS_BASIC_N_SAMPLES;
if (filterKernelsBasic == null || filterKernelsBasic.Length != filterKernelsLen)
{
filterKernelsBasic = new Vector4[filterKernelsLen];
}
// <<< Old SSS Model
for (int i = 0; i < SssConstants.SSS_N_PROFILES - 1; i++)
{
// If a profile is null, it means that it was never set in the HDRenderPipeline Asset or that the profile asset has been deleted.
// In this case we want the users to be warned if a material uses one of those. This is why we fill the profile with pink transmission values.
if (i >= numProfiles || profiles[i] == null)
{
// Pink transmission
transmissionFlags |= 1 << i * 2;
transmissionTints[i] = new Vector4(100.0f, 0.0f, 100.0f, 1.0f);
// Default neutral values for the rest
worldScales[i] = Vector4.one;
shapeParams[i] = Vector4.zero;
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernels[n * i + j].x = 0.0f;
filterKernels[n * i + j].y = 1.0f;
filterKernels[n * i + j].z = 0.0f;
filterKernels[n * i + j].w = 1.0f;
}
// Old SSS Model >>>
halfRcpWeightedVariances[i] = Vector4.one;
halfRcpVariancesAndWeights[2 * i + 0] = Vector4.one;
halfRcpVariancesAndWeights[2 * i + 1] = Vector4.one;
for (int j = 0, n = SssConstants.SSS_BASIC_N_SAMPLES; j < n; j++)
{
filterKernelsBasic[n * i + j] = Vector4.one;
filterKernelsBasic[n * i + j].w = 0.0f;
}
continue;
}
Debug.Assert(numProfiles < 16, "Transmission flags (32-bit integer) cannot support more than 16 profiles.");
texturingModeFlags |= (int)profiles[i].texturingMode << i;
transmissionFlags |= (int)profiles[i].transmissionMode << i * 2;
thicknessRemaps[i] = new Vector4(profiles[i].thicknessRemap.x, profiles[i].thicknessRemap.y - profiles[i].thicknessRemap.x, 0.0f, 0.0f);
worldScales[i] = new Vector4(profiles[i].worldScale, 0, 0, 0);
shapeParams[i] = profiles[i].shapeParameter;
shapeParams[i].w = profiles[i].maxRadius;
transmissionTints[i] = profiles[i].transmissionTint * 0.25f; // Premultiplied
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernels[n * i + j].x = profiles[i].filterKernelNearField[j].x;
filterKernels[n * i + j].y = profiles[i].filterKernelNearField[j].y;
if (j < SssConstants.SSS_N_SAMPLES_FAR_FIELD)
{
filterKernels[n * i + j].z = profiles[i].filterKernelFarField[j].x;
filterKernels[n * i + j].w = profiles[i].filterKernelFarField[j].y;
}
}
// Old SSS Model >>>
halfRcpWeightedVariances[i] = profiles[i].halfRcpWeightedVariances;
Vector4 stdDev1 = ((1.0f / 3.0f) * SssConstants.SSS_BASIC_DISTANCE_SCALE) * profiles[i].scatterDistance1;
Vector4 stdDev2 = ((1.0f / 3.0f) * SssConstants.SSS_BASIC_DISTANCE_SCALE) * profiles[i].scatterDistance2;
// Multiply by 0.1 to convert from millimeters to centimeters. Apply the distance scale.
// Rescale by 4 to counter rescaling of transmission tints.
float a = 0.1f * SssConstants.SSS_BASIC_DISTANCE_SCALE;
halfRcpVariancesAndWeights[2 * i + 0] = new Vector4(a * a * 0.5f / (stdDev1.x * stdDev1.x), a * a * 0.5f / (stdDev1.y * stdDev1.y), a * a * 0.5f / (stdDev1.z * stdDev1.z), 4 * (1.0f - profiles[i].lerpWeight));
halfRcpVariancesAndWeights[2 * i + 1] = new Vector4(a * a * 0.5f / (stdDev2.x * stdDev2.x), a * a * 0.5f / (stdDev2.y * stdDev2.y), a * a * 0.5f / (stdDev2.z * stdDev2.z), 4 * profiles[i].lerpWeight);
for (int j = 0, n = SssConstants.SSS_BASIC_N_SAMPLES; j < n; j++)
{
filterKernelsBasic[n * i + j] = profiles[i].filterKernelBasic[j];
}
// <<< Old SSS Model
}
// Fill the neutral profile.
{
int i = SssConstants.SSS_NEUTRAL_PROFILE_ID;
worldScales[i] = Vector4.one;
shapeParams[i] = Vector4.zero;
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernels[n * i + j].x = 0.0f;
filterKernels[n * i + j].y = 1.0f;
filterKernels[n * i + j].z = 0.0f;
filterKernels[n * i + j].w = 1.0f;
}
// Old SSS Model >>>
halfRcpWeightedVariances[i] = Vector4.one;
for (int j = 0, n = SssConstants.SSS_BASIC_N_SAMPLES; j < n; j++)
{
filterKernelsBasic[n * i + j] = Vector4.one;
filterKernelsBasic[n * i + j].w = 0.0f;
}
// <<< Old SSS Model
}
}
public void OnBeforeSerialize()
{
// No special action required.
}
public void OnAfterDeserialize()
{
// TODO: uncomment when SSS profile asset serialization is fixed.
// UpdateCache();
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(SubsurfaceScatteringProfile))]
public class SubsurfaceScatteringProfileEditor : Editor
{
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 (center).");
public readonly GUIContent sssProfilePreview2 = new GUIContent("The distance to the boundary of the image corresponds to the Max Radius.");
public readonly GUIContent sssProfilePreview3 = new GUIContent("Note that the intensity of pixels around the center may be clipped.");
public readonly GUIContent sssTransmittancePreview0 = new GUIContent("Transmittance Preview");
public readonly GUIContent sssTransmittancePreview1 = new GUIContent("Shows the fraction of light passing through the object for thickness values from the remap.");
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 sssProfileScatteringDistance = new GUIContent("Scattering Distance", "Determines the shape of the profile, and the blur radius of the filter per color channel. Alpha is ignored.");
public readonly GUIContent sssProfileTransmissionTint = new GUIContent("Transmission tint", "Color which tints transmitted light. Alpha is ignored.");
public readonly GUIContent sssProfileMaxRadius = new GUIContent("Max Radius", "Effective radius of the filter (in millimeters). The blur is energy-preserving, so a wide filter results in a large area with small contributions of individual samples. Reducing the distance increases the sharpness of the result.");
public readonly GUIContent sssTexturingMode = new GUIContent("Texturing Mode", "Specifies when the diffuse texture should be applied.");
public readonly GUIContent[] sssTexturingModeOptions = new GUIContent[2]
{
new GUIContent("Pre- and post-scatter", "Texturing is performed during both the lighting and the SSS passes. Slightly blurs the diffuse texture. Choose this mode if your diffuse texture contains little to no SSS lighting."),
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 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 GUIContent sssProfileWorldScale = new GUIContent("World Scale", "Size of the world unit in meters.");
// Old SSS Model >>>
public readonly GUIContent sssProfileScatterDistance1 = new GUIContent("Scattering 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("Scattering 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.");
// <<< Old SSS Model
public readonly GUIStyle centeredMiniBoldLabel = new GUIStyle(GUI.skin.label);
public Styles()
{
centeredMiniBoldLabel.alignment = TextAnchor.MiddleCenter;
centeredMiniBoldLabel.fontSize = 10;
centeredMiniBoldLabel.fontStyle = FontStyle.Bold;
}
}
private static Styles styles
{
get
{
if (s_Styles == null)
{
s_Styles = new Styles();
}
return s_Styles;
}
}
private static Styles s_Styles = null;
private RenderTexture m_ProfileImage, m_TransmittanceImage;
private Material m_ProfileMaterial, m_TransmittanceMaterial;
private SerializedProperty m_ScatteringDistance, m_MaxRadius, m_ShapeParam, m_TransmissionTint,
m_TexturingMode, m_TransmissionMode, m_ThicknessRemap, m_WorldScale;
// Old SSS Model >>>
private SerializedProperty m_ScatterDistance1, m_ScatterDistance2, m_LerpWeight;
// <<< Old SSS Model
void OnEnable()
{
m_ScatteringDistance = serializedObject.FindProperty("scatteringDistance");
m_MaxRadius = serializedObject.FindProperty("m_MaxRadius");
m_ShapeParam = serializedObject.FindProperty("m_ShapeParam");
m_TransmissionTint = serializedObject.FindProperty("transmissionTint");
m_TexturingMode = serializedObject.FindProperty("texturingMode");
m_TransmissionMode = serializedObject.FindProperty("transmissionMode");
m_ThicknessRemap = serializedObject.FindProperty("thicknessRemap");
m_WorldScale = serializedObject.FindProperty("worldScale");
// Old SSS Model >>>
m_ScatterDistance1 = serializedObject.FindProperty("scatterDistance1");
m_ScatterDistance2 = serializedObject.FindProperty("scatterDistance2");
m_LerpWeight = serializedObject.FindProperty("lerpWeight");
// <<< Old SSS Model
// These shaders don't need to be reference by RenderPipelineResource as they are not use at runtime
m_ProfileMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/DrawSssProfile");
m_TransmittanceMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/DrawTransmittanceGraph");
m_ProfileImage = new RenderTexture(256, 256, 0, RenderTextureFormat.DefaultHDR);
m_TransmittanceImage = new RenderTexture( 16, 256, 0, RenderTextureFormat.DefaultHDR);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Old SSS Model >>>
bool useDisneySSS;
{
HDRenderPipeline hdPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline;
useDisneySSS = hdPipeline.sssSettings.useDisneySSS;
}
// <<< Old SSS Model
EditorGUI.BeginChangeCheck();
{
if (useDisneySSS)
{
EditorGUILayout.PropertyField(m_ScatteringDistance, styles.sssProfileScatteringDistance);
GUI.enabled = false;
EditorGUILayout.PropertyField(m_MaxRadius, styles.sssProfileMaxRadius);
GUI.enabled = true;
}
else
{
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);
m_TransmissionMode.intValue = EditorGUILayout.Popup(styles.sssProfileTransmissionMode, m_TransmissionMode.intValue, styles.sssTransmissionModeOptions);
EditorGUILayout.PropertyField(m_TransmissionTint, styles.sssProfileTransmissionTint);
EditorGUILayout.PropertyField(m_ThicknessRemap, styles.sssProfileMinMaxThickness);
Vector2 thicknessRemap = m_ThicknessRemap.vector2Value;
EditorGUILayout.MinMaxSlider(styles.sssProfileThicknessRemap, ref thicknessRemap.x, ref thicknessRemap.y, 0.0f, 50.0f);
m_ThicknessRemap.vector2Value = thicknessRemap;
EditorGUILayout.PropertyField(m_WorldScale, styles.sssProfileWorldScale);
EditorGUILayout.Space();
EditorGUILayout.LabelField(styles.sssProfilePreview0, styles.centeredMiniBoldLabel);
EditorGUILayout.LabelField(styles.sssProfilePreview1, EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.LabelField(styles.sssProfilePreview2, EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.LabelField(styles.sssProfilePreview3, EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.Space();
}
float r = m_MaxRadius.floatValue;
Vector3 S = m_ShapeParam.vector3Value;
Vector4 T = m_TransmissionTint.colorValue;
Vector2 R = m_ThicknessRemap.vector2Value;
bool transmissionEnabled = m_TransmissionMode.intValue != (int)SubsurfaceScatteringProfile.TransmissionMode.None;
m_ProfileMaterial.SetFloat(HDShaderIDs._MaxRadius, r);
m_ProfileMaterial.SetVector(HDShaderIDs._ShapeParam, S);
// Old SSS Model >>>
Utilities.SelectKeyword(m_ProfileMaterial, "SSS_MODEL_DISNEY", "SSS_MODEL_BASIC", useDisneySSS);
// Apply the three-sigma rule, and rescale.
float s = (1.0f / 3.0f) * SssConstants.SSS_BASIC_DISTANCE_SCALE;
float rMax = Mathf.Max(m_ScatterDistance1.colorValue.r, m_ScatterDistance1.colorValue.g, m_ScatterDistance1.colorValue.b,
m_ScatterDistance2.colorValue.r, m_ScatterDistance2.colorValue.g, m_ScatterDistance2.colorValue.b);
Vector4 stdDev1 = s * m_ScatterDistance1.colorValue;
Vector4 stdDev2 = s * m_ScatterDistance2.colorValue;
m_ProfileMaterial.SetVector(HDShaderIDs._StdDev1, stdDev1);
m_ProfileMaterial.SetVector(HDShaderIDs._StdDev2, stdDev2);
m_ProfileMaterial.SetFloat(HDShaderIDs._LerpWeight, m_LerpWeight.floatValue);
m_ProfileMaterial.SetFloat(HDShaderIDs._MaxRadius, rMax);
// <<< Old SSS Model
// Draw the profile.
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(256, 256), m_ProfileImage, m_ProfileMaterial, ScaleMode.ScaleToFit, 1.0f);
EditorGUILayout.Space();
EditorGUILayout.LabelField(styles.sssTransmittancePreview0, styles.centeredMiniBoldLabel);
EditorGUILayout.LabelField(styles.sssTransmittancePreview1, EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.LabelField(styles.sssTransmittancePreview2, EditorStyles.centeredGreyMiniLabel);
EditorGUILayout.Space();
// Old SSS Model >>>
// Multiply by 0.1 to convert from millimeters to centimeters. Apply the distance scale.
float a = 0.1f * SssConstants.SSS_BASIC_DISTANCE_SCALE;
Vector4 halfRcpVarianceAndWeight1 = new Vector4(a * a * 0.5f / (stdDev1.x * stdDev1.x), a * a * 0.5f / (stdDev1.y * stdDev1.y), a * a * 0.5f / (stdDev1.z * stdDev1.z), 4 * (1.0f - m_LerpWeight.floatValue));
Vector4 halfRcpVarianceAndWeight2 = new Vector4(a * a * 0.5f / (stdDev2.x * stdDev2.x), a * a * 0.5f / (stdDev2.y * stdDev2.y), a * a * 0.5f / (stdDev2.z * stdDev2.z), 4 * m_LerpWeight.floatValue);
m_TransmittanceMaterial.SetVector(HDShaderIDs._HalfRcpVarianceAndWeight1, halfRcpVarianceAndWeight1);
m_TransmittanceMaterial.SetVector(HDShaderIDs._HalfRcpVarianceAndWeight2, halfRcpVarianceAndWeight2);
// <<< Old SSS Model
m_TransmittanceMaterial.SetVector(HDShaderIDs._ShapeParam, S);
m_TransmittanceMaterial.SetVector(HDShaderIDs._TransmissionTint, transmissionEnabled ? T : Vector4.zero);
m_TransmittanceMaterial.SetVector(HDShaderIDs._ThicknessRemap, R);
// Draw the transmittance graph.
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(16, 16), m_TransmittanceImage, m_TransmittanceMaterial, ScaleMode.ScaleToFit, 16.0f);
serializedObject.ApplyModifiedProperties();
if (EditorGUI.EndChangeCheck())
{
HDRenderPipeline hdPipeline = RenderPipelineManager.currentPipeline as HDRenderPipeline;
// Validate each individual asset and update caches.
hdPipeline.sssSettings.OnValidate();
}
}
}
#endif
}