浏览代码

Improve the quality of bilateral weighting

/Branch_Batching2
Evgenii Golubev 7 年前
当前提交
1a787e97
共有 4 个文件被更改,包括 94 次插入127 次删除
  1. 10
      Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
  2. 2
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
  3. 97
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/CombineSubsurfaceScattering.shader
  4. 112
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs

10
Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs


// It is stored within 'm_CameraSubsurfaceBufferRT'.
readonly RenderTargetIdentifier m_CameraColorBufferRT;
readonly RenderTargetIdentifier m_CameraSubsurfaceBufferRT;
readonly RenderTargetIdentifier m_CameraFilteringBufferRT;
readonly RenderTargetIdentifier m_VelocityBufferRT;
readonly RenderTargetIdentifier m_DistortionBufferRT;

m_CameraSubsurfaceBuffer = Shader.PropertyToID("_CameraSubsurfaceTexture");
m_CameraFilteringBuffer = Shader.PropertyToID("_CameraFilteringBuffer");
m_CameraColorBufferRT = new RenderTargetIdentifier(m_CameraColorBuffer);
m_CameraSubsurfaceBufferRT = new RenderTargetIdentifier(m_CameraSubsurfaceBuffer);
m_CameraFilteringBufferRT = new RenderTargetIdentifier(m_CameraFilteringBuffer);
m_CameraColorBufferRT = new RenderTargetIdentifier(m_CameraColorBuffer);
m_CameraSubsurfaceBufferRT = new RenderTargetIdentifier(m_CameraSubsurfaceBuffer);
m_FilterAndCombineSubsurfaceScattering = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/CombineSubsurfaceScattering");

var cmd = new CommandBuffer() { name = "Subsurface Scattering" };
cmd.SetGlobalTexture("_IrradianceSource", m_CameraSubsurfaceBufferRT); // Cannot set a RT on a material
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_FilterKernelsNearField", sssParameters.filterKernelsNearField);
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_FilterKernelsFarField", sssParameters.filterKernelsFarField);
m_FilterAndCombineSubsurfaceScattering.SetFloatArray("_FilterKernelsNearField", sssParameters.filterKernelsNearField);
m_FilterAndCombineSubsurfaceScattering.SetFloatArray("_FilterKernelsFarField", sssParameters.filterKernelsFarField);
Utilities.DrawFullScreen(cmd, m_FilterAndCombineSubsurfaceScattering, hdCamera, m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);

2
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl


uint _TexturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
uint _TransmissionFlags; // 2 bit/profile; 0 = inf. thick, 1 = thin, 2 = regular
float _ThicknessRemaps[SSS_N_PROFILES][2]; // Remap: 0 = start, 1 = end - start
float4 _ShapeParameters[SSS_N_PROFILES]; // RGB: S = 1 / D; alpha is unused
float4 _ShapeParameters[SSS_N_PROFILES]; // RGB = S = 1 / D; A = filter radius
#define SSS_LOW_THICKNESS 10000 // REMOVE

97
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/CombineSubsurfaceScattering.shader


// Inputs & outputs
//-------------------------------------------------------------------------------------
float4 _FilterKernelsNearField[SSS_N_PROFILES][SSS_N_SAMPLES_NEAR_FIELD]; // RGB = weights, A = radial distance (in millimeters)
float4 _FilterKernelsFarField[SSS_N_PROFILES][SSS_N_SAMPLES_FAR_FIELD]; // RGB = weights, A = radial distance (in millimeters)
float _FilterKernelsNearField[SSS_N_PROFILES][SSS_N_SAMPLES_NEAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF
float _FilterKernelsFarField[SSS_N_PROFILES][SSS_N_SAMPLES_FAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF
TEXTURE2D(_IrradianceSource); // RGB = irradiance on the back side of the object
TEXTURE2D(_IrradianceSource); // Includes transmitted light
DECLARE_GBUFFER_TEXTURE(_GBufferTexture); // Contains the albedo and SSS parameters
//-------------------------------------------------------------------------------------

// Computes A * B, s.t.:
// A = (exp(-S * sqrt(r*r + z*z)) + exp(-S * sqrt(r*r + z*z) / 3)) / sqrt(r*r + z*z)
// B = sqrt(r*r) / (exp(-S * sqrt(r*r)) + exp(-S * sqrt(r*r) / 3))
float3 ComputeBilateralWeight(float3 S, float r, float z, float distScale)
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
float3 KernelValCircle(float r, float3 S)
float3 S3 = S * (1.0 / 3.0); // Same for all samples
float iF = rcp(distScale); // Same for all samples
float3 expOneThird = exp(-r * (S * (1.0 / 3.0)));
return (0.25 * S) * (expOneThird + expOneThird * expOneThird * expOneThird);
}
r *= iF; z *= iF;
// Computes F(x)/P(x), s.t. x = sqrt(r^2 + z^2).
float3 ComputeBilateralWeight(float3 S, float r, float z, float distScale, float rcpPdf)
{
// Reducing the integration distance is equivalent to stretching the integration axis.
float3 valX = KernelValCircle(sqrt(r * r + z * z) * rcp(distScale), S);
float rz2 = r * r + z * z;
float sR = abs(r);
float sRZ = sqrt(rz2);
float iRZ = rsqrt(rz2);
float3 eR = exp(-S3 * sR);
float3 eRZ = exp(-S3 * sRZ);
// The reciprocal of the PDF could be reinterpreted as a 'dx' term in Int{F(x)dx}.
// As we shift the location of the value on the curve during integration,
// the length of the segment 'dx' under the curve changes approximately linearly.
float rcpPdfX = rcpPdf * (1 + abs(z) / r);
eR += eR * eR * eR;
float3 A = iRZ * (eRZ * eRZ * eRZ + eRZ);
float3 B = sR / (eR * eR * eR + eR ); // TODO: precompute
return A * B;
return valX * rcpPdfX;
}
struct Attributes

FETCH_GBUFFER(gbuffer, _GBufferTexture, posInput.unPositionSS);
DECODE_FROM_GBUFFER(gbuffer, 0xFFFFFFFF, bsdfData, unused);
int profileID = bsdfData.subsurfaceProfile;
float distScale = bsdfData.subsurfaceRadius;
float3 shapeParam = _ShapeParameters[profileID].rgb;
int profileID = bsdfData.subsurfaceProfile;
float distScale = bsdfData.subsurfaceRadius;
float3 shapeParam = _ShapeParameters[profileID].rgb;
float maxDistance = _ShapeParameters[profileID].a;
// Reconstruct the view-space position.
float rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, posInput.unPositionSS).r;

// TODO: this could be done more accurately using a matrix precomputed on the CPU.
float2 metersPerPixel = float2(ddx_fine(centerPosVS.x), ddy_fine(centerPosVS.y));
float2 pixelsPerMillimeter = distScale * rcp(METERS_TO_MILLIMETERS * metersPerPixel);
float2 metersPerPixel = float2(ddx_fine(centerPosVS.x), ddy_fine(centerPosVS.y));
float2 scaledPixPerMm = distScale * rcp(METERS_TO_MILLIMETERS * metersPerPixel);
bool useNearFieldKernel = true; // TODO
// Take the first (central) sample.
float2 samplePosition = posInput.unPositionSS;
float3 sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
if (useNearFieldKernel)
// We perform point sampling. Therefore, we can avoid the cost
// of filtering if we stay within the bounds of the current pixel.
[branch]
if (maxDistance * max(scaledPixPerMm.x, scaledPixPerMm.y) < 0.5)
// Take the first (central) sample.
float2 samplePosition = posInput.unPositionSS;
float3 sampleWeight = _FilterKernelsNearField[profileID][0].rgb;
float3 sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
return float4(bsdfData.diffuseColor * sampleIrradiance, 1);
}
// We perform point sampling. Therefore, we can avoid the cost
// of filtering if we stay within the bounds of the current pixel.
float maxDistance = _FilterKernelsNearField[profileID][0].a;
bool useNearFieldKernel = true; // TODO
[branch]
if (maxDistance * max(pixelsPerMillimeter.x, pixelsPerMillimeter.y) < 0.5)
{
return float4(bsdfData.diffuseColor * sampleIrradiance, 1);
}
if (useNearFieldKernel)
{
float sampleRcpPdf = _FilterKernelsNearField[profileID][0][1];
float3 sampleWeight = KernelValCircle(0, shapeParam) * sampleRcpPdf;
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance = sampleWeight * sampleIrradiance;

for (uint i = 1; i < SSS_N_SAMPLES_NEAR_FIELD; i++)
{
// Everything except for the radius is a compile-time constant.
float r = _FilterKernelsNearField[profileID][i].a;
float r = _FilterKernelsNearField[profileID][i][0];
samplePosition = posInput.unPositionSS + pos * pixelsPerMillimeter;
sampleWeight = _FilterKernelsNearField[profileID][i].rgb;
samplePosition = posInput.unPositionSS + pos * scaledPixPerMm;
sampleRcpPdf = _FilterKernelsNearField[profileID][i][1];
// Apply bilateral weighting.
// We adjust the precomputed weight W(r) by W(d)/W(r), where
// r = sqrt(x^2 + y^2), d = sqrt(x^2 + y^2 + z^2) = sqrt(r^2 + z^2).
float sampleZ = LinearEyeDepth(rawDepth, _ZBufferParams);
float z = METERS_TO_MILLIMETERS * sampleZ - (METERS_TO_MILLIMETERS * centerPosVS.z);
sampleWeight *= ComputeBilateralWeight(shapeParam, r, z, distScale);
[flatten]
if (any(sampleIrradiance) == false)
{

// of the remaining samples (e.g. in case of hair covering skin).
continue;
}
// Apply bilateral weighting.
float sampleZ = LinearEyeDepth(rawDepth, _ZBufferParams);
float z = METERS_TO_MILLIMETERS * sampleZ - (METERS_TO_MILLIMETERS * centerPosVS.z);
sampleWeight = ComputeBilateralWeight(shapeParam, r, z, distScale, sampleRcpPdf);
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;

112
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs


public TransmissionMode transmissionMode;
public Vector2 thicknessRemap; // X = min, Y = max (in millimeters)
[HideInInspector]
public int settingsIndex;
public int settingsIndex; // For SubsurfaceScatteringSettings.profiles
Vector3 m_S; // RGB shape parameter: S = 1 / D
Vector4 m_S; // RGB = shape parameter, A = filter radius
Vector4[] m_FilterKernelNearField; // RGB = weights, A = radial distance (in millimeters)
Vector2[] m_FilterKernelNearField; // X = radius, Y = reciprocal of the PDF
Vector4[] m_FilterKernelFarField; // RGB = weights, A = radial distance (in millimeters)
Vector2[] m_FilterKernelFarField; // X = radius, Y = reciprocal of the PDF
// --- Public Methods ---

{
if (m_FilterKernelNearField == null || m_FilterKernelNearField.Length != SssConstants.SSS_N_SAMPLES_NEAR_FIELD)
{
m_FilterKernelNearField = new Vector4[SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
m_FilterKernelNearField = new Vector2[SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
m_FilterKernelFarField = new Vector4[SssConstants.SSS_N_SAMPLES_FAR_FIELD];
m_FilterKernelFarField = new Vector2[SssConstants.SSS_N_SAMPLES_FAR_FIELD];
m_S = new Vector3();
m_S = new Vector4();
// Evaluate the fit for diffuse surface transmission.
m_S.x = FindFitForS(surfaceAlbedo.r);

// CDF(r, s) = 1 - 1/4 * Exp[-r * s] - 3/4 * Exp[-r * s / 3]
// ------------------------------------------------------------------------------------
Vector3 weightSum = new Vector3(0, 0, 0);
float pdf = KernelPdf(r, s);
Vector3 val;
// N.b.: we multiply by the surface albedo of the actual geometry during shading.
val.x = KernelValCircle(r, m_S.x);
val.y = KernelValCircle(r, m_S.y);
val.z = KernelValCircle(r, m_S.z);
// float a = (2.0f * Mathf.PI) * VanDerCorputBase2(i);
// m_FilterKernelNearFieldPositions[i] = new Vector2(r * Mathf.Cos(a), r * Mathf.Sin(a));
// We do not divide by 'NUM_SAMPLES_NEAR_FIELD' due to the subsequent weight normalization.
m_FilterKernelNearField[i].x = val.x * (1.0f / pdf);
m_FilterKernelNearField[i].y = val.y * (1.0f / pdf);
m_FilterKernelNearField[i].z = val.z * (1.0f / pdf);
m_FilterKernelNearField[i].w = r;
weightSum.x += m_FilterKernelNearField[i].x;
weightSum.y += m_FilterKernelNearField[i].y;
weightSum.z += m_FilterKernelNearField[i].z;
// 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);
// As the first sample is in the center of the filter,
// we can use its radius slot to store the max radius of the kernel.
m_FilterKernelNearField[0].w = m_FilterKernelNearField[SssConstants.SSS_N_SAMPLES_NEAR_FIELD - 1].w;
// Normalize the weights to conserve energy.
for (int i = 0; i < SssConstants.SSS_N_SAMPLES_NEAR_FIELD; i++)
{
m_FilterKernelNearField[i].x *= 1.0f / weightSum.x;
m_FilterKernelNearField[i].y *= 1.0f / weightSum.y;
m_FilterKernelNearField[i].z *= 1.0f / weightSum.z;
}
// Store the radius of the disk kernel.
m_S.w = m_FilterKernelNearField[SssConstants.SSS_N_SAMPLES_NEAR_FIELD - 1].x;
public Vector3 shapeParameter
public Vector4 shapeParameter
public Vector4[] filterKernelNearField
public Vector2[] filterKernelNearField
public Vector4[] filterKernelFarField
public Vector2[] filterKernelFarField
{
// Set in BuildKernel().
get { return m_FilterKernelFarField; }

return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
}
// Same formula for s = 1 / d.
// Returns (2 * PI * r) * KernelVal(), which is useful for integration over a disk.
// Same formula for s = 1 / d.
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
// Same formula for s = 1 / d.
// Same formula for s = 1 / d.
// Same formula for s = 1 / d.
// Same formula for s = 1 / d.
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)));

// { f(r, s, p) = CDF(r, s) - p = 0 } with the initial guess { r = (10^p - 1) / s }.
// Same formula for s = 1 / d.
static float KernelCdfInverse(float p, float s)
{
// Supply the initial guess.

{
public int numProfiles; // Excluding the neutral profile
public SubsurfaceScatteringProfile[] profiles;
// Below is the cache filled during OnValidate().
// Below are the cached values.
[NonSerialized] public Vector4[] shapeParameters; // RGB: S = 1 / D; alpha is unused
[NonSerialized] public Vector4[] filterKernelsNearField; // RGB = weights, A = radial distance
[NonSerialized] public Vector4[] filterKernelsFarField; // RGB = weights, A = radial distance
[NonSerialized] public Vector4[] shapeParameters; // RGB = S = 1 / D, A = filter radius
[NonSerialized] public float[] filterKernelsNearField; // 0 = radius, 1 = reciprocal of the PDF
[NonSerialized] public float[] filterKernelsFarField; // 0 = radius, 1 = reciprocal of the PDF
// --- Public Methods ---

texturingModeFlags = transmissionFlags = 0;
if (thicknessRemaps == null || thicknessRemaps.Length != (SssConstants.SSS_N_PROFILES * 2))
const int thicknessRemapsLen = SssConstants.SSS_N_PROFILES * 2;
if (thicknessRemaps == null || thicknessRemaps.Length != thicknessRemapsLen)
thicknessRemaps = new float[SssConstants.SSS_N_PROFILES * 2];
thicknessRemaps = new float[thicknessRemapsLen];
if (shapeParameters == null || shapeParameters.Length != SssConstants.SSS_N_PROFILES)
const int shapeParametersLen = SssConstants.SSS_N_PROFILES;
if (shapeParameters == null || shapeParameters.Length != shapeParametersLen)
shapeParameters = new Vector4[SssConstants.SSS_N_PROFILES];
shapeParameters = new Vector4[shapeParametersLen];
if (filterKernelsNearField == null || filterKernelsNearField.Length != SssConstants.SSS_N_PROFILES)
const int filterKernelsNearFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD;
if (filterKernelsNearField == null || filterKernelsNearField.Length != filterKernelsNearFieldLen)
filterKernelsNearField = new Vector4[SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
filterKernelsNearField = new float[filterKernelsNearFieldLen];
if (filterKernelsFarField == null || filterKernelsFarField.Length != SssConstants.SSS_N_PROFILES)
const int filterKernelsFarFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_FAR_FIELD;
if (filterKernelsFarField == null || filterKernelsFarField.Length != filterKernelsFarFieldLen)
filterKernelsFarField = new Vector4[SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_FAR_FIELD];
filterKernelsFarField = new float[filterKernelsFarFieldLen];
}
for (int i = 0; i < numProfiles; i++)

for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernelsNearField[n * i + j] = profiles[i].filterKernelNearField[j];
filterKernelsNearField[2 * (n * i + j) + 0] = profiles[i].filterKernelNearField[j].x;
filterKernelsNearField[2 * (n * i + j) + 1] = profiles[i].filterKernelNearField[j].y;
filterKernelsFarField[n * i + j] = profiles[i].filterKernelFarField[j];
filterKernelsFarField[2 * (n * i + j) + 0] = profiles[i].filterKernelFarField[j].x;
filterKernelsFarField[2 * (n * i + j) + 1] = profiles[i].filterKernelFarField[j].y;
}
}

shapeParameters[i] = Vector4.zero; // Plays no role in this case (only used for bilateral filtering)
shapeParameters[i] = Vector4.zero;
filterKernelsNearField[n * i + j] = Vector3.one; // Radius = 0
filterKernelsNearField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsNearField[2 * (n * i + j) + 1] = 1.0f;
filterKernelsFarField[n * i + j] = Vector3.one; // Radius = 0
filterKernelsFarField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsFarField[2 * (n * i + j) + 1] = 1.0f;
}
}
}

float d = m_ScatteringDistance.floatValue;
Vector4 A = m_SurfaceAlbedo.colorValue;
Vector4 S = m_S.vector3Value;
Vector4 S = m_S.vector4Value;
Vector2 R = m_ThicknessRemap.vector2Value;
// Draw the profile.

正在加载...
取消
保存