John Parsaie
7 年前
当前提交
86e68589
共有 3 个文件被更改,包括 698 次插入 和 680 次删除
-
705ScriptableRenderPipeline/LightweightPipeline/LWRP/DiffusionProfile/DiffusionProfileSettings.cs
-
475ScriptableRenderPipeline/LightweightPipeline/LWRP/Editor/DiffusionProfile/DiffusionProfileSettingsEditor.cs
-
198ScriptableRenderPipeline/LightweightPipeline/LWRP/Shaders/Subsurface/DiffusionProfile/PreintegratedScatter.shader
|
|||
using System; |
|||
|
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering.LightweightPipeline |
|||
{ |
|||
|
|||
//A minimal implementation of HDRP Diffusion Profile, to support out preintegrated subsurface scattering model.
|
|||
|
|||
[GenerateHLSL] |
|||
public class DiffusionProfileConstants |
|||
{ |
|||
public const int DIFFUSION_PROFILE_COUNT = 9; |
|||
public const int DIFFUSION_PROFILE_NEUTRAL_ID = 0; |
|||
public const int SSS_N_SAMPLES = 11; |
|||
public const int SSS_DISTANCE_SCALE = 3; |
|||
} |
|||
|
|||
public static class DiffusionProfileShaderIDs |
|||
{ |
|||
public static readonly int _StdDev1 = Shader.PropertyToID("_StdDev1"); |
|||
public static readonly int _StdDev2 = Shader.PropertyToID("_StdDev2"); |
|||
public static readonly int _LerpWeight = Shader.PropertyToID("_LerpWeight"); |
|||
public static readonly int _HalfRcpVarianceAndWeight1 = Shader.PropertyToID("_HalfRcpVarianceAndWeight1"); |
|||
public static readonly int _HalfRcpVarianceAndWeight2 = Shader.PropertyToID("_HalfRcpVarianceAndWeight2"); |
|||
public static readonly int _TransmissionTint = Shader.PropertyToID("_TransmissionTint"); |
|||
public static readonly int _ThicknessRemap = Shader.PropertyToID("_ThicknessRemap"); |
|||
|
|||
//Addition Runtime Constants
|
|||
public static readonly int _PreintegratedDiffuseScatteringTextures = Shader.PropertyToID("_PreintegratedDiffuseScatteringTextures"); |
|||
public static readonly int _HalfRcpVariancesAndWeights = Shader.PropertyToID("_HalfRcpVariancesAndWeights"); |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class DiffusionProfile |
|||
{ |
|||
public string name; |
|||
|
|||
[ColorUsage(false, true)] public Color scatterDistance1; |
|||
[ColorUsage(false, true)] public Color scatterDistance2; |
|||
[Range(0f, 1f)] public float lerpWeight; |
|||
[ColorUsage(false)] public Color transmissionTint; |
|||
public Vector2 thicknessRemap; |
|||
|
|||
public Vector4[] filterKernel { get; private set; } |
|||
public Vector4 halfRcpWeightedVariances { get; private set; } |
|||
|
|||
public DiffusionProfile(string name) |
|||
{ |
|||
this.name = name; |
|||
|
|||
scatterDistance1 = new Color(0.3f, 0.3f, 0.3f, 0f); |
|||
scatterDistance2 = new Color(0.5f, 0.5f, 0.5f, 0f); |
|||
lerpWeight = 1f; |
|||
|
|||
transmissionTint = Color.white; |
|||
thicknessRemap = new Vector2(0f, 5f); |
|||
|
|||
} |
|||
|
|||
public void Validate() |
|||
{ |
|||
thicknessRemap.y = Mathf.Max(thicknessRemap.y, 0f); |
|||
thicknessRemap.x = Mathf.Clamp(thicknessRemap.x, 0f, thicknessRemap.y); |
|||
|
|||
scatterDistance1 = new Color |
|||
{ |
|||
r = Mathf.Max(0.05f, scatterDistance1.r), |
|||
g = Mathf.Max(0.05f, scatterDistance1.g), |
|||
b = Mathf.Max(0.05f, scatterDistance1.b), |
|||
a = 0.0f |
|||
}; |
|||
|
|||
scatterDistance2 = new Color |
|||
{ |
|||
r = Mathf.Max(0.05f, scatterDistance2.r), |
|||
g = Mathf.Max(0.05f, scatterDistance2.g), |
|||
b = Mathf.Max(0.05f, scatterDistance2.b), |
|||
a = 0.0f |
|||
}; |
|||
|
|||
UpdateKernelAndVarianceData(); |
|||
} |
|||
|
|||
public void UpdateKernelAndVarianceData() |
|||
{ |
|||
const int kNumSamples = DiffusionProfileConstants.SSS_N_SAMPLES; |
|||
const int kDistanceScale = DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
|
|||
if (filterKernel == null || filterKernel.Length != kNumSamples) |
|||
filterKernel = new Vector4[kNumSamples]; |
|||
|
|||
// Apply the three-sigma rule, and rescale.
|
|||
var stdDev1 = ((1f / 3f) * kDistanceScale) * scatterDistance1; |
|||
var stdDev2 = ((1f / 3f) * kDistanceScale) * 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); |
|||
|
|||
var weightSum = Vector3.zero; |
|||
|
|||
float step = 1f / (kNumSamples - 1); |
|||
|
|||
// Importance sample the linear combination of two Gaussians.
|
|||
for (int i = 0; i < kNumSamples; i++) |
|||
{ |
|||
// Generate 'u' on (0, 0.5] and (0.5, 1).
|
|||
float u = (i <= kNumSamples / 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.
|
|||
filterKernel[i].x = val.x * (1 / pdf); |
|||
filterKernel[i].y = val.y * (1 / pdf); |
|||
filterKernel[i].z = val.z * (1 / pdf); |
|||
filterKernel[i].w = pos; |
|||
|
|||
weightSum.x += filterKernel[i].x; |
|||
weightSum.y += filterKernel[i].y; |
|||
weightSum.z += filterKernel[i].z; |
|||
} |
|||
|
|||
// Renormalize the weights to conserve energy.
|
|||
for (int i = 0; i < kNumSamples; i++) |
|||
{ |
|||
filterKernel[i].x *= 1 / weightSum.x; |
|||
filterKernel[i].y *= 1 / weightSum.y; |
|||
filterKernel[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.
|
|||
// Warning: do not use halfRcpWeightedVariances.Set(). It will not work.
|
|||
halfRcpWeightedVariances = new Vector4(0.5f / (weightedStdDev.x * weightedStdDev.x), |
|||
0.5f / (weightedStdDev.y * weightedStdDev.y), |
|||
0.5f / (weightedStdDev.z * weightedStdDev.z), |
|||
0.5f / (weightedStdDev.w * weightedStdDev.w)); |
|||
} |
|||
|
|||
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(-2f * Mathf.Log(p))); |
|||
} |
|||
else |
|||
{ |
|||
// F^-1(p) = G^-1(1-p)
|
|||
x = RationalApproximation(Mathf.Sqrt(-2f * Mathf.Log(1f - 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); |
|||
} |
|||
} |
|||
|
|||
public sealed class DiffusionProfileSettings : ScriptableObject |
|||
{ |
|||
public DiffusionProfile[] profiles; |
|||
|
|||
[NonSerialized] public Vector4[] worldScales; |
|||
[NonSerialized] public Vector4[] thicknessRemaps; |
|||
[NonSerialized] public Vector4[] transmissionTints; |
|||
|
|||
//TODO: Preintegration turns this into a texture
|
|||
[NonSerialized] public Vector4[] halfRcpWeightedVariances; |
|||
[NonSerialized] public Vector4[] halfRcpVariancesAndWeights; |
|||
[NonSerialized] public Vector4[] filterKernels; |
|||
|
|||
[NonSerialized] private Material preintegration; |
|||
[NonSerialized] public TextureCache2D preintegratedScatterLUTs; |
|||
|
|||
public DiffusionProfile this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
if (index >= DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1) |
|||
throw new IndexOutOfRangeException("index"); |
|||
|
|||
return profiles[index]; |
|||
} |
|||
} |
|||
|
|||
static void ValidateArray<T>(ref T[] array, int len) |
|||
{ |
|||
if (array == null || array.Length != len) |
|||
array = new T[len]; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
// The neutral profile is not a part of the array.
|
|||
int profileArraySize = DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; |
|||
|
|||
if (profiles != null && profiles.Length != profileArraySize) |
|||
Array.Resize(ref profiles, profileArraySize); |
|||
|
|||
if (profiles == null) |
|||
profiles = new DiffusionProfile[profileArraySize]; |
|||
|
|||
for (int i = 0; i < profileArraySize; i++) |
|||
{ |
|||
if (profiles[i] == null) |
|||
profiles[i] = new DiffusionProfile("Profile " + (i + 1)); |
|||
|
|||
profiles[i].Validate(); |
|||
} |
|||
|
|||
|
|||
ValidateArray(ref thicknessRemaps, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref worldScales, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref transmissionTints, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref halfRcpWeightedVariances, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref halfRcpVariancesAndWeights, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT * 2); |
|||
ValidateArray(ref filterKernels, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT * DiffusionProfileConstants.SSS_N_SAMPLES); |
|||
|
|||
Debug.Assert(DiffusionProfileConstants.DIFFUSION_PROFILE_NEUTRAL_ID <= 32, "Transmission and Texture flags (32-bit integer) cannot support more than 32 profiles."); |
|||
|
|||
//NOTE: We can't get reference to the render pipeline assets from scriptable object, but we add it to the assets anyways so they get included in build.
|
|||
// Searching here is safe because we linked already in the LW resources, forcing it to be included in build.
|
|||
preintegration = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/PreintegratedScatter"); |
|||
|
|||
preintegratedScatterLUTs = new TextureCache2D(); |
|||
preintegratedScatterLUTs.AllocTextureArray(DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT, 128, 128, TextureFormat.ARGB32, false); |
|||
|
|||
UpdateCache(); |
|||
} |
|||
|
|||
public void UpdateCache() |
|||
{ |
|||
for (int i = 0; i < DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; i++) |
|||
{ |
|||
UpdateCache(i); |
|||
} |
|||
|
|||
// Fill the neutral profile.
|
|||
int neutralId = DiffusionProfileConstants.DIFFUSION_PROFILE_NEUTRAL_ID; |
|||
halfRcpWeightedVariances[neutralId] = Vector4.one; |
|||
for (int j = 0, n = DiffusionProfileConstants.SSS_N_SAMPLES; j < n; j++) |
|||
{ |
|||
filterKernels[n * neutralId + j] = Vector4.one; |
|||
filterKernels[n * neutralId + j].w = 0f; |
|||
} |
|||
} |
|||
|
|||
public void UpdateCache(int p) |
|||
{ |
|||
// 'p' is the profile array index. 'i' is the index in the shader (accounting for the neutral profile).
|
|||
int i = p + 1; |
|||
|
|||
// Erase previous value (This need to be done here individually as in the SSS editor we edit individual component)
|
|||
thicknessRemaps[i] = new Vector4(profiles[p].thicknessRemap.x, profiles[p].thicknessRemap.y - profiles[p].thicknessRemap.x, 0f, 0f); |
|||
// Convert ior to fresnel0
|
|||
transmissionTints[i] = new Vector4(profiles[p].transmissionTint.r * 0.25f, profiles[p].transmissionTint.g * 0.25f, profiles[p].transmissionTint.b * 0.25f, 0f); // Premultiplied
|
|||
//disabledTransmissionTintsAndFresnel0[i] = new Vector4(0.0f, 0.0f, 0.0f, fresnel0);
|
|||
|
|||
halfRcpWeightedVariances[i] = profiles[p].halfRcpWeightedVariances; |
|||
|
|||
var stdDev1 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)profiles[p].scatterDistance1; |
|||
var stdDev2 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)profiles[p].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 * DiffusionProfileConstants.SSS_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), 4f * (1f - profiles[p].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), 4f * profiles[p].lerpWeight); |
|||
|
|||
for (int j = 0, n = DiffusionProfileConstants.SSS_N_SAMPLES; j < n; j++) |
|||
{ |
|||
filterKernels[n * i + j] = profiles[p].filterKernel[j]; |
|||
} |
|||
|
|||
RTHandle preintegratedScatterRT = RTHandle.Alloc(128, 128, 1, DepthBits.None, RenderTextureFormat.ARGB32, FilterMode.Point, TextureWrapMode.Clamp, TextureDimension.Tex2D, false); |
|||
|
|||
|
|||
preintegration.SetVector(DiffusionProfileShaderIDs._StdDev1, stdDev1); |
|||
preintegration.SetVector(DiffusionProfileShaderIDs._StdDev2, stdDev2); |
|||
preintegration.SetFloat (DiffusionProfileShaderIDs._LerpWeight, profiles[p].lerpWeight); |
|||
|
|||
CommandBuffer cmd = new CommandBuffer() { name = "BuildLUT_" + p }; |
|||
cmd.Blit(null, preintegratedScatterRT, preintegration); |
|||
preintegratedScatterLUTs.TransferToSlice(cmd, i, preintegratedScatterRT); |
|||
Graphics.ExecuteCommandBuffer(cmd); |
|||
} |
|||
} |
|||
using System; |
|||
|
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEngine.Experimental.Rendering.LightweightPipeline |
|||
{ |
|||
|
|||
//A minimal implementation of HDRP Diffusion Profile, to support out preintegrated subsurface scattering model.
|
|||
|
|||
[GenerateHLSL] |
|||
public class DiffusionProfileConstants |
|||
{ |
|||
public const int DIFFUSION_PROFILE_COUNT = 9; |
|||
public const int DIFFUSION_PROFILE_NEUTRAL_ID = 0; |
|||
public const int SSS_N_SAMPLES = 11; |
|||
public const int SSS_DISTANCE_SCALE = 3; |
|||
} |
|||
|
|||
public static class DiffusionProfileShaderIDs |
|||
{ |
|||
public static readonly int _StdDev1 = Shader.PropertyToID("_StdDev1"); |
|||
public static readonly int _StdDev2 = Shader.PropertyToID("_StdDev2"); |
|||
public static readonly int _LerpWeight = Shader.PropertyToID("_LerpWeight"); |
|||
public static readonly int _HalfRcpVarianceAndWeight1 = Shader.PropertyToID("_HalfRcpVarianceAndWeight1"); |
|||
public static readonly int _HalfRcpVarianceAndWeight2 = Shader.PropertyToID("_HalfRcpVarianceAndWeight2"); |
|||
public static readonly int _TransmissionTint = Shader.PropertyToID("_TransmissionTint"); |
|||
public static readonly int _ThicknessRemap = Shader.PropertyToID("_ThicknessRemap"); |
|||
|
|||
//Addition Runtime Constants
|
|||
public static readonly int _PreintegratedDiffuseScatteringTextures = Shader.PropertyToID("_PreintegratedDiffuseScatteringTextures"); |
|||
public static readonly int _HalfRcpVariancesAndWeights = Shader.PropertyToID("_HalfRcpVariancesAndWeights"); |
|||
} |
|||
|
|||
[Serializable] |
|||
public sealed class DiffusionProfile |
|||
{ |
|||
public string name; |
|||
|
|||
[ColorUsage(false, true)] public Color scatterDistance1; |
|||
[ColorUsage(false, true)] public Color scatterDistance2; |
|||
[Range(0f, 1f)] public float lerpWeight; |
|||
[ColorUsage(false)] public Color transmissionTint; |
|||
public Vector2 thicknessRemap; |
|||
|
|||
public Vector4[] filterKernel { get; private set; } |
|||
public Vector4 halfRcpWeightedVariances { get; private set; } |
|||
|
|||
public Texture2D preintegratedLUT; |
|||
|
|||
public DiffusionProfile(string name) |
|||
{ |
|||
this.name = name; |
|||
|
|||
scatterDistance1 = new Color(0.3f, 0.3f, 0.3f, 0f); |
|||
scatterDistance2 = new Color(0.5f, 0.5f, 0.5f, 0f); |
|||
lerpWeight = 1f; |
|||
|
|||
transmissionTint = Color.white; |
|||
thicknessRemap = new Vector2(0f, 5f); |
|||
|
|||
preintegratedLUT = new Texture2D(128, 128, TextureFormat.ARGB32, false, true); |
|||
} |
|||
|
|||
public void Validate() |
|||
{ |
|||
thicknessRemap.y = Mathf.Max(thicknessRemap.y, 0f); |
|||
thicknessRemap.x = Mathf.Clamp(thicknessRemap.x, 0f, thicknessRemap.y); |
|||
|
|||
scatterDistance1 = new Color |
|||
{ |
|||
r = Mathf.Max(0.05f, scatterDistance1.r), |
|||
g = Mathf.Max(0.05f, scatterDistance1.g), |
|||
b = Mathf.Max(0.05f, scatterDistance1.b), |
|||
a = 0.0f |
|||
}; |
|||
|
|||
scatterDistance2 = new Color |
|||
{ |
|||
r = Mathf.Max(0.05f, scatterDistance2.r), |
|||
g = Mathf.Max(0.05f, scatterDistance2.g), |
|||
b = Mathf.Max(0.05f, scatterDistance2.b), |
|||
a = 0.0f |
|||
}; |
|||
|
|||
UpdateKernelAndVarianceData(); |
|||
BakeLUT(); |
|||
} |
|||
|
|||
public void UpdateKernelAndVarianceData() |
|||
{ |
|||
const int kNumSamples = DiffusionProfileConstants.SSS_N_SAMPLES; |
|||
const int kDistanceScale = DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
|
|||
if (filterKernel == null || filterKernel.Length != kNumSamples) |
|||
filterKernel = new Vector4[kNumSamples]; |
|||
|
|||
// Apply the three-sigma rule, and rescale.
|
|||
var stdDev1 = ((1f / 3f) * kDistanceScale) * scatterDistance1; |
|||
var stdDev2 = ((1f / 3f) * kDistanceScale) * 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); |
|||
|
|||
var weightSum = Vector3.zero; |
|||
|
|||
float step = 1f / (kNumSamples - 1); |
|||
|
|||
// Importance sample the linear combination of two Gaussians.
|
|||
for (int i = 0; i < kNumSamples; i++) |
|||
{ |
|||
// Generate 'u' on (0, 0.5] and (0.5, 1).
|
|||
float u = (i <= kNumSamples / 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.
|
|||
filterKernel[i].x = val.x * (1 / pdf); |
|||
filterKernel[i].y = val.y * (1 / pdf); |
|||
filterKernel[i].z = val.z * (1 / pdf); |
|||
filterKernel[i].w = pos; |
|||
|
|||
weightSum.x += filterKernel[i].x; |
|||
weightSum.y += filterKernel[i].y; |
|||
weightSum.z += filterKernel[i].z; |
|||
} |
|||
|
|||
// Renormalize the weights to conserve energy.
|
|||
for (int i = 0; i < kNumSamples; i++) |
|||
{ |
|||
filterKernel[i].x *= 1 / weightSum.x; |
|||
filterKernel[i].y *= 1 / weightSum.y; |
|||
filterKernel[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.
|
|||
// Warning: do not use halfRcpWeightedVariances.Set(). It will not work.
|
|||
halfRcpWeightedVariances = new Vector4(0.5f / (weightedStdDev.x * weightedStdDev.x), |
|||
0.5f / (weightedStdDev.y * weightedStdDev.y), |
|||
0.5f / (weightedStdDev.z * weightedStdDev.z), |
|||
0.5f / (weightedStdDev.w * weightedStdDev.w)); |
|||
} |
|||
|
|||
public void BakeLUT() |
|||
{ |
|||
CommandBuffer cmd = new CommandBuffer() { name = "BuildLUT" }; |
|||
Material preintegration = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/PreintegratedScatter"); |
|||
|
|||
var stdDev1 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)scatterDistance1; |
|||
var stdDev2 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)scatterDistance2; |
|||
|
|||
preintegration.SetVector(DiffusionProfileShaderIDs._StdDev1, stdDev1); |
|||
preintegration.SetVector(DiffusionProfileShaderIDs._StdDev2, stdDev2); |
|||
preintegration.SetFloat (DiffusionProfileShaderIDs._LerpWeight, lerpWeight); |
|||
|
|||
RTHandle preintegratedScatterRT = RTHandle.Alloc(128, 128, 1, DepthBits.None, RenderTextureFormat.ARGB32, FilterMode.Point, TextureWrapMode.Clamp, TextureDimension.Tex2D, false); |
|||
cmd.Blit(null, preintegratedScatterRT, preintegration); |
|||
|
|||
if(preintegratedLUT == null) |
|||
preintegratedLUT = new Texture2D(128, 128, TextureFormat.ARGB32, false, true); |
|||
|
|||
cmd.CopyTexture(preintegratedScatterRT, preintegratedLUT); |
|||
preintegratedLUT.Apply(); |
|||
|
|||
Graphics.ExecuteCommandBuffer(cmd); |
|||
cmd.Dispose(); |
|||
CoreUtils.Destroy(preintegration); |
|||
} |
|||
|
|||
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(-2f * Mathf.Log(p))); |
|||
} |
|||
else |
|||
{ |
|||
// F^-1(p) = G^-1(1-p)
|
|||
x = RationalApproximation(Mathf.Sqrt(-2f * Mathf.Log(1f - 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); |
|||
} |
|||
} |
|||
|
|||
public sealed class DiffusionProfileSettings : ScriptableObject |
|||
{ |
|||
public DiffusionProfile[] profiles; |
|||
|
|||
[NonSerialized] public Vector4[] worldScales; |
|||
[NonSerialized] public Vector4[] thicknessRemaps; |
|||
[NonSerialized] public Vector4[] transmissionTints; |
|||
|
|||
//TODO: Preintegration turns this into a texture
|
|||
[NonSerialized] public Vector4[] halfRcpWeightedVariances; |
|||
[NonSerialized] public Vector4[] halfRcpVariancesAndWeights; |
|||
[NonSerialized] public Vector4[] filterKernels; |
|||
|
|||
[NonSerialized] public TextureCache2D preintegratedScatterLUTs; |
|||
|
|||
public DiffusionProfile this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
if (index >= DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1) |
|||
throw new IndexOutOfRangeException("index"); |
|||
|
|||
return profiles[index]; |
|||
} |
|||
} |
|||
|
|||
static void ValidateArray<T>(ref T[] array, int len) |
|||
{ |
|||
if (array == null || array.Length != len) |
|||
array = new T[len]; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
// The neutral profile is not a part of the array.
|
|||
int profileArraySize = DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; |
|||
|
|||
if (profiles != null && profiles.Length != profileArraySize) |
|||
Array.Resize(ref profiles, profileArraySize); |
|||
|
|||
if (profiles == null) |
|||
profiles = new DiffusionProfile[profileArraySize]; |
|||
|
|||
for (int i = 0; i < profileArraySize; i++) |
|||
{ |
|||
if (profiles[i] == null) |
|||
profiles[i] = new DiffusionProfile("Profile " + (i + 1)); |
|||
|
|||
profiles[i].Validate(); |
|||
} |
|||
|
|||
ValidateArray(ref thicknessRemaps, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref worldScales, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref transmissionTints, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref halfRcpWeightedVariances, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT); |
|||
ValidateArray(ref halfRcpVariancesAndWeights, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT * 2); |
|||
ValidateArray(ref filterKernels, DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT * DiffusionProfileConstants.SSS_N_SAMPLES); |
|||
|
|||
Debug.Assert(DiffusionProfileConstants.DIFFUSION_PROFILE_NEUTRAL_ID <= 32, "Transmission and Texture flags (32-bit integer) cannot support more than 32 profiles."); |
|||
|
|||
preintegratedScatterLUTs = new TextureCache2D(); |
|||
preintegratedScatterLUTs.AllocTextureArray(DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT, 128, 128, TextureFormat.ARGB32, false); |
|||
|
|||
UpdateCache(); |
|||
} |
|||
|
|||
public void UpdateCache() |
|||
{ |
|||
for (int i = 0; i < DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; i++) |
|||
{ |
|||
UpdateCache(i); |
|||
} |
|||
|
|||
// Fill the neutral profile.
|
|||
int neutralId = DiffusionProfileConstants.DIFFUSION_PROFILE_NEUTRAL_ID; |
|||
halfRcpWeightedVariances[neutralId] = Vector4.one; |
|||
for (int j = 0, n = DiffusionProfileConstants.SSS_N_SAMPLES; j < n; j++) |
|||
{ |
|||
filterKernels[n * neutralId + j] = Vector4.one; |
|||
filterKernels[n * neutralId + j].w = 0f; |
|||
} |
|||
} |
|||
|
|||
public void UpdateCache(int p) |
|||
{ |
|||
// 'p' is the profile array index. 'i' is the index in the shader (accounting for the neutral profile).
|
|||
int i = p + 1; |
|||
|
|||
// Erase previous value (This need to be done here individually as in the SSS editor we edit individual component)
|
|||
thicknessRemaps[i] = new Vector4(profiles[p].thicknessRemap.x, profiles[p].thicknessRemap.y - profiles[p].thicknessRemap.x, 0f, 0f); |
|||
// Convert ior to fresnel0
|
|||
transmissionTints[i] = new Vector4(profiles[p].transmissionTint.r * 0.25f, profiles[p].transmissionTint.g * 0.25f, profiles[p].transmissionTint.b * 0.25f, 0f); // Premultiplied
|
|||
|
|||
halfRcpWeightedVariances[i] = profiles[p].halfRcpWeightedVariances; |
|||
|
|||
var stdDev1 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)profiles[p].scatterDistance1; |
|||
var stdDev2 = ((1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE) * (Vector4)profiles[p].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 * DiffusionProfileConstants.SSS_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), 4f * (1f - profiles[p].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), 4f * profiles[p].lerpWeight); |
|||
|
|||
for (int j = 0, n = DiffusionProfileConstants.SSS_N_SAMPLES; j < n; j++) |
|||
{ |
|||
filterKernels[n * i + j] = profiles[p].filterKernel[j]; |
|||
} |
|||
|
|||
CommandBuffer cmd = new CommandBuffer() { name = "BuildLUT_" + p }; |
|||
preintegratedScatterLUTs.TransferToSlice(cmd, i, profiles[p].preintegratedLUT); |
|||
Graphics.ExecuteCommandBuffer(cmd); |
|||
} |
|||
} |
|||
} |
|
|||
using System.Collections.Generic; |
|||
|
|||
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
using UnityEngine.Experimental.Rendering.LightweightPipeline; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering.LightweightPipeline |
|||
{ |
|||
|
|||
//LW class name and namespace are the same. Gotta do this for now.
|
|||
using LWPipeline = UnityEngine.Experimental.Rendering.LightweightPipeline.LightweightPipeline; |
|||
public class LWBaseEditor<T> : Editor where T : UnityEngine.Object |
|||
{ |
|||
internal PropertyFetcher<T> properties { get; private set; } |
|||
|
|||
protected T m_Target |
|||
{ |
|||
get { return target as T; } |
|||
} |
|||
|
|||
protected T[] m_Targets |
|||
{ |
|||
get { return targets as T[]; } |
|||
} |
|||
|
|||
protected LWPipeline m_LWPipeline |
|||
{ |
|||
get { return RenderPipelineManager.currentPipeline as LWPipeline; } |
|||
} |
|||
|
|||
protected virtual void OnEnable() |
|||
{ |
|||
properties = new PropertyFetcher<T>(serializedObject); |
|||
} |
|||
} |
|||
|
|||
[CustomEditor(typeof(DiffusionProfileSettings))] |
|||
public sealed partial class DiffusionProfileSettingsEditor : LWBaseEditor<DiffusionProfileSettings> |
|||
{ |
|||
sealed class Profile |
|||
{ |
|||
internal SerializedProperty self; |
|||
internal DiffusionProfile objReference; |
|||
|
|||
internal SerializedProperty name; |
|||
internal SerializedProperty transmissionTint; |
|||
internal SerializedProperty thicknessRemap; |
|||
internal SerializedProperty scatterDistance1; |
|||
internal SerializedProperty scatterDistance2; |
|||
internal SerializedProperty lerpWeight; |
|||
|
|||
// Render preview
|
|||
internal RenderTexture profileRT; |
|||
internal RenderTexture transmittanceRT; |
|||
|
|||
internal Profile() |
|||
{ |
|||
profileRT = new RenderTexture(256, 256, 0, RenderTextureFormat.DefaultHDR); |
|||
transmittanceRT = new RenderTexture( 16, 256, 0, RenderTextureFormat.DefaultHDR); |
|||
} |
|||
|
|||
internal void Release() |
|||
{ |
|||
CoreUtils.Destroy(profileRT); |
|||
CoreUtils.Destroy(transmittanceRT); |
|||
} |
|||
} |
|||
|
|||
List<Profile> m_Profiles; |
|||
|
|||
Material m_ProfileMaterial; |
|||
Material m_TransmittanceMaterial; |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
|
|||
// These shaders don't need to be reference by RenderPipelineResource as they are not use at runtime
|
|||
m_ProfileMaterial = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/PreintegratedScatter"); |
|||
m_TransmittanceMaterial = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/DrawTransmittanceGraph"); |
|||
|
|||
int count = DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; |
|||
m_Profiles = new List<Profile>(); |
|||
|
|||
var serializedProfiles = properties.Find(x => x.profiles); |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
var serializedProfile = serializedProfiles.GetArrayElementAtIndex(i); |
|||
var rp = new RelativePropertyFetcher<DiffusionProfile>(serializedProfile); |
|||
|
|||
var profile = new Profile |
|||
{ |
|||
self = serializedProfile, |
|||
objReference = m_Target.profiles[i], |
|||
|
|||
name = rp.Find(x => x.name), |
|||
|
|||
transmissionTint = rp.Find(x => x.transmissionTint), |
|||
thicknessRemap = rp.Find(x => x.thicknessRemap), |
|||
scatterDistance1 = rp.Find(x => x.scatterDistance1), |
|||
scatterDistance2 = rp.Find(x => x.scatterDistance2), |
|||
lerpWeight = rp.Find(x => x.lerpWeight) |
|||
}; |
|||
|
|||
m_Profiles.Add(profile); |
|||
} |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
CoreUtils.Destroy(m_ProfileMaterial); |
|||
CoreUtils.Destroy(m_TransmittanceMaterial); |
|||
|
|||
foreach (var profile in m_Profiles) |
|||
profile.Release(); |
|||
|
|||
m_Profiles = null; |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
CheckStyles(); |
|||
|
|||
// Display a warning if this settings asset is not currently in use
|
|||
if (m_LWPipeline == null || m_LWPipeline.DiffusionProfileSettings != m_Target) |
|||
EditorGUILayout.HelpBox("These profiles aren't currently in use, assign this asset to the LW render pipeline asset to use them.", MessageType.Warning); |
|||
|
|||
serializedObject.Update(); |
|||
|
|||
EditorGUILayout.Space(); |
|||
|
|||
if (m_Profiles == null || m_Profiles.Count == 0) |
|||
return; |
|||
|
|||
for (int i = 0; i < m_Profiles.Count; i++) |
|||
{ |
|||
var profile = m_Profiles[i]; |
|||
|
|||
CoreEditorUtils.DrawSplitter(); |
|||
|
|||
bool state = profile.self.isExpanded; |
|||
state = CoreEditorUtils.DrawHeaderFoldout(profile.name.stringValue, state); |
|||
|
|||
if (state) |
|||
{ |
|||
EditorGUI.indentLevel++; |
|||
EditorGUILayout.PropertyField(profile.name); |
|||
|
|||
using (var scope = new EditorGUI.ChangeCheckScope()) |
|||
{ |
|||
EditorGUILayout.PropertyField(profile.scatterDistance1, s_Styles.profileScatterDistance1); |
|||
EditorGUILayout.PropertyField(profile.scatterDistance2, s_Styles.profileScatterDistance2); |
|||
EditorGUILayout.PropertyField(profile.lerpWeight, s_Styles.profileLerpWeight); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.TransmissionLabel, EditorStyles.boldLabel); |
|||
|
|||
EditorGUILayout.PropertyField(profile.transmissionTint, s_Styles.profileTransmissionTint); |
|||
EditorGUILayout.PropertyField(profile.thicknessRemap, s_Styles.profileMinMaxThickness); |
|||
var thicknessRemap = profile.thicknessRemap.vector2Value; |
|||
EditorGUILayout.MinMaxSlider(s_Styles.profileThicknessRemap, ref thicknessRemap.x, ref thicknessRemap.y, 0f, 50f); |
|||
profile.thicknessRemap.vector2Value = thicknessRemap; |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview0, s_Styles.centeredMiniBoldLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview1, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview2, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview3, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.Space(); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
if (scope.changed) |
|||
{ |
|||
// Validate and update the cache for this profile only
|
|||
profile.objReference.Validate(); |
|||
m_Target.UpdateCache(i); |
|||
} |
|||
} |
|||
|
|||
RenderPreview(profile); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUI.indentLevel--; |
|||
} |
|||
|
|||
profile.self.isExpanded = state; |
|||
} |
|||
|
|||
CoreEditorUtils.DrawSplitter(); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
void RenderPreview(Profile profile) |
|||
{ |
|||
var T = (Vector4)profile.transmissionTint.colorValue; |
|||
var R = profile.thicknessRemap.vector2Value; |
|||
|
|||
// Apply the three-sigma rule, and rescale.
|
|||
float s = (1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
var scatterDist1 = profile.scatterDistance1.colorValue; |
|||
var scatterDist2 = profile.scatterDistance2.colorValue; |
|||
//float rMax = Mathf.Max(scatterDist1.r, scatterDist1.g, scatterDist1.b,
|
|||
// scatterDist2.r, scatterDist2.g, scatterDist2.b);
|
|||
var stdDev1 = s * (Vector4)scatterDist1; |
|||
var stdDev2 = s * (Vector4)scatterDist2; |
|||
m_ProfileMaterial.SetVector(DiffusionProfileShaderIDs._StdDev1, stdDev1); |
|||
m_ProfileMaterial.SetVector(DiffusionProfileShaderIDs._StdDev2, stdDev2); |
|||
m_ProfileMaterial.SetFloat (DiffusionProfileShaderIDs._LerpWeight, profile.lerpWeight.floatValue); |
|||
|
|||
// Draw the profile.
|
|||
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(128f, 128f), profile.profileRT, m_ProfileMaterial, ScaleMode.ScaleToFit, 1f); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview0, s_Styles.centeredMiniBoldLabel); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview1, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview2, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.Space(); |
|||
|
|||
// Multiply by 0.1 to convert from millimeters to centimeters. Apply the distance scale.
|
|||
float a = 0.1f * DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
var 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), 4f * (1f - profile.lerpWeight.floatValue)); |
|||
var 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), 4f * profile.lerpWeight.floatValue); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._HalfRcpVarianceAndWeight1, halfRcpVarianceAndWeight1); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._HalfRcpVarianceAndWeight2, halfRcpVarianceAndWeight2); |
|||
|
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._TransmissionTint, T); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._ThicknessRemap, R); |
|||
|
|||
// Draw the transmittance graph.
|
|||
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(16f, 16f), profile.transmittanceRT, m_TransmittanceMaterial, ScaleMode.ScaleToFit, 16f); |
|||
} |
|||
} |
|||
using System.Collections.Generic; |
|||
|
|||
|
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
using UnityEngine.Experimental.Rendering.LightweightPipeline; |
|||
|
|||
namespace UnityEditor.Experimental.Rendering.LightweightPipeline |
|||
{ |
|||
|
|||
//LW class name and namespace are the same. Gotta do this for now.
|
|||
using LWPipeline = UnityEngine.Experimental.Rendering.LightweightPipeline.LightweightPipeline; |
|||
public class LWBaseEditor<T> : Editor where T : UnityEngine.Object |
|||
{ |
|||
internal PropertyFetcher<T> properties { get; private set; } |
|||
|
|||
protected T m_Target |
|||
{ |
|||
get { return target as T; } |
|||
} |
|||
|
|||
protected T[] m_Targets |
|||
{ |
|||
get { return targets as T[]; } |
|||
} |
|||
|
|||
protected LWPipeline m_LWPipeline |
|||
{ |
|||
get { return RenderPipelineManager.currentPipeline as LWPipeline; } |
|||
} |
|||
|
|||
protected virtual void OnEnable() |
|||
{ |
|||
properties = new PropertyFetcher<T>(serializedObject); |
|||
} |
|||
} |
|||
|
|||
[CustomEditor(typeof(DiffusionProfileSettings))] |
|||
public sealed partial class DiffusionProfileSettingsEditor : LWBaseEditor<DiffusionProfileSettings> |
|||
{ |
|||
sealed class Profile |
|||
{ |
|||
internal SerializedProperty self; |
|||
internal DiffusionProfile objReference; |
|||
|
|||
internal SerializedProperty name; |
|||
internal SerializedProperty transmissionTint; |
|||
internal SerializedProperty thicknessRemap; |
|||
internal SerializedProperty scatterDistance1; |
|||
internal SerializedProperty scatterDistance2; |
|||
internal SerializedProperty lerpWeight; |
|||
|
|||
internal SerializedProperty preintegratedLUT; |
|||
|
|||
// Render preview
|
|||
internal RenderTexture profileRT; |
|||
internal RenderTexture transmittanceRT; |
|||
|
|||
internal Profile() |
|||
{ |
|||
profileRT = new RenderTexture(256, 256, 0, RenderTextureFormat.DefaultHDR); |
|||
transmittanceRT = new RenderTexture( 16, 256, 0, RenderTextureFormat.DefaultHDR); |
|||
} |
|||
|
|||
internal void Release() |
|||
{ |
|||
CoreUtils.Destroy(profileRT); |
|||
CoreUtils.Destroy(transmittanceRT); |
|||
} |
|||
} |
|||
|
|||
List<Profile> m_Profiles; |
|||
|
|||
Material m_ProfileMaterial; |
|||
Material m_TransmittanceMaterial; |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
|
|||
// These shaders don't need to be reference by RenderPipelineResource as they are not use at runtime
|
|||
m_ProfileMaterial = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/PreintegratedScatter"); |
|||
m_TransmittanceMaterial = CoreUtils.CreateEngineMaterial("Hidden/LightweightPipeline/DrawTransmittanceGraph"); |
|||
|
|||
int count = DiffusionProfileConstants.DIFFUSION_PROFILE_COUNT - 1; |
|||
m_Profiles = new List<Profile>(); |
|||
|
|||
var serializedProfiles = properties.Find(x => x.profiles); |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
var serializedProfile = serializedProfiles.GetArrayElementAtIndex(i); |
|||
var rp = new RelativePropertyFetcher<DiffusionProfile>(serializedProfile); |
|||
|
|||
var profile = new Profile |
|||
{ |
|||
self = serializedProfile, |
|||
objReference = m_Target.profiles[i], |
|||
|
|||
name = rp.Find(x => x.name), |
|||
|
|||
transmissionTint = rp.Find(x => x.transmissionTint), |
|||
thicknessRemap = rp.Find(x => x.thicknessRemap), |
|||
scatterDistance1 = rp.Find(x => x.scatterDistance1), |
|||
scatterDistance2 = rp.Find(x => x.scatterDistance2), |
|||
lerpWeight = rp.Find(x => x.lerpWeight), |
|||
preintegratedLUT = rp.Find(x => x.preintegratedLUT) |
|||
}; |
|||
|
|||
m_Profiles.Add(profile); |
|||
} |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
CoreUtils.Destroy(m_ProfileMaterial); |
|||
CoreUtils.Destroy(m_TransmittanceMaterial); |
|||
|
|||
foreach (var profile in m_Profiles) |
|||
profile.Release(); |
|||
|
|||
m_Profiles = null; |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
CheckStyles(); |
|||
|
|||
// Display a warning if this settings asset is not currently in use
|
|||
if (m_LWPipeline == null || m_LWPipeline.DiffusionProfileSettings != m_Target) |
|||
EditorGUILayout.HelpBox("These profiles aren't currently in use, assign this asset to the LW render pipeline asset to use them.", MessageType.Warning); |
|||
|
|||
serializedObject.Update(); |
|||
|
|||
EditorGUILayout.Space(); |
|||
|
|||
if (m_Profiles == null || m_Profiles.Count == 0) |
|||
return; |
|||
|
|||
for (int i = 0; i < m_Profiles.Count; i++) |
|||
{ |
|||
var profile = m_Profiles[i]; |
|||
|
|||
CoreEditorUtils.DrawSplitter(); |
|||
|
|||
bool state = profile.self.isExpanded; |
|||
state = CoreEditorUtils.DrawHeaderFoldout(profile.name.stringValue, state); |
|||
|
|||
if (state) |
|||
{ |
|||
EditorGUI.indentLevel++; |
|||
EditorGUILayout.PropertyField(profile.name); |
|||
|
|||
using (var scope = new EditorGUI.ChangeCheckScope()) |
|||
{ |
|||
EditorGUILayout.PropertyField(profile.scatterDistance1, s_Styles.profileScatterDistance1); |
|||
EditorGUILayout.PropertyField(profile.scatterDistance2, s_Styles.profileScatterDistance2); |
|||
EditorGUILayout.PropertyField(profile.lerpWeight, s_Styles.profileLerpWeight); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.TransmissionLabel, EditorStyles.boldLabel); |
|||
|
|||
EditorGUILayout.PropertyField(profile.transmissionTint, s_Styles.profileTransmissionTint); |
|||
EditorGUILayout.PropertyField(profile.thicknessRemap, s_Styles.profileMinMaxThickness); |
|||
var thicknessRemap = profile.thicknessRemap.vector2Value; |
|||
EditorGUILayout.MinMaxSlider(s_Styles.profileThicknessRemap, ref thicknessRemap.x, ref thicknessRemap.y, 0f, 50f); |
|||
profile.thicknessRemap.vector2Value = thicknessRemap; |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview0, s_Styles.centeredMiniBoldLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview1, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview2, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.profilePreview3, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.Space(); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
if (scope.changed) |
|||
{ |
|||
// Validate and update the cache for this profile only
|
|||
profile.objReference.Validate(); |
|||
m_Target.UpdateCache(i); |
|||
} |
|||
} |
|||
|
|||
RenderPreview(profile); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUI.indentLevel--; |
|||
} |
|||
|
|||
profile.self.isExpanded = state; |
|||
} |
|||
|
|||
CoreEditorUtils.DrawSplitter(); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
|
|||
void RenderPreview(Profile profile) |
|||
{ |
|||
var T = (Vector4)profile.transmissionTint.colorValue; |
|||
var R = profile.thicknessRemap.vector2Value; |
|||
|
|||
// Apply the three-sigma rule, and rescale.
|
|||
float s = (1f / 3f) * DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
var scatterDist1 = profile.scatterDistance1.colorValue; |
|||
var scatterDist2 = profile.scatterDistance2.colorValue; |
|||
//float rMax = Mathf.Max(scatterDist1.r, scatterDist1.g, scatterDist1.b,
|
|||
// scatterDist2.r, scatterDist2.g, scatterDist2.b);
|
|||
var stdDev1 = s * (Vector4)scatterDist1; |
|||
var stdDev2 = s * (Vector4)scatterDist2; |
|||
m_ProfileMaterial.SetVector(DiffusionProfileShaderIDs._StdDev1, stdDev1); |
|||
m_ProfileMaterial.SetVector(DiffusionProfileShaderIDs._StdDev2, stdDev2); |
|||
m_ProfileMaterial.SetFloat (DiffusionProfileShaderIDs._LerpWeight, profile.lerpWeight.floatValue); |
|||
|
|||
// Draw the profile.
|
|||
EditorGUI.DrawTextureTransparent(GUILayoutUtility.GetRect(128f, 128f), (Texture2D)profile.preintegratedLUT.objectReferenceValue, ScaleMode.ScaleToFit, 1f); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview0, s_Styles.centeredMiniBoldLabel); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview1, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.LabelField(s_Styles.transmittancePreview2, EditorStyles.centeredGreyMiniLabel); |
|||
EditorGUILayout.Space(); |
|||
|
|||
// Multiply by 0.1 to convert from millimeters to centimeters. Apply the distance scale.
|
|||
float a = 0.1f * DiffusionProfileConstants.SSS_DISTANCE_SCALE; |
|||
var 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), 4f * (1f - profile.lerpWeight.floatValue)); |
|||
var 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), 4f * profile.lerpWeight.floatValue); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._HalfRcpVarianceAndWeight1, halfRcpVarianceAndWeight1); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._HalfRcpVarianceAndWeight2, halfRcpVarianceAndWeight2); |
|||
|
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._TransmissionTint, T); |
|||
m_TransmittanceMaterial.SetVector(DiffusionProfileShaderIDs._ThicknessRemap, R); |
|||
|
|||
// Draw the transmittance graph.
|
|||
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(16f, 16f), profile.transmittanceRT, m_TransmittanceMaterial, ScaleMode.ScaleToFit, 16f); |
|||
} |
|||
} |
|||
} |
|
|||
Shader "Hidden/LightweightPipeline/PreintegratedScatter" |
|||
{ |
|||
SubShader |
|||
{ |
|||
Pass |
|||
{ |
|||
Cull Off |
|||
ZTest Always |
|||
ZWrite Off |
|||
Blend Off |
|||
|
|||
HLSLPROGRAM |
|||
#pragma target 4.5 |
|||
#pragma only_renderers d3d11 ps4 xboxone vulkan metal |
|||
|
|||
#pragma vertex Vert |
|||
#pragma fragment Frag |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Include |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
#include "CoreRP/ShaderLibrary/Common.hlsl" |
|||
#define USE_LEGACY_UNITY_MATRIX_VARIABLES |
|||
#include "HDRP/ShaderVariables.hlsl" |
|||
|
|||
#include "LWRP/DiffusionProfile/DiffusionProfileSettings.cs.hlsl" |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Inputs & outputs |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
float4 _StdDev1, _StdDev2; |
|||
float _LerpWeight; // See 'SubsurfaceScatteringParameters' |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Implementation |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
struct Attributes |
|||
{ |
|||
float3 vertex : POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
struct Varyings |
|||
{ |
|||
float4 vertex : SV_POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
Varyings Vert(Attributes input) |
|||
{ |
|||
Varyings output; |
|||
output.vertex = TransformWorldToHClip(input.vertex); |
|||
output.texcoord = input.texcoord.xy; |
|||
return output; |
|||
} |
|||
|
|||
float3 DiffusionProfile(float r, float3 var1, float3 var2) |
|||
{ |
|||
return lerp(exp(-r * r / (2 * var1)) / (TWO_PI * var1), |
|||
exp(-r * r / (2 * var2)) / (TWO_PI * var2), _LerpWeight); |
|||
} |
|||
|
|||
//Pre-integrate the diffuse scattering, indexed by Curvate and NdotL. |
|||
float4 Frag(Varyings input) : SV_Target |
|||
{ |
|||
float NdotL = 2.0 * input.texcoord.x - 1.0; |
|||
float Curvature = 1.0 / input.texcoord.y; |
|||
|
|||
float Theta = acos(NdotL); |
|||
float3 W = float3(0, 0, 0); |
|||
float3 L = float3(0, 0, 0); |
|||
|
|||
float x = -(PI / 2.0); |
|||
while(x <= (PI / 2.0)) |
|||
{ |
|||
float Diffuse = saturate(cos(Theta + x)); |
|||
|
|||
float R = abs(2.0 * Curvature * sin(x * 0.5)); |
|||
float3 Variance1 = _StdDev1.rgb * _StdDev1.rgb; |
|||
float3 Variance2 = _StdDev2.rgb * _StdDev2.rgb; |
|||
|
|||
float3 Weights = DiffusionProfile(R, Variance1, Variance2); |
|||
|
|||
W += Weights; |
|||
L += Diffuse * Weights; |
|||
|
|||
x += PI / 180.0; |
|||
} |
|||
|
|||
return float4(sqrt(L / W), 1); |
|||
} |
|||
ENDHLSL |
|||
} |
|||
} |
|||
Fallback Off |
|||
} |
|||
Shader "Hidden/LightweightPipeline/PreintegratedScatter" |
|||
{ |
|||
SubShader |
|||
{ |
|||
Pass |
|||
{ |
|||
Cull Off |
|||
ZTest Always |
|||
ZWrite Off |
|||
Blend Off |
|||
|
|||
HLSLPROGRAM |
|||
#pragma target 4.5 |
|||
#pragma only_renderers d3d11 ps4 xboxone vulkan metal |
|||
|
|||
#pragma vertex Vert |
|||
#pragma fragment Frag |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Include |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
#include "CoreRP/ShaderLibrary/Common.hlsl" |
|||
#define USE_LEGACY_UNITY_MATRIX_VARIABLES |
|||
#include "HDRP/ShaderVariables.hlsl" |
|||
|
|||
#include "LWRP/DiffusionProfile/DiffusionProfileSettings.cs.hlsl" |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Inputs & outputs |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
float4 _StdDev1, _StdDev2; |
|||
float _LerpWeight; // See 'SubsurfaceScatteringParameters' |
|||
|
|||
//------------------------------------------------------------------------------------- |
|||
// Implementation |
|||
//------------------------------------------------------------------------------------- |
|||
|
|||
struct Attributes |
|||
{ |
|||
float3 vertex : POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
struct Varyings |
|||
{ |
|||
float4 vertex : SV_POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
Varyings Vert(Attributes input) |
|||
{ |
|||
Varyings output; |
|||
output.vertex = TransformWorldToHClip(input.vertex); |
|||
output.texcoord = input.texcoord.xy; |
|||
return output; |
|||
} |
|||
|
|||
float3 DiffusionProfile(float r, float3 var1, float3 var2) |
|||
{ |
|||
return lerp(exp(-r * r / (2 * var1)) / (TWO_PI * var1), |
|||
exp(-r * r / (2 * var2)) / (TWO_PI * var2), _LerpWeight); |
|||
} |
|||
|
|||
//Pre-integrate the diffuse scattering, indexed by Curvate and NdotL. |
|||
float4 Frag(Varyings input) : SV_Target |
|||
{ |
|||
float NdotL = 2.0 * input.texcoord.x - 1.0; |
|||
float Curvature = 1.0 / input.texcoord.y; |
|||
|
|||
float Theta = acos(NdotL); |
|||
float3 W = float3(0, 0, 0); |
|||
float3 L = float3(0, 0, 0); |
|||
|
|||
float x = -(PI / 2.0); |
|||
while(x <= (PI / 2.0)) |
|||
{ |
|||
float Diffuse = saturate(cos(Theta + x)); |
|||
|
|||
float R = abs(2.0 * Curvature * sin(x * 0.5)); |
|||
float3 Variance1 = _StdDev1.rgb * _StdDev1.rgb; |
|||
float3 Variance2 = _StdDev2.rgb * _StdDev2.rgb; |
|||
|
|||
float3 Weights = DiffusionProfile(R, Variance1, Variance2); |
|||
|
|||
W += Weights; |
|||
L += Diffuse * Weights; |
|||
|
|||
x += PI / 180.0; |
|||
} |
|||
|
|||
return float4(sqrt(L / W), 1); |
|||
} |
|||
ENDHLSL |
|||
} |
|||
} |
|||
Fallback Off |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue