您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
301 行
11 KiB
301 行
11 KiB
using System;
|
|
using UnityEditor;
|
|
|
|
namespace UnityEngine.Experimental.Rendering.HDPipeline
|
|
{
|
|
[Serializable]
|
|
public class SubsurfaceScatteringProfile
|
|
{
|
|
public const int numSamples = 7; // Must be an odd number
|
|
|
|
[SerializeField, ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
|
|
Color m_StdDev1;
|
|
[SerializeField, ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
|
|
Color m_StdDev2;
|
|
[SerializeField]
|
|
float m_LerpWeight;
|
|
[SerializeField]
|
|
Vector4[] m_FilterKernel;
|
|
[SerializeField]
|
|
public bool m_KernelNeedsUpdate;
|
|
|
|
// --- Public Methods ---
|
|
|
|
public SubsurfaceScatteringProfile()
|
|
{
|
|
m_StdDev1 = new Color(0.3f, 0.3f, 0.3f, 0.0f);
|
|
m_StdDev2 = new Color(1.0f, 1.0f, 1.0f, 0.0f);
|
|
m_LerpWeight = 0.5f;
|
|
ComputeKernel();
|
|
}
|
|
|
|
public Color stdDev1
|
|
{
|
|
get { return m_StdDev1; }
|
|
set { if (m_StdDev1 != value) { m_StdDev1 = value; m_KernelNeedsUpdate = true; } }
|
|
}
|
|
|
|
public Color stdDev2
|
|
{
|
|
get { return m_StdDev2; }
|
|
set { if (m_StdDev2 != value) { m_StdDev2 = value; m_KernelNeedsUpdate = true; } }
|
|
}
|
|
|
|
public float lerpWeight
|
|
{
|
|
get { return m_LerpWeight; }
|
|
set { if (m_LerpWeight != value) { m_LerpWeight = value; m_KernelNeedsUpdate = true; } }
|
|
}
|
|
|
|
public Vector4[] filterKernel
|
|
{
|
|
get { if (m_KernelNeedsUpdate) ComputeKernel(); return m_FilterKernel; }
|
|
}
|
|
|
|
public void SetDirtyFlag()
|
|
{
|
|
m_KernelNeedsUpdate = true;
|
|
}
|
|
|
|
// --- Private Methods ---
|
|
|
|
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);
|
|
}
|
|
|
|
void ComputeKernel()
|
|
{
|
|
if (m_FilterKernel == null || m_FilterKernel.Length != numSamples)
|
|
{
|
|
m_FilterKernel = new Vector4[numSamples];
|
|
}
|
|
|
|
// 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² / (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.
|
|
|
|
// Find the widest Gaussian across 3 color channels.
|
|
float maxStdDev1 = Mathf.Max(m_StdDev1.r, m_StdDev1.g, m_StdDev1.b);
|
|
float maxStdDev2 = Mathf.Max(m_StdDev2.r, m_StdDev2.g, m_StdDev2.b);
|
|
|
|
Vector3 weightSum = new Vector3(0, 0, 0);
|
|
|
|
// Importance sample the linear combination of two Gaussians.
|
|
for (uint i = 0; i < numSamples; i++)
|
|
{
|
|
float u = (i + 0.5f) / numSamples;
|
|
float pos = GaussianCombinationCdfInverse(u, maxStdDev1, maxStdDev2, m_LerpWeight);
|
|
float pdf = GaussianCombination(pos, maxStdDev1, maxStdDev2, m_LerpWeight);
|
|
|
|
Vector3 val;
|
|
val.x = GaussianCombination(pos, m_StdDev1.r, m_StdDev2.r, m_LerpWeight);
|
|
val.y = GaussianCombination(pos, m_StdDev1.g, m_StdDev2.g, m_LerpWeight);
|
|
val.z = GaussianCombination(pos, m_StdDev1.b, m_StdDev2.b, m_LerpWeight);
|
|
|
|
m_FilterKernel[i].x = val.x / (pdf * numSamples);
|
|
m_FilterKernel[i].y = val.y / (pdf * numSamples);
|
|
m_FilterKernel[i].z = val.z / (pdf * numSamples);
|
|
m_FilterKernel[i].w = pos;
|
|
|
|
weightSum.x += m_FilterKernel[i].x;
|
|
weightSum.y += m_FilterKernel[i].y;
|
|
weightSum.z += m_FilterKernel[i].z;
|
|
}
|
|
|
|
// Renormalize the weights to conserve energy.
|
|
for (uint i = 0; i < numSamples; i++)
|
|
{
|
|
m_FilterKernel[i].x *= 1.0f / weightSum.x;
|
|
m_FilterKernel[i].y *= 1.0f / weightSum.y;
|
|
m_FilterKernel[i].z *= 1.0f / weightSum.z;
|
|
}
|
|
|
|
m_KernelNeedsUpdate = false;
|
|
}
|
|
}
|
|
|
|
public class SubsurfaceScatteringParameters : ScriptableObject
|
|
{
|
|
const int m_maxNumProfiles = 8;
|
|
[SerializeField]
|
|
int m_NumProfiles;
|
|
[SerializeField]
|
|
SubsurfaceScatteringProfile[] m_Profiles;
|
|
[SerializeField]
|
|
float m_BilateralScale;
|
|
|
|
// --- Public Methods ---
|
|
|
|
public SubsurfaceScatteringParameters()
|
|
{
|
|
m_NumProfiles = 1;
|
|
m_Profiles = new SubsurfaceScatteringProfile[m_NumProfiles];
|
|
m_BilateralScale = 0.1f;
|
|
|
|
for (int i = 0; i < m_NumProfiles; i++)
|
|
{
|
|
m_Profiles[i] = new SubsurfaceScatteringProfile();
|
|
}
|
|
}
|
|
|
|
public SubsurfaceScatteringProfile[] profiles { set { m_Profiles = value; OnValidate(); } get { return m_Profiles; } }
|
|
public float bilateralScale { set { m_BilateralScale = value; OnValidate(); } get { return m_BilateralScale; } }
|
|
|
|
public void SetDirtyFlag()
|
|
{
|
|
for (int i = 0; i < m_Profiles.Length; i++)
|
|
{
|
|
m_Profiles[i].SetDirtyFlag();
|
|
}
|
|
}
|
|
|
|
// --- Private Methods ---
|
|
|
|
void OnValidate()
|
|
{
|
|
if (m_Profiles.Length > m_maxNumProfiles)
|
|
{
|
|
Array.Resize(ref m_Profiles, m_maxNumProfiles);
|
|
}
|
|
|
|
m_NumProfiles = m_Profiles.Length;
|
|
|
|
Color c = new Color();
|
|
|
|
for (int i = 0; i < m_NumProfiles; i++)
|
|
{
|
|
c.r = Mathf.Clamp(m_Profiles[i].stdDev1.r, 0.05f, 2.0f);
|
|
c.g = Mathf.Clamp(m_Profiles[i].stdDev1.g, 0.05f, 2.0f);
|
|
c.b = Mathf.Clamp(m_Profiles[i].stdDev1.b, 0.05f, 2.0f);
|
|
c.a = 0.0f;
|
|
|
|
m_Profiles[i].stdDev1 = c;
|
|
|
|
c.r = Mathf.Clamp(m_Profiles[i].stdDev2.r, 0.05f, 2.0f);
|
|
c.g = Mathf.Clamp(m_Profiles[i].stdDev2.g, 0.05f, 2.0f);
|
|
c.b = Mathf.Clamp(m_Profiles[i].stdDev2.b, 0.05f, 2.0f);
|
|
c.a = 0.0f;
|
|
|
|
m_Profiles[i].stdDev2 = c;
|
|
|
|
m_Profiles[i].lerpWeight = Mathf.Clamp01(m_Profiles[i].lerpWeight);
|
|
}
|
|
|
|
m_BilateralScale = Mathf.Clamp01(m_BilateralScale);
|
|
}
|
|
}
|
|
|
|
public class SubsurfaceScatteringSettings : Singleton<SubsurfaceScatteringSettings>
|
|
{
|
|
SubsurfaceScatteringParameters settings { get; set; }
|
|
|
|
public static SubsurfaceScatteringParameters overrideSettings
|
|
{
|
|
get { return instance.settings; }
|
|
set { instance.settings = value; }
|
|
}
|
|
}
|
|
|
|
[CustomEditor(typeof(SubsurfaceScatteringParameters))]
|
|
public class SubsurfaceScatteringParametersEditor : Editor
|
|
{
|
|
private class Styles
|
|
{
|
|
public readonly GUIContent sssCategory = new GUIContent("Subsurface scattering");
|
|
public readonly GUIContent sssProfileStdDev1 = new GUIContent("SSS profile standard deviation #1", "Determines the shape of the 1st Gaussian filter. Increases the strength and the radius of the blur of the corresponding color channel.");
|
|
public readonly GUIContent sssProfileStdDev2 = new GUIContent("SSS profile standard deviation #2", "Determines the shape of the 2nd Gaussian filter. Increases the strength and the radius of the blur of the corresponding color channel.");
|
|
public readonly GUIContent sssProfileLerpWeight = new GUIContent("SSS profile filter interpolation", "Controls linear interpolation between the two Gaussian filters.");
|
|
public readonly GUIContent sssBilateralScale = new GUIContent("SSS bilateral filtering scale", "Larger values make the filter more tolerant to depth differences.");
|
|
}
|
|
|
|
private static Styles s_Styles;
|
|
|
|
private SerializedProperty m_Profiles;
|
|
private SerializedProperty m_BilateralScale;
|
|
|
|
// --- Public Methods ---
|
|
|
|
private static Styles styles
|
|
{
|
|
get
|
|
{
|
|
if (s_Styles == null)
|
|
{
|
|
s_Styles = new Styles();
|
|
}
|
|
return s_Styles;
|
|
}
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
serializedObject.Update();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(m_Profiles, true);
|
|
EditorGUILayout.PropertyField(m_BilateralScale, styles.sssBilateralScale);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
// Serialization ignores setters.
|
|
((SubsurfaceScatteringParameters)target).SetDirtyFlag();
|
|
}
|
|
}
|
|
|
|
// --- Private Methods ---
|
|
|
|
void OnEnable()
|
|
{
|
|
m_Profiles = serializedObject.FindProperty("m_Profiles");
|
|
m_BilateralScale = serializedObject.FindProperty("m_BilateralScale");
|
|
}
|
|
}
|
|
}
|