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

196 行
7.2 KiB

using System;
namespace UnityEngine.Experimental.Rendering.HDPipeline
{
[Serializable]
public class SubsurfaceScatteringProfile
{
public const int numSamples = 7;
Color m_StdDev1;
Color m_StdDev2;
float m_LerpWeight;
Vector4[] m_FilterKernel;
bool m_KernelNeedsUpdate;
// --- Methods ---
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 static SubsurfaceScatteringProfile Default
{
get
{
SubsurfaceScatteringProfile profile = new SubsurfaceScatteringProfile();
profile.m_StdDev1 = new Color(0.3f, 0.3f, 0.3f, 0.0f);
profile.m_StdDev2 = new Color(1.0f, 1.0f, 1.0f, 0.0f);
profile.m_LerpWeight = 0.5f;
profile.ComputeKernel();
return profile;
}
}
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);
}
// Ref: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
static float VanDerCorputBase2(uint i)
{
i = i + 1;
i = (i << 16) | (i >> 16);
i = ((i & 0x00ff00ff) << 8) | ((i & 0xff00ff00) >> 8);
i = ((i & 0x0f0f0f0f) << 4) | ((i & 0xf0f0f0f0) >> 4);
i = ((i & 0x33333333) << 2) | ((i & 0xcccccccc) >> 2);
i = ((i & 0x55555555) << 1) | ((i & 0xaaaaaaaa) >> 1);
return i * (1.0f / 4294967296);
}
void ComputeKernel()
{
if (m_FilterKernel == null)
{
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 = VanDerCorputBase2(i);
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;
}
}
[System.Serializable]
public class SubsurfaceScatteringParameters
{
public const int numProfiles = 1;
public SubsurfaceScatteringProfile[] profiles;
public float bilateralScale;
// --- Methods ---
public static SubsurfaceScatteringParameters Default
{
get
{
SubsurfaceScatteringParameters parameters = new SubsurfaceScatteringParameters();
parameters.profiles = new SubsurfaceScatteringProfile[numProfiles];
for (int i = 0; i < numProfiles; i++)
{
parameters.profiles[i] = SubsurfaceScatteringProfile.Default;
}
parameters.bilateralScale = 0.1f;
return parameters;
}
}
}
}