浏览代码

Disney SSS WIP

/Branch_Batching2
Evgenii Golubev 7 年前
当前提交
162d8e01
共有 14 个文件被更改,包括 525 次插入526 次删除
  1. 6
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Editor/HDRenderPipelineInspector.cs
  2. 34
      Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
  3. 2
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Editor/LitUI.cs
  4. 60
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
  5. 177
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/CombineSubsurfaceScattering.shader
  6. 526
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs
  7. 21
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawTransmittanceGraph.shader
  8. 15
      Assets/ScriptableRenderPipeline/ShaderLibrary/BSDF.hlsl
  9. 18
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl
  10. 9
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl.meta
  11. 79
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader
  12. 9
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader.meta
  13. 9
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawGaussianProfile.shader.meta
  14. 86
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawGaussianProfile.shader

6
Assets/ScriptableRenderPipeline/HDRenderPipeline/Editor/HDRenderPipelineInspector.cs


public readonly GUIContent shadowsAtlasHeight = new GUIContent("Atlas height");
// Subsurface Scattering Settings
public readonly GUIContent[] sssProfiles = new GUIContent[SubsurfaceScatteringSettings.maxNumProfiles] { new GUIContent("Profile #0"), new GUIContent("Profile #1"), new GUIContent("Profile #2"), new GUIContent("Profile #3"), new GUIContent("Profile #4"), new GUIContent("Profile #5"), new GUIContent("Profile #6"), new GUIContent("Profile #7") };
public readonly GUIContent sssNumProfiles = new GUIContent("Number of profiles");
public readonly GUIContent[] sssProfiles = new GUIContent[SssConstants.SSS_N_PROFILES - 1] { new GUIContent("Profile #1"), new GUIContent("Profile #2"), new GUIContent("Profile #3"), new GUIContent("Profile #4"), new GUIContent("Profile #5"), new GUIContent("Profile #6"), new GUIContent("Profile #7") };
public readonly GUIContent sssNumProfiles = new GUIContent("Number of profiles");
// Tile pass Settings
public readonly GUIContent tileLightLoopSettings = new GUIContent("Tile Light Loop Settings");

EditorGUILayout.PropertyField(m_NumProfiles, styles.sssNumProfiles);
for (int i = 0, n = Math.Min(m_Profiles.arraySize, SubsurfaceScatteringSettings.maxNumProfiles); i < n; i++)
for (int i = 0, n = m_Profiles.arraySize; i < n; i++)
{
SerializedProperty profile = m_Profiles.GetArrayElementAtIndex(i);
EditorGUILayout.PropertyField(profile, styles.sssProfiles[i]);

34
Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs


readonly GBufferManager m_gbufferManager = new GBufferManager();
// Various set of material use in render loop
readonly Material m_FilterSubsurfaceScattering;
readonly Material m_FilterAndCombineSubsurfaceScattering;
private Material m_DebugDisplayShadowMap;

m_CameraSubsurfaceBufferRT = new RenderTargetIdentifier(m_CameraSubsurfaceBuffer);
m_CameraFilteringBufferRT = new RenderTargetIdentifier(m_CameraFilteringBuffer);
m_FilterSubsurfaceScattering = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/CombineSubsurfaceScattering");
m_FilterSubsurfaceScattering.DisableKeyword("SSS_FILTER_HORIZONTAL_AND_COMBINE");
m_FilterSubsurfaceScattering.SetFloat("_DstBlend", (float)BlendMode.Zero);
m_FilterAndCombineSubsurfaceScattering.EnableKeyword("SSS_FILTER_HORIZONTAL_AND_COMBINE");
m_FilterAndCombineSubsurfaceScattering.SetFloat("_DstBlend", (float)BlendMode.One);
InitializeDebugMaterials();

}
// Broadcast SSS parameters to all shaders.
Shader.SetGlobalInt("_EnableSSS", debugDisplaySettings.renderingDebugSettings.enableSSS ? 1 : 0);
Shader.SetGlobalInt("_TransmissionFlags", sssParameters.transmissionFlags);
Shader.SetGlobalInt("_TexturingModeFlags", sssParameters.texturingModeFlags);
cmd.SetGlobalFloatArray("_ThicknessRemaps", sssParameters.thicknessRemaps);
cmd.SetGlobalVectorArray("_TintColors", sssParameters.tintColors);
cmd.SetGlobalVectorArray("_HalfRcpVariancesAndLerpWeights", sssParameters.halfRcpVariancesAndLerpWeights);
Shader.SetGlobalInt( "_EnableSSS", debugDisplaySettings.renderingDebugSettings.enableSSS ? 1 : 0);
Shader.SetGlobalInt( "_TexturingModeFlags", (int)sssParameters.texturingModeFlags);
Shader.SetGlobalInt( "_TransmissionFlags", (int)sssParameters.transmissionFlags);
cmd.SetGlobalFloatArray( "_ThicknessRemaps", sssParameters.thicknessRemaps);
cmd.SetGlobalVectorArray("_ShapeParameters", sssParameters.shapeParameters);
renderContext.ExecuteCommandBuffer(cmd);
cmd.Dispose();

var cmd = new CommandBuffer() { name = "Subsurface Scattering" };
// Perform the vertical SSS filtering pass.
m_FilterSubsurfaceScattering.SetVectorArray("_FilterKernels", sssParameters.filterKernels);
m_FilterSubsurfaceScattering.SetVectorArray("_HalfRcpWeightedVariances", sssParameters.halfRcpWeightedVariances);
cmd.SetGlobalTexture("_IrradianceSource", m_CameraSubsurfaceBufferRT);
Utilities.DrawFullScreen(cmd, m_FilterSubsurfaceScattering, hdCamera,
m_CameraFilteringBufferRT, m_CameraDepthStencilBufferRT);
cmd.SetGlobalTexture("_IrradianceSource", m_CameraSubsurfaceBufferRT); // Cannot set a RT on a material
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_FilterKernelsNearField", sssParameters.filterKernelsNearField);
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_FilterKernelsFarField", sssParameters.filterKernelsFarField);
// Perform the horizontal SSS filtering pass, and combine diffuse and specular lighting.
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_FilterKernels", sssParameters.filterKernels);
m_FilterAndCombineSubsurfaceScattering.SetVectorArray("_HalfRcpWeightedVariances", sssParameters.halfRcpWeightedVariances);
cmd.SetGlobalTexture("_IrradianceSource", m_CameraFilteringBufferRT);
Utilities.DrawFullScreen(cmd, m_FilterAndCombineSubsurfaceScattering, hdCamera,
m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);
Utilities.DrawFullScreen(cmd, m_FilterAndCombineSubsurfaceScattering, hdCamera, m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);
context.ExecuteCommandBuffer(cmd);
cmd.Dispose();

2
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Editor/LitUI.cs


if (!validProfile)
{
// Disable SSS for this object.
material.SetInt("_SubsurfaceProfile", SubsurfaceScatteringSettings.neutralProfileID);
material.SetInt("_SubsurfaceProfile", SssConstants.SSS_NEUTRAL_PROFILE_ID);
}
m_MaterialEditor.ShaderProperty(subsurfaceRadius, Styles.subsurfaceRadiusText);

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


// SurfaceData is define in Lit.cs which generate Lit.cs.hlsl
#include "Lit.cs.hlsl"
#include "SubsurfaceScatteringProfile.cs.hlsl"
// In case we pack data uint16 buffer we need to change the output render target format to uint16
// TODO: Is there a way to automate these output type based on the format declare in lit.cs ?

#define LTC_LUT_SCALE ((LTC_LUT_SIZE - 1) * rcp(LTC_LUT_SIZE))
#define LTC_LUT_OFFSET (0.5 * rcp(LTC_LUT_SIZE))
#define MIN_N_DOT_V 0.0001 // The minimum value of 'NdotV'
#define MIN_N_DOT_V 0.0001 // The minimum value of 'NdotV'
// SSS parameters
#define SSS_N_PROFILES 8
#define SSS_N_SAMPLES 11
#define SSS_LOW_THICKNESS 0.005 // 0.5 cm
#define SSS_DISTANCE_SCALE 3 // SSS distance units per centimeter
#define CENTIMETERS_TO_METERS 0.01
uint _EnableSSS; // Globally toggles subsurface scattering on/off
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
uint _EnableSSS; // Globally toggles subsurface scattering on/off
uint _TransmissionFlags; // 1 bit/profile; 0 = inf. thick, 1 = supports transmission
uint _TexturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
float4 _TintColors[SSS_N_PROFILES]; // For transmission; alpha is unused
float _ThicknessRemaps[SSS_N_PROFILES][2]; // Remap: 0 = start, 1 = end - start
float4 _HalfRcpVariancesAndLerpWeights[SSS_N_PROFILES][2]; // 2x Gaussians per color channel, A is the the associated interpolation weight
#define SSS_LOW_THICKNESS 10000 // REMOVE
//-----------------------------------------------------------------------------
// Helper functions/variable specific to this material

// Thickness and SSS radius are decoupled for artists.
// In theory, we should modify the thickness by the inverse of the radius scale of the profile.
// thickness *= SSS_DISTANCE_SCALE / radiusScale;
thickness *= SSS_DISTANCE_SCALE / CENTIMETERS_TO_METERS;
float t2 = thickness * thickness;
// TODO: 6 exponentials is kind of expensive... Should we use a LUT instead?
// T = lerp(exp(-t2 * halfRcpVariance1), exp(-t2 * halfRcpVariance2), lerpWeight2)
float3 transmittance = exp(-t2 * halfRcpVariance1) * lerpWeight1
+ exp(-t2 * halfRcpVariance2) * lerpWeight2;
return transmittance * tintColor;
return float3(0, 0, 0);
}
void FillMaterialIdStandardData(float3 baseColor, float specular, float metallic, float roughness, float3 normalWS, float3 tangentWS, float anisotropy, inout BSDFData bsdfData)

// TODO take from subsurfaceProfile
bsdfData.fresnel0 = 0.04; // Should be 0.028 for the skin
bsdfData.subsurfaceProfile = subsurfaceProfile;
bsdfData.subsurfaceRadius = CENTIMETERS_TO_METERS * subsurfaceRadius + 0.0001;
bsdfData.thickness = CENTIMETERS_TO_METERS * (_ThicknessRemaps[subsurfaceProfile][0] +
_ThicknessRemaps[subsurfaceProfile][1] * thickness);
bsdfData.subsurfaceRadius = subsurfaceRadius;
bsdfData.thickness = _ThicknessRemaps[subsurfaceProfile][0] +
_ThicknessRemaps[subsurfaceProfile][1] * thickness;
bsdfData.enableTransmission = IsBitSet(_TransmissionFlags, subsurfaceProfile);
bsdfData.enableTransmission = false;
bsdfData.transmittance = ComputeTransmittance( _HalfRcpVariancesAndLerpWeights[subsurfaceProfile][0].xyz,
_HalfRcpVariancesAndLerpWeights[subsurfaceProfile][0].w,
_HalfRcpVariancesAndLerpWeights[subsurfaceProfile][1].xyz,
_HalfRcpVariancesAndLerpWeights[subsurfaceProfile][1].w,
_TintColors[subsurfaceProfile].rgb, bsdfData.thickness, bsdfData.subsurfaceRadius);
bsdfData.transmittance = 0;
#ifndef SSS_FILTER_HORIZONTAL_AND_COMBINE // When doing the SSS comine pass, we must not apply the modification of diffuse color
// Handle post-scatter, or pre- and post-scatter texturing.
// We modify diffuseColor here so it affect all the lighting + GI (lightprobe / lightmap) (Need to be done also in GBuffer pass) + transmittance
// diffuseColor will be solely use during lighting pass. The other contribution will be apply in subsurfacescattering convolution.
bsdfData.diffuseColor = performPostScatterTexturing ? float3(1.0, 1.0, 1.0) : sqrt(bsdfData.diffuseColor);
// We modify the albedo here as this code is used by all lighting (including light maps and GI).
if (performPostScatterTexturing)
{
#ifndef SSS_PASS
bsdfData.diffuseColor = float3(1.0, 1.0, 1.0);
}
else
{
bsdfData.diffuseColor = sqrt(bsdfData.diffuseColor);
}
}
//-----------------------------------------------------------------------------

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


Cull Off
ZTest Always
ZWrite Off
Blend One [_DstBlend]
Blend One One
// #pragma enable_d3d11_debug_symbols
#pragma enable_d3d11_debug_symbols
#pragma multi_compile _ SSS_FILTER_HORIZONTAL_AND_COMBINE
#define SSS_PASS
#define METERS_TO_MILLIMETERS 1000
//-------------------------------------------------------------------------------------
// Include

// Inputs & outputs
//-------------------------------------------------------------------------------------
float4 _FilterKernels[SSS_N_PROFILES][SSS_N_SAMPLES]; // RGB = weights, A = radial distance
float4 _HalfRcpWeightedVariances[SSS_N_PROFILES]; // RGB for chromatic, A for achromatic
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)
TEXTURE2D(_IrradianceSource); // RGB = irradiance on the back side of the object
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)
{
float3 S3 = S * (1.0 / 3.0); // Same for all samples
float iF = rcp(distScale); // Same for all samples
r *= iF; z *= iF;
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);
eR += eR * eR * eR;
float3 A = iRZ * (eRZ * eRZ * eRZ + eRZ);
float3 B = sR / (eR * eR * eR + eR ); // TODO: precompute
return A * B;
}
struct Attributes
{
uint vertexID : SV_VertexID;

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

float fragWidth = ddx_fine(centerPosVS.x);
float fragheight = ddy_fine(centerPosVS.y);
float stepSizeX = rcp(fragWidth);
float stepSizeY = rcp(fragheight);
// 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);
// Compute the filtering direction.
#ifdef SSS_FILTER_HORIZONTAL_AND_COMBINE
float stepSize = stepSizeX;
float2 unitDirection = float2(1, 0);
#else
float stepSize = stepSizeY;
float2 unitDirection = float2(0, 1);
#endif
bool useNearFieldKernel = true; // TODO
float scaledStepSize = distScale * stepSize;
float2 scaledDirection = scaledStepSize * unitDirection;
float phi = 0; // Random rotation; unused for now
float2x2 rotationMatrix = float2x2(cos(phi), -sin(phi), sin(phi), cos(phi));
float2 rotatedDirection = mul(rotationMatrix, scaledDirection);
if (useNearFieldKernel)
{
// Take the first (central) sample.
float2 samplePosition = posInput.unPositionSS;
float3 sampleWeight = _FilterKernelsNearField[profileID][0].rgb;
float3 sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// Load (1 / (2 * WeightedVariance)) for bilateral weighting.
#ifdef RBG_BILATERAL_WEIGHTS
float3 halfRcpVariance = _HalfRcpWeightedVariances[profileID].rgb;
#else
float halfRcpVariance = _HalfRcpWeightedVariances[profileID].a;
#endif
// 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;
#ifdef SSS_FILTER_HORIZONTAL_AND_COMBINE
bool performPostScatterTexturing = IsBitSet(_TexturingModeFlags, profileID);
[branch]
if (maxDistance * max(pixelsPerMillimeter.x, pixelsPerMillimeter.y) < 0.5)
{
return float4(bsdfData.diffuseColor * sampleIrradiance, 1);
}
// It's either post-scatter, or pre- and post-scatter texturing.
float3 albedoContrib = performPostScatterTexturing ? bsdfData.diffuseColor
: sqrt(bsdfData.diffuseColor);
#else
float3 albedoContrib = float3(1, 1, 1);
#endif
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance = sampleWeight * sampleIrradiance;
float3 totalWeight = sampleWeight;
// Take the first (central) sample.
float2 samplePosition = posInput.unPositionSS;
float3 sampleWeight = _FilterKernels[profileID][0].rgb;
float3 sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// We perform point sampling. Therefore, we can avoid the cost
// of filtering if we stay within the bounds of the current pixel.
float maxDistance = _FilterKernels[profileID][SSS_N_SAMPLES - 1].a;
[branch]
if (scaledStepSize * maxDistance < 0.5)
{
return float4(albedoContrib * sampleIrradiance, 1);
}
// Accumulate filtered irradiance.
float3 totalIrradiance = sampleWeight * sampleIrradiance;
// Perform integration over the screen-aligned plane in the view space.
// TODO: it would be more accurate to use the tangent plane in the world space.
[unroll]
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 phi = TWO_PI * VanDerCorputBase2(i);
float2 pos = r * float2(cos(phi), sin(phi));
// Make sure bilateral filtering does not cause energy loss.
// TODO: ask Morten if there is a better way to do this.
float3 totalWeight = sampleWeight;
samplePosition = posInput.unPositionSS + pos * pixelsPerMillimeter;
sampleWeight = _FilterKernelsNearField[profileID][i].rgb;
[unroll]
for (int i = 1; i < SSS_N_SAMPLES; i++)
{
samplePosition = posInput.unPositionSS + rotatedDirection * _FilterKernels[profileID][i].a;
sampleWeight = _FilterKernels[profileID][i].rgb;
rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, samplePosition).r;
sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, samplePosition).r;
sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// 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);
// Apply bilateral weighting.
// Ref #1: Skin Rendering by Pseudo–Separable Cross Bilateral Filtering.
// Ref #2: Separable SSS, Supplementary Materials, Section E.
float sampleDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
float zDistance = invDistScale * sampleDepth - (invDistScale * centerPosVS.z);
sampleWeight *= exp(-zDistance * zDistance * halfRcpVariance);
[flatten]
if (any(sampleIrradiance) == false)
{
// The irradiance is 0. This could happen for 2 reasons.
// Most likely, the surface fragment does not have an SSS material.
// Alternatively, the surface fragment could be completely shadowed.
// Our blur is energy-preserving, so 'sampleWeight' should be set to 0.
// We do not terminate the loop since we want to gather the contribution
// of the remaining samples (e.g. in case of hair covering skin).
continue;
}
if (any(sampleIrradiance) == false)
{
// The irradiance is 0. This could happen for 2 reasons.
// Most likely, the surface fragment does not have an SSS material.
// Alternatively, the surface fragment could be completely shadowed.
// Our blur is energy-preserving, so 'sampleWeight' should be set to 0.
// We do not terminate the loop since we want to gather the contribution
// of the remaining samples (e.g. in case of hair covering skin).
continue;
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;
return float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
}
else
{
return float4(0, 0, 0, 0); // TODO
return float4(albedoContrib * totalIrradiance / totalWeight, 1);
}
ENDHLSL
}

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


using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor;
[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 = 64; // Used for extreme close ups; must be a power of 2
public const int SSS_N_SAMPLES_FAR_FIELD = 32; // Used at a regular distance; must be a power of 2
public const int SSS_TRSM_MODE_NONE = 0;
public const int SSS_TRSM_MODE_THIN = 1;
}
public enum TexturingMode : int { PreAndPostScatter = 0, PostScatter = 1 };
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 };
public const int numSamples = 11; // Must be an odd number
public const float distanceScale = 3; // SSS distance units per centimeter
[ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
public Color scatterDistance1;
[ColorUsage(false, true, 0.05f, 2.0f, 1.0f, 1.0f)]
public Color scatterDistance2;
public float lerpWeight;
public TexturingMode texturingMode;
public bool enableTransmission;
public Color tintColor;
public Vector2 thicknessRemap;
public Color surfaceAlbedo; // Color, 0 to 1
public float scatteringDistance; // Effective radius (in millimeters)
public TexturingMode texturingMode;
public TransmissionMode transmissionMode;
public Vector2 thicknessRemap; // X = min, Y = max (in millimeters)
public int settingsIndex;
public int settingsIndex;
Vector4[] m_FilterKernel;
Vector3 m_S; // RGB shape parameter: S = 1 / D
Vector3[] m_HalfRcpVariances;
Vector4[] m_FilterKernelNearField; // RGB = weights, A = radial distance (in millimeters)
Vector4 m_HalfRcpWeightedVariances;
Vector4[] m_FilterKernelFarField; // RGB = weights, A = radial distance (in millimeters)
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;
surfaceAlbedo = Color.white;
scatteringDistance = 5.0f;
enableTransmission = false;
tintColor = Color.white;
thicknessRemap = new Vector2(0, 0.5f);
settingsIndex = SubsurfaceScatteringSettings.neutralProfileID; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
UpdateKernelAndVarianceData();
}
public Vector4[] filterKernel
{
// Set via UpdateKernelAndVarianceData().
get { return m_FilterKernel; }
}
public Vector3[] halfRcpVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpVariances; }
}
transmissionMode = TransmissionMode.None;
thicknessRemap = new Vector2(0.0f, 5.0f);
settingsIndex = SssConstants.SSS_NEUTRAL_PROFILE_ID; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
public Vector4 halfRcpWeightedVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpWeightedVariances; }
BuildKernel();
public void UpdateKernelAndVarianceData()
// Ref: Approximate Reflectance Profiles for Efficient Subsurface Scattering by Pixar.
public void BuildKernel()
if (m_FilterKernel == null || m_FilterKernel.Length != numSamples)
if (m_FilterKernelNearField == null || m_FilterKernelNearField.Length != SssConstants.SSS_N_SAMPLES_NEAR_FIELD)
m_FilterKernel = new Vector4[numSamples];
m_FilterKernelNearField = new Vector4[SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
if (m_HalfRcpVariances == null)
if (m_FilterKernelFarField == null || m_FilterKernelFarField.Length != SssConstants.SSS_N_SAMPLES_FAR_FIELD)
m_HalfRcpVariances = new Vector3[2];
m_FilterKernelFarField = new Vector4[SssConstants.SSS_N_SAMPLES_FAR_FIELD];
// Apply the three-sigma rule, and rescale.
Color stdDev1 = ((1.0f / 3.0f) * distanceScale) * scatterDistance1;
Color stdDev2 = ((1.0f / 3.0f) * distanceScale) * scatterDistance2;
m_S = new Vector3();
// 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.
// Evaluate the fit for diffuse surface transmission.
m_S.x = FindFitForS(surfaceAlbedo.r);
m_S.y = FindFitForS(surfaceAlbedo.g);
m_S.z = FindFitForS(surfaceAlbedo.b);
// Compute the length of the volume mean free path (in millimeters).
float L = 0.1f * scatteringDistance;
// 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).
// Compute { 1 / D = S / L } as you can substitute s = 1 / d in all formulas.
m_S.x *= 1.0f / L;
m_S.y *= 1.0f / L;
m_S.z *= 1.0f / L;
// 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);
// We importance sample the color channel with the highest albedo value,
// since higher albedo values result in scattering over a larger distance.
// S(A) is a monotonically decreasing function.
float s = Mathf.Min(m_S.x, m_S.y, m_S.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]
// ------------------------------------------------------------------------------------
float step = 1.0f / (numSamples - 1);
// Importance sample the linear combination of two Gaussians.
for (int i = 0; i < numSamples; i++)
// Importance sample the near field kernel.
for (int i = 0; i < SssConstants.SSS_N_SAMPLES_NEAR_FIELD; 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
float p = i * (1.0f / SssConstants.SSS_N_SAMPLES_NEAR_FIELD);
float r = KernelCdfInverse(p, s);
u = Mathf.Clamp(u, 0.001f, 0.999f);
float pos = GaussianCombinationCdfInverse(u, maxStdDev1, maxStdDev2, lerpWeight);
float pdf = GaussianCombination(pos, maxStdDev1, maxStdDev2, lerpWeight);
float pdf = KernelPdf(r, s);
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);
// 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 'numSamples' since we will renormalize, anyway.
m_FilterKernel[i].x = val.x * (1 / pdf);
m_FilterKernel[i].y = val.y * (1 / pdf);
m_FilterKernel[i].z = val.z * (1 / pdf);
m_FilterKernel[i].w = pos;
// 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_FilterKernel[i].x;
weightSum.y += m_FilterKernel[i].y;
weightSum.z += m_FilterKernel[i].z;
weightSum.x += m_FilterKernelNearField[i].x;
weightSum.y += m_FilterKernelNearField[i].y;
weightSum.z += m_FilterKernelNearField[i].z;
// Renormalize the weights to conserve energy.
for (int i = 0; i < numSamples; i++)
// 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_FilterKernel[i].x *= 1 / weightSum.x;
m_FilterKernel[i].y *= 1 / weightSum.y;
m_FilterKernel[i].z *= 1 / weightSum.z;
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 (1 / (2 * Variance)) per color channel per Gaussian.
m_HalfRcpVariances[0].x = 0.5f / (stdDev1.r * stdDev1.r);
m_HalfRcpVariances[0].y = 0.5f / (stdDev1.g * stdDev1.g);
m_HalfRcpVariances[0].z = 0.5f / (stdDev1.b * stdDev1.b);
m_HalfRcpVariances[1].x = 0.5f / (stdDev2.r * stdDev2.r);
m_HalfRcpVariances[1].y = 0.5f / (stdDev2.g * stdDev2.g);
m_HalfRcpVariances[1].z = 0.5f / (stdDev2.b * stdDev2.b);
// TODO: far field.
}
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);
public Vector3 shapeParameter
{
// Set in BuildKernel().
get { return m_S; }
}
// 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);
public Vector4[] filterKernelNearField
{
// Set in BuildKernel().
get { return m_FilterKernelNearField; }
}
public Vector4[] filterKernelFarField
{
// Set in BuildKernel().
get { return m_FilterKernelFarField; }
static float Gaussian(float x, float stdDev)
static float FindFitForS(float A)
float variance = stdDev * stdDev;
return Mathf.Exp(-x * x / (2 * variance)) / Mathf.Sqrt(2 * Mathf.PI * variance);
return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
static float GaussianCombination(float x, float stdDev1, float stdDev2, float lerpWeight)
// Same formula for s = 1 / d.
static float KernelVal(float r, float s)
return Mathf.Lerp(Gaussian(x, stdDev1), Gaussian(x, stdDev2), lerpWeight);
return s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f))) / (8.0f * Mathf.PI * r);
static float RationalApproximation(float t)
// Returns (2 * PI * r) * KernelVal(), which is useful for integration over a disk.
// Same formula for s = 1 / d.
static float KernelValCircle(float r, float s)
// 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);
return 0.25f * s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f)));
// Ref: https://www.johndcook.com/blog/csharp_phi_inverse/
static float NormalCdfInverse(float p, float stdDev)
// Same formula for s = 1 / d.
static float KernelPdf(float r, float s)
float x;
return KernelValCircle(r, s);
}
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)));
}
// Same formula for s = 1 / d.
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));
}
return x * stdDev;
// Same formula for s = 1 / d.
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 GaussianCombinationCdfInverse(float p, float stdDev1, float stdDev2, float lerpWeight)
// Same formula for s = 1 / d.
static float KernelCdfDerivative2(float r, float s)
return Mathf.Lerp(NormalCdfInverse(p, stdDev1), NormalCdfInverse(p, stdDev2), lerpWeight);
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 }.
// Same formula for s = 1 / d.
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;
}
}

public const int maxNumProfiles = 8;
public const int neutralProfileID = maxNumProfiles - 1;
public int numProfiles;
public int numProfiles; // Excluding the neutral profile
[NonSerialized] public int texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public int transmissionFlags; // 1 bit/profile; 0 = inf. thick, 1 = supports transmission
[NonSerialized] public Vector4[] tintColors; // For transmission; alpha is unused
[NonSerialized] public float[] thicknessRemaps; // Remap: 0 = start, 1 = end - start
[NonSerialized] public Vector4[] halfRcpVariancesAndLerpWeights;
[NonSerialized] public Vector4[] halfRcpWeightedVariances;
[NonSerialized] public Vector4[] filterKernels;
[NonSerialized] public uint texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public uint transmissionFlags; // 2 bit/profile; 0 = None, 1 = ThinObject, 2 = ThickObject
[NonSerialized] public float[] thicknessRemaps; // Remap: 0 = start, 1 = end - start
[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
numProfiles = 1;
profiles = new SubsurfaceScatteringProfile[numProfiles];
profiles[0] = null;
texturingModeFlags = 0;
transmissionFlags = 0;
tintColors = null;
thicknessRemaps = null;
halfRcpVariancesAndLerpWeights = null;
halfRcpWeightedVariances = null;
filterKernels = null;
numProfiles = 1;
profiles = new SubsurfaceScatteringProfile[numProfiles];
profiles[0] = null;
texturingModeFlags = 0;
transmissionFlags = 0;
thicknessRemaps = null;
shapeParameters = null;
filterKernelsNearField = null;
filterKernelsFarField = null;
UpdateCache();
}

// Reserve one slot for the neutral profile.
numProfiles = Math.Min(profiles.Length, maxNumProfiles - 1);
numProfiles = Math.Min(profiles.Length, SssConstants.SSS_N_PROFILES - 1);
if (profiles.Length != numProfiles)
{

}
}
Color c = new Color();
c.r = Mathf.Clamp(profiles[i].scatterDistance1.r, 0.05f, 2.0f);
c.g = Mathf.Clamp(profiles[i].scatterDistance1.g, 0.05f, 2.0f);
c.b = Mathf.Clamp(profiles[i].scatterDistance1.b, 0.05f, 2.0f);
c.a = 0.0f;
profiles[i].scatterDistance1 = c;
c.r = Mathf.Clamp(profiles[i].scatterDistance2.r, 0.05f, 2.0f);
c.g = Mathf.Clamp(profiles[i].scatterDistance2.g, 0.05f, 2.0f);
c.b = Mathf.Clamp(profiles[i].scatterDistance2.b, 0.05f, 2.0f);
c.a = 0.0f;
profiles[i].scatterDistance2 = c;
profiles[i].lerpWeight = Mathf.Clamp01(profiles[i].lerpWeight);
profiles[i].tintColor.r = Mathf.Clamp01(profiles[i].tintColor.r);
profiles[i].tintColor.g = Mathf.Clamp01(profiles[i].tintColor.g);
profiles[i].tintColor.b = Mathf.Clamp01(profiles[i].tintColor.b);
profiles[i].tintColor.a = 1.0f;
profiles[i].thicknessRemap.y = Mathf.Max(profiles[i].thicknessRemap.y, 0);
profiles[i].thicknessRemap.y = Mathf.Max(profiles[i].thicknessRemap.x, profiles[i].thicknessRemap.y);
profiles[i].UpdateKernelAndVarianceData();
profiles[i].BuildKernel();
}
UpdateCache();

{
texturingModeFlags = 0;
transmissionFlags = 0;
texturingModeFlags = transmissionFlags = 0;
if (tintColors == null || tintColors.Length != maxNumProfiles)
if (thicknessRemaps == null || thicknessRemaps.Length != (SssConstants.SSS_N_PROFILES * 2))
tintColors = new Vector4[maxNumProfiles];
thicknessRemaps = new float[SssConstants.SSS_N_PROFILES * 2];
if (thicknessRemaps == null || thicknessRemaps.Length != (maxNumProfiles * 2))
if (shapeParameters == null || shapeParameters.Length != SssConstants.SSS_N_PROFILES)
thicknessRemaps = new float[maxNumProfiles * 2];
}
if (halfRcpVariancesAndLerpWeights == null || halfRcpVariancesAndLerpWeights.Length != (maxNumProfiles * 2))
{
halfRcpVariancesAndLerpWeights = new Vector4[maxNumProfiles * 2];
shapeParameters = new Vector4[SssConstants.SSS_N_PROFILES];
if (halfRcpWeightedVariances == null || halfRcpWeightedVariances.Length != maxNumProfiles)
if (filterKernelsNearField == null || filterKernelsNearField.Length != SssConstants.SSS_N_PROFILES)
halfRcpWeightedVariances = new Vector4[maxNumProfiles];
filterKernelsNearField = new Vector4[SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD];
if (filterKernels == null || filterKernels.Length != (maxNumProfiles * SubsurfaceScatteringProfile.numSamples))
if (filterKernelsFarField == null || filterKernelsFarField.Length != SssConstants.SSS_N_PROFILES)
filterKernels = new Vector4[maxNumProfiles * SubsurfaceScatteringProfile.numSamples];
filterKernelsFarField = new Vector4[SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_FAR_FIELD];
}
for (int i = 0; i < numProfiles; i++)

texturingModeFlags |= ((int)profiles[i].texturingMode) << i;
transmissionFlags |= (profiles[i].enableTransmission ? 1 : 0) << i;
Debug.Assert(numProfiles < 16, "Transmission flags (32-bit integer) cannot support more than 16 profiles.");
texturingModeFlags |= (uint)profiles[i].texturingMode << i;
transmissionFlags |= (uint)profiles[i].transmissionMode << i * 2;
thicknessRemaps[2 * i] = profiles[i].thicknessRemap.x;
thicknessRemaps[2 * i + 1] = profiles[i].thicknessRemap.y - profiles[i].thicknessRemap.x;
shapeParameters[i] = profiles[i].shapeParameter;
tintColors[i] = profiles[i].tintColor;
thicknessRemaps[2 * i] = profiles[i].thicknessRemap.x;
thicknessRemaps[2 * i + 1] = profiles[i].thicknessRemap.y - profiles[i].thicknessRemap.x;
halfRcpVariancesAndLerpWeights[2 * i] = profiles[i].halfRcpVariances[0];
halfRcpVariancesAndLerpWeights[2 * i].w = 1.0f - profiles[i].lerpWeight;
halfRcpVariancesAndLerpWeights[2 * i + 1] = profiles[i].halfRcpVariances[1];
halfRcpVariancesAndLerpWeights[2 * i + 1].w = profiles[i].lerpWeight;
halfRcpWeightedVariances[i] = profiles[i].halfRcpWeightedVariances;
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernelsNearField[n * i + j] = profiles[i].filterKernelNearField[j];
}
for (int j = 0, n = SubsurfaceScatteringProfile.numSamples; j < n; j++)
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_FAR_FIELD; j < n; j++)
filterKernels[n * i + j] = profiles[i].filterKernel[j];
filterKernelsFarField[n * i + j] = profiles[i].filterKernelFarField[j];
int i = neutralProfileID;
int i = SssConstants.SSS_NEUTRAL_PROFILE_ID;
halfRcpWeightedVariances[i] = Vector4.one;
shapeParameters[i] = Vector4.zero; // Plays no role in this case (only used for bilateral filtering)
for (int j = 0, n = SubsurfaceScatteringProfile.numSamples; j < n; j++)
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
filterKernels[n * i + j] = Vector4.one;
filterKernels[n * i + j].w = 0.0f;
filterKernelsNearField[n * i + j] = Vector3.one; // Radius = 0
}
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_FAR_FIELD; j < n; j++)
{
filterKernelsFarField[n * i + j] = Vector3.one; // Radius = 0
}
}
}

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 as the radius increases to 1.");
public readonly GUIContent sssProfilePreview2 = new GUIContent("Note that the intensity of the region in the center may be clamped.");
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 Scattering Distance.");
public readonly GUIContent sssProfilePreview3 = new GUIContent("Note that the intensity of pixels around the center may be clipped.");
public readonly GUIContent sssTransmittancePreview2 = new GUIContent("Can be thought of as a cross section of a slab of material illuminated by a white light from the left.");
public readonly GUIContent sssProfileScatterDistance1 = new GUIContent("Scatter 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("Scatter 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.");
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 sssProfileSurfaceAlbedo = new GUIContent("Surface Albedo", "A color which determines the shape of the profile.");
public readonly GUIContent sssProfileScatterDistance = new GUIContent("Scattering Distance", "Determines the effective radius of the filter. 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 of the result.");
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).")
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 sssProfileTransmission = new GUIContent("Enable Transmission", "Toggles simulation of light passing through thin objects. Depends on the thickness of the material.");
public readonly GUIContent sssProfileTintColor = new GUIContent("Transmission Tint Color", "Tints transmitted light.");
public readonly GUIContent sssProfileMinMaxThickness = new GUIContent("Min-Max Thickness", "Shows the values of the thickness remap below (in centimeters).");
public readonly GUIContent sssProfileThicknessRemap = new GUIContent("Thickness Remap", "Remaps the thickness parameter from [0, 1] to the desired range (in centimeters).");
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 GUIStyle centeredMiniBoldLabel = new GUIStyle(GUI.skin.label);

private RenderTexture m_ProfileImage, m_TransmittanceImage;
private Material m_ProfileMaterial, m_TransmittanceMaterial;
private SerializedProperty m_ScatterDistance1, m_ScatterDistance2, m_LerpWeight, m_TintColor,
m_TexturingMode, m_Transmission, m_ThicknessRemap;
private SerializedProperty m_ScatteringDistance, m_SurfaceAlbedo, m_S,
m_TexturingMode, m_TransmissionMode, m_ThicknessRemap;
m_ScatterDistance1 = serializedObject.FindProperty("scatterDistance1");
m_ScatterDistance2 = serializedObject.FindProperty("scatterDistance2");
m_LerpWeight = serializedObject.FindProperty("lerpWeight");
m_TexturingMode = serializedObject.FindProperty("texturingMode");
m_Transmission = serializedObject.FindProperty("enableTransmission");
m_TintColor = serializedObject.FindProperty("tintColor");
m_ThicknessRemap = serializedObject.FindProperty("thicknessRemap");
m_SurfaceAlbedo = serializedObject.FindProperty("surfaceAlbedo");
m_ScatteringDistance = serializedObject.FindProperty("scatteringDistance");
m_S = serializedObject.FindProperty("m_S");
m_TexturingMode = serializedObject.FindProperty("texturingMode");
m_TransmissionMode = serializedObject.FindProperty("transmissionMode");
m_ThicknessRemap = serializedObject.FindProperty("thicknessRemap");
m_ProfileMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/DrawGaussianProfile");
m_ProfileMaterial = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/DrawSssProfile");
m_TransmittanceImage = new RenderTexture(16, 256, 0, RenderTextureFormat.DefaultHDR);
m_TransmittanceImage = new RenderTexture( 16, 256, 0, RenderTextureFormat.DefaultHDR);
}
public override void OnInspectorGUI()

EditorGUI.BeginChangeCheck();
{
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);
EditorGUILayout.PropertyField(m_Transmission, styles.sssProfileTransmission);
EditorGUILayout.PropertyField(m_TintColor, styles.sssProfileTintColor);
EditorGUILayout.PropertyField(m_SurfaceAlbedo, styles.sssProfileSurfaceAlbedo);
m_ScatteringDistance.floatValue = EditorGUILayout.Slider(styles.sssProfileScatterDistance, m_ScatteringDistance.floatValue, 0.1f, 10.0f);
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.MinMaxSlider(styles.sssProfileThicknessRemap, ref thicknessRemap.x, ref thicknessRemap.y, 0, 10);
EditorGUILayout.MinMaxSlider(styles.sssProfileThicknessRemap, ref thicknessRemap.x, ref thicknessRemap.y, 0.0f, 50.0f);
m_ThicknessRemap.vector2Value = thicknessRemap;
EditorGUILayout.Space();

EditorGUILayout.LabelField(styles.sssProfilePreview3, EditorStyles.centeredGreyMiniLabel);
// Apply the three-sigma rule, and rescale.
float s = (1.0f / 3.0f) * SubsurfaceScatteringProfile.distanceScale;
Vector4 stdDev1 = new Vector4(s * m_ScatterDistance1.colorValue.r, s * m_ScatterDistance1.colorValue.g, s * m_ScatterDistance1.colorValue.b);
Vector4 stdDev2 = new Vector4(s * m_ScatterDistance2.colorValue.r, s * m_ScatterDistance2.colorValue.g, s * m_ScatterDistance2.colorValue.b);
Vector4 tintCol = new Vector4(m_TintColor.colorValue.r, m_TintColor.colorValue.g, m_TintColor.colorValue.b);
float d = m_ScatteringDistance.floatValue;
Vector4 A = m_SurfaceAlbedo.colorValue;
Vector4 S = m_S.vector3Value;
Vector2 R = m_ThicknessRemap.vector2Value;
m_ProfileMaterial.SetVector("_StdDev1", stdDev1);
m_ProfileMaterial.SetVector("_StdDev2", stdDev2);
m_ProfileMaterial.SetFloat("_LerpWeight", m_LerpWeight.floatValue);
m_ProfileMaterial.SetFloat("_ScatteringDistance", d);
m_ProfileMaterial.SetVector("_SurfaceAlbedo", A);
m_ProfileMaterial.SetVector("_ShapeParameter", S);
EditorGUILayout.Space();
EditorGUILayout.LabelField(styles.sssTransmittancePreview0, styles.centeredMiniBoldLabel);
EditorGUILayout.LabelField(styles.sssTransmittancePreview1, EditorStyles.centeredGreyMiniLabel);

bool transmissionEnabled = m_TransmissionMode.intValue != (int)SubsurfaceScatteringProfile.TransmissionMode.None;
m_TransmittanceMaterial.SetVector("_StdDev1", stdDev1);
m_TransmittanceMaterial.SetVector("_StdDev2", stdDev2);
m_TransmittanceMaterial.SetFloat("_LerpWeight", m_LerpWeight.floatValue);
m_TransmittanceMaterial.SetVector("_ThicknessRemap", m_ThicknessRemap.vector2Value);
m_TransmittanceMaterial.SetVector("_TintColor", tintCol);
m_TransmittanceMaterial.SetFloat("_ScatteringDistance", d);
m_TransmittanceMaterial.SetVector("_SurfaceAlbedo", transmissionEnabled ? A : Vector4.zero);
m_TransmittanceMaterial.SetVector("_ShapeParameter", S);
m_TransmittanceMaterial.SetVector("_ThicknessRemap", R);
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(16, 16), m_TransmittanceImage, m_TransmittanceMaterial, ScaleMode.ScaleToFit, 16.0f);
serializedObject.ApplyModifiedProperties();

}
}
#endif
}
}

21
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawTransmittanceGraph.shader


// Inputs & outputs
//-------------------------------------------------------------------------------------
#define SSS_DISTANCE_SCALE 3 // SSS distance units per centimeter
float4 _StdDev1, _StdDev2, _ThicknessRemap, _TintColor;
float _LerpWeight; // See 'SubsurfaceScatteringParameters'
float4 _SurfaceAlbedo, _ShapeParameter, _ThicknessRemap;
float _ScatteringDistance; // See 'SubsurfaceScatteringProfile'
//-------------------------------------------------------------------------------------
// Implementation

float4 Frag(Varyings input) : SV_Target
{
float thickness = (_ThicknessRemap.x + input.texcoord.x * (_ThicknessRemap.y - _ThicknessRemap.x)) * SSS_DISTANCE_SCALE;
float t2 = thickness * thickness;
float d = (_ThicknessRemap.x + input.texcoord.x * (_ThicknessRemap.y - _ThicknessRemap.x));
float3 S = _ShapeParameter.rgb;
float3 T = 0.5 * exp(-d * S) + 0.5 * exp(-d * S * (1.0 / 3.0));
float3 var1 = _StdDev1.rgb * _StdDev1.rgb;
float3 var2 = _StdDev2.rgb * _StdDev2.rgb;
// See ComputeTransmittance() in Lit.hlsl for more details.
float3 transmittance = lerp(exp(-t2 * 0.5 * rcp(var1)),
exp(-t2 * 0.5 * rcp(var2)), _LerpWeight);
return float4(transmittance * _TintColor.rgb, 1);
// N.b.: we multiply by the surface albedo of the actual geometry during shading.
return float4(T * _SurfaceAlbedo.rgb, 1);
}
ENDHLSL
}

15
Assets/ScriptableRenderPipeline/ShaderLibrary/BSDF.hlsl


float x = 1.0 - u;
float x5 = x * x;
x5 = x5 * x5 * x;
return (float3(f90, f90, f90) - f0) * x5 + f0; // sub mul mul mul sub mad
return (float3(f90, f90, f90) - f0) * x5 + f0; // sub mul mul mul sub*3 mad*3
return F_Schlick(f0, 1.0, u);
float x = 1.0 - u;
float x5 = x * x;
x5 = x5 * x5 * x;
return f0 * (1.0 - x5) + float3(x5, x5, x5); // sub mul mul mul sub mad*3
}
float3 F_t_Schlick(float3 f0, float u)
{
float x = 1.0 - u;
float x2 = x * x;
float y = 1.0 - x2 * x2 * x;
return y - y * f0; // sub mul mul mad mad*3
}
//-----------------------------------------------------------------------------

18
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl


//
// This file was automatically generated from Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs. Please don't edit by hand.
//
#ifndef SUBSURFACESCATTERINGPROFILE_CS_HLSL
#define SUBSURFACESCATTERINGPROFILE_CS_HLSL
//
// UnityEngine.Experimental.Rendering.HDPipeline.SssConstants: static fields
//
#define SSS_N_PROFILES (8)
#define SSS_NEUTRAL_PROFILE_ID (7)
#define SSS_N_SAMPLES_NEAR_FIELD (64)
#define SSS_N_SAMPLES_FAR_FIELD (32)
#define SSS_TRSM_MODE_NONE (0)
#define SSS_TRSM_MODE_THIN (1)
#endif

9
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl.meta


fileFormatVersion: 2
guid: 5ecd8d3cc501ee64181689bfb64bba66
timeCreated: 1495110790
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

79
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader


Shader "Hidden/HDRenderPipeline/DrawSssProfile"
{
Properties
{
[HideInInspector] _StdDev1("", Vector) = (0, 0, 0, 0)
[HideInInspector] _StdDev2("", Vector) = (0, 0, 0, 0)
[HideInInspector] _LerpWeight("", Float) = 0
}
SubShader
{
Pass
{
Cull Off
ZTest Off
ZWrite Off
Blend Off
HLSLPROGRAM
#pragma target 4.5
#pragma only_renderers d3d11 ps4 metal // TEMP: until we go further in dev
#pragma vertex Vert
#pragma fragment Frag
//-------------------------------------------------------------------------------------
// Include
//-------------------------------------------------------------------------------------
#include "../../../ShaderLibrary/Common.hlsl"
#include "../../../ShaderLibrary/Color.hlsl"
#include "../../ShaderVariables.hlsl"
//-------------------------------------------------------------------------------------
// Inputs & outputs
//-------------------------------------------------------------------------------------
float4 _SurfaceAlbedo, _ShapeParameter;
float _ScatteringDistance; // See 'SubsurfaceScatteringProfile'
//-------------------------------------------------------------------------------------
// 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;
}
float4 Frag(Varyings input) : SV_Target
{
float r = (2 * length(input.texcoord - 0.5)) * _ScatteringDistance;
float3 S = _ShapeParameter.rgb;
float3 M = S * (exp(-r * S) + exp(-r * S * (1.0 / 3.0))) / (8 * PI * r);
// Apply gamma for visualization only. It is not present in the actual formula!
// N.b.: we multiply by the surface albedo of the actual geometry during shading.
return float4(pow(M * _SurfaceAlbedo.rgb, 1.0 / 3.0), 1);
}
ENDHLSL
}
}
Fallback Off
}

9
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader.meta


fileFormatVersion: 2
guid: 91d9e1751d0a17749819762ebf2b6846
timeCreated: 1494839797
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

9
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawGaussianProfile.shader.meta


fileFormatVersion: 2
guid: 2e8a76823cb2af944b4b45169f2649f9
timeCreated: 1487181006
licenseType: Pro
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

86
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawGaussianProfile.shader


Shader "Hidden/HDRenderPipeline/DrawGaussianProfile"
{
Properties
{
[HideInInspector] _StdDev1("", Vector) = (0, 0, 0, 0)
[HideInInspector] _StdDev2("", Vector) = (0, 0, 0, 0)
[HideInInspector] _LerpWeight("", Float) = 0
}
SubShader
{
Pass
{
Cull Off
ZTest Off
ZWrite Off
Blend Off
HLSLPROGRAM
#pragma target 4.5
#pragma only_renderers d3d11 ps4 metal // TEMP: until we go further in dev
#pragma vertex Vert
#pragma fragment Frag
//-------------------------------------------------------------------------------------
// Include
//-------------------------------------------------------------------------------------
#include "../../../ShaderLibrary/Common.hlsl"
#include "../../../ShaderLibrary/Color.hlsl"
#include "../../ShaderVariables.hlsl"
//-------------------------------------------------------------------------------------
// Inputs & outputs
//-------------------------------------------------------------------------------------
#define SSS_DISTANCE_SCALE 3 // SSS distance units per centimeter
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;
}
float4 Frag(Varyings input) : SV_Target
{
float dist = length(input.texcoord - 0.5) * SSS_DISTANCE_SCALE;
float3 var1 = _StdDev1.rgb * _StdDev1.rgb;
float3 var2 = _StdDev2.rgb * _StdDev2.rgb;
// Evaluate the linear combination of two 2D Gaussians instead of
// product of a linear combination of two normalized 1D Gaussians
// since we do not want to bother artists with the lack of radial symmetry.
float3 magnitude = lerp(exp(-dist * dist / (2 * var1)) / (TWO_PI * var1),
exp(-dist * dist / (2 * var2)) / (TWO_PI * var2), _LerpWeight);
return float4(magnitude, 1);
}
ENDHLSL
}
}
Fallback Off
}
正在加载...
取消
保存