浏览代码

Merge branch 'disney_sss'

/RenderPassXR_Sandbox
Evgenii Golubev 7 年前
当前提交
85e3c31c
共有 17 个文件被更改,包括 634 次插入493 次删除
  1. 8
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Editor/HDRenderPipelineInspector.cs
  2. 41
      Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
  3. 6
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Editor/LitUI.cs
  4. 5
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.cs
  5. 15
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.cs.hlsl
  6. 150
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
  7. 12
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/LitData.hlsl
  8. 177
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/CombineSubsurfaceScattering.shader
  9. 547
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs
  10. 9
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl
  11. 4
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs.hlsl.meta
  12. 25
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/DrawTransmittanceGraph.shader
  13. 34
      Assets/ScriptableRenderPipeline/ShaderLibrary/BSDF.hlsl
  14. 6
      Assets/ScriptableRenderPipeline/ShaderLibrary/Common.hlsl
  15. 79
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader
  16. 9
      Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/Resources/DrawSssProfile.shader.meta

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


public readonly GUIContent shadowsAtlasHeight = new GUIContent("Atlas height");
// Subsurface Scattering Settings
public readonly GUIContent[] sssProfiles = new GUIContent[SSSConstants.SSS_PROFILES_MAX] { 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"), new GUIContent("Profile #8"), new GUIContent("Profile #9"), new GUIContent("Profile #10"),
new GUIContent("Profile #11"), new GUIContent("Profile #12"), new GUIContent("Profile #13"), new GUIContent("Profile #14"), new GUIContent("Profile #15") };
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, SSSConstants.SSS_PROFILES_MAX); 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]);

41
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_DebugViewMaterialGBuffer;

// 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_FilterSubsurfaceScattering = Utilities.CreateEngineMaterial("Hidden/HDRenderPipeline/CombineSubsurfaceScattering");
m_FilterSubsurfaceScattering.DisableKeyword("SSS_FILTER_HORIZONTAL_AND_COMBINE");
m_FilterSubsurfaceScattering.SetFloat("_DstBlend", (float)BlendMode.Zero);
m_CameraColorBufferRT = new RenderTargetIdentifier(m_CameraColorBuffer);
m_CameraSubsurfaceBufferRT = new RenderTargetIdentifier(m_CameraSubsurfaceBuffer);
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);
cmd.SetGlobalFloatArray("_TransmissionType", sssParameters.transmissionType);
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);
cmd.SetGlobalVectorArray("_SurfaceAlbedos", sssParameters.surfaceAlbedos);
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);
// when recombining the lighting, we apply albedo. This need to be modified in case of debug display with diffuse lighting only.
Utilities.SetKeyword(m_FilterAndCombineSubsurfaceScattering, "DEBUG_DISPLAY", debugDisplaySettings.IsDebugDisplayEnabled());
cmd.SetGlobalTexture("_IrradianceSource", m_CameraSubsurfaceBufferRT); // Cannot set a RT on a material
m_FilterAndCombineSubsurfaceScattering.SetFloatArray("_WorldScales", sssParameters.worldScales);
m_FilterAndCombineSubsurfaceScattering.SetFloatArray("_FilterKernelsNearField", sssParameters.filterKernelsNearField);
m_FilterAndCombineSubsurfaceScattering.SetFloatArray("_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);
context.ExecuteCommandBuffer(cmd);

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


// Load the profile from the GUI field.
int profileID = subsurfaceProfile.settingsIndex;
if (0 <= profileID && profileID < hdPipeline.sssSettings.profiles.Length)
if (0 <= profileID && profileID < hdPipeline.sssSettings.profiles.Length &&
hdPipeline.sssSettings.profiles[profileID] != null &&
hdPipeline.sssSettings.profiles[profileID] == subsurfaceProfile)
{
validProfile = true;
material.SetInt("_SubsurfaceProfile", profileID);

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);

5
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.cs


public float subsurfaceRadius;
public float thickness;
public int subsurfaceProfile;
public TransmissionType transmissionType; // Compute from the SSS profile. 0 is none, 1 is regular transmission, 2 is thin transmission
public Vector3 transmittance; // Compute from SSS profile
public bool enableTransmission; // Read from the SSS profile
public bool useThinObjectMode; // Read from the SSS profile
public Vector3 transmittance;
// SpecColor
// fold into fresnel0

15
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.cs.hlsl


#define DEBUGVIEW_LIT_BSDFDATA_SUBSURFACE_RADIUS (1042)
#define DEBUGVIEW_LIT_BSDFDATA_THICKNESS (1043)
#define DEBUGVIEW_LIT_BSDFDATA_SUBSURFACE_PROFILE (1044)
#define DEBUGVIEW_LIT_BSDFDATA_TRANSMISSION_TYPE (1045)
#define DEBUGVIEW_LIT_BSDFDATA_TRANSMITTANCE (1046)
#define DEBUGVIEW_LIT_BSDFDATA_ENABLE_TRANSMISSION (1045)
#define DEBUGVIEW_LIT_BSDFDATA_USE_THIN_OBJECT_MODE (1046)
#define DEBUGVIEW_LIT_BSDFDATA_TRANSMITTANCE (1047)
//
// UnityEngine.Experimental.Rendering.HDPipeline.Lit.GBufferMaterial: static fields

float subsurfaceRadius;
float thickness;
int subsurfaceProfile;
int transmissionType;
bool enableTransmission;
bool useThinObjectMode;
float3 transmittance;
};

case DEBUGVIEW_LIT_BSDFDATA_SUBSURFACE_PROFILE:
result = GetIndexColor(bsdfdata.subsurfaceProfile);
break;
case DEBUGVIEW_LIT_BSDFDATA_TRANSMISSION_TYPE:
result = GetIndexColor(bsdfdata.transmissionType);
case DEBUGVIEW_LIT_BSDFDATA_ENABLE_TRANSMISSION:
result = (bsdfdata.enableTransmission) ? float3(1.0, 1.0, 1.0) : float3(0.0, 0.0, 0.0);
break;
case DEBUGVIEW_LIT_BSDFDATA_USE_THIN_OBJECT_MODE:
result = (bsdfdata.useThinObjectMode) ? float3(1.0, 1.0, 1.0) : float3(0.0, 0.0, 0.0);
break;
case DEBUGVIEW_LIT_BSDFDATA_TRANSMITTANCE:
result = bsdfdata.transmittance;

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


#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 CENTIMETERS_TO_METERS 0.01
uint _EnableSSS; // Globally toggles subsurface scattering on/off
uint _TexturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
float _TransmissionType[SSS_PROFILES_MAX]; // transmissionType enum - TODO: no int array in Unity :(
float4 _TintColors[SSS_PROFILES_MAX]; // For transmission; alpha is unused
float _ThicknessRemaps[SSS_PROFILES_MAX][2]; // Remap: 0 = start, 1 = end - start
float4 _HalfRcpVariancesAndLerpWeights[SSS_PROFILES_MAX][2]; // 2x Gaussians per color channel, A is the the associated interpolation weight
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; A = filter radius
float4 _SurfaceAlbedos[SSS_N_PROFILES]; // RGB = color, A = unused
//-----------------------------------------------------------------------------
// Helper functions/variable specific to this material

#endif
}
// Evaluates transmittance for a linear combination of two normalized 2D Gaussians.
// Computes results for each color channel separately.
// Ref: Real-Time Realistic Skin Translucency (2010), equation 9 (modified).
float3 ComputeTransmittance(float3 halfRcpVariance1, float lerpWeight1,
float3 halfRcpVariance2, float lerpWeight2,
float3 tintColor, float thickness, float radiusScale)
// Computes the fraction of light passing through the object.
// N.b.: it is not just zero scattering (light traveling in a straight path)!
// Ref: Approximate Reflectance Profiles for Efficient Subsurface Scattering by Pixar (BSSRDF only).
float3 ComputeTransmittance(float3 S, float3 surfaceAlbedo, float thickness, float radiusScale)
thickness /= CENTIMETERS_TO_METERS;
float t2 = thickness * thickness;
float3 expOneThird = exp(((-1.0 / 3.0) * thickness) * S);
// 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 0.5 * (expOneThird + expOneThird * expOneThird * expOneThird) * surfaceAlbedo;
}
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.transmissionType = (int)_TransmissionType[subsurfaceProfile];
uint transmissionMode = BitFieldExtract(_TransmissionFlags, 2, 2 * subsurfaceProfile);
if (bsdfData.transmissionType != TRANSMISSIONTYPE_NONE)
bsdfData.enableTransmission = transmissionMode != SSS_TRSM_MODE_NONE;
bsdfData.useThinObjectMode = transmissionMode == SSS_TRSM_MODE_THIN;
if (bsdfData.enableTransmission)
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 = ComputeTransmittance(_ShapeParameters[subsurfaceProfile].rgb,
_SurfaceAlbedos[subsurfaceProfile].rgb,
bsdfData.thickness, bsdfData.subsurfaceRadius);
#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);
}
}
//-----------------------------------------------------------------------------

}
else if (surfaceData.materialId == MATERIALID_LIT_SSS)
{
outGBuffer2 = float4(surfaceData.subsurfaceRadius, surfaceData.thickness, 0.0, surfaceData.subsurfaceProfile * rcp(SSS_PROFILES_MAX - 1));
// Use 16 bits to encode the thickness, and up to 8 bits to encode the profile ID.
// We need a lot of precision to minimize banding of NdotV-weighted thickness.
int ip = surfaceData.subsurfaceProfile; // [0, 255]
float ft = surfaceData.thickness; // [0, 1]
int it = int(ft * ((1 << 16) - 1)); // [0, 65535]
int lo = it & ((1 << 8) - 1); // 8 bit low
int hi = (it >> 8) & ((1 << 8) - 1); // 8 bit high
float y = saturate(lo * rcp((1 << 8) - 1));
float z = saturate(hi * rcp((1 << 8) - 1));
float w = saturate(ip * rcp((1 << 8) - 1));
outGBuffer2 = float4(surfaceData.subsurfaceRadius, y, z, w);
}
else if (surfaceData.materialId == MATERIALID_LIT_SPECULAR)
{

}
else if (supportsSSS && bsdfData.materialId == MATERIALID_LIT_SSS)
{
int subsurfaceProfile = (SSS_PROFILES_MAX - 0.9) * inGBuffer2.a;
float thickness = inGBuffer2.g;
float y = inGBuffer2.g;
float z = inGBuffer2.b;
float w = inGBuffer2.a;
// Use 16 bits to encode the thickness, and up to 8 bits to encode the profile ID.
// We need a lot of precision to minimize banding of NdotV-weighted thickness.
int lo = int(y * ((1 << 8) - 1)); // 8 bit low
int hi = int(z * ((1 << 8) - 1)); // 8 bit high
int ip = int(w * ((1 << 8) - 1)); // [0, 255]
int it = lo + (hi << 8); // [0, 65535]
float ft = saturate(it * rcp((1 << 16) - 1)); // [0, 1]
float thickness = ft;
int subsurfaceProfile = ip;
FillMaterialIdSSSData(baseColor, subsurfaceProfile, subsurfaceRadius, thickness, bsdfData);
}
else if (supportsSpecular && bsdfData.materialId == MATERIALID_LIT_SPECULAR)

specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
}
[branch] if (bsdfData.transmissionType != TRANSMISSIONTYPE_NONE)
[branch] if (bsdfData.enableTransmission)
// Reverse the normal + do some wrap lighting to have a nicer transition between regular lighting and transmittance
// Ref: Steve McAuley - Energy-Conserving Wrapped Diffuse
const float w = 0.15;
float illuminance = saturate((dot(-bsdfData.normalWS, L) + w) / ((1.0 + w) * (1.0 + w)));
float LdotV = dot(L, V); // Also computed in BSDF()
// Compute the normal at the back of the object as R = reflect(N, -V)
// float RdotL = NdotL - 2 * preLightData.NdotV * LdotV;
float illuminance = saturate(-LdotV);
// For thin material we can reuse the shadowing status for the back of the object.
shadow = (bsdfData.transmissionType == TRANSMISSIONTYPE_THIN_OBJECT) ? shadow : 1;
// For low thickness, we can reuse the shadowing status for the back of the object.
shadow = bsdfData.useThinObjectMode ? shadow : 1;
illuminance *= shadow * cookie.a;
// The difference between the Disney Diffuse and the Lambertian BRDF for transmission is negligible.

specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
}
[branch] if (bsdfData.transmissionType != TRANSMISSIONTYPE_NONE)
[branch] if (bsdfData.enableTransmission)
// Reverse the normal + do some wrap lighting to have a nicer transition between regular lighting and transmittance
// Ref: Steve McAuley - Energy-Conserving Wrapped Diffuse
const float w = 0.15;
float illuminance = saturate((dot(-bsdfData.normalWS, L) + w) / ((1.0 + w) * (1.0 + w)));
illuminance *= attenuation;
float LdotV = dot(L, V); // Also computed in BSDF()
// Compute the normal at the back of the object as R = reflect(N, -V)
// float RdotL = NdotL - 2 * preLightData.NdotV * LdotV;
float illuminance = saturate(-LdotV * attenuation);
// For thin material we can reuse the shadowing status for the back of the object.
shadow = (bsdfData.transmissionType == TRANSMISSIONTYPE_THIN_OBJECT) ? shadow : 1;
// For low thickness, we can reuse the shadowing status for the back of the object.
shadow = bsdfData.useThinObjectMode ? shadow : 1;
illuminance *= shadow * cookie.a;
// The difference between the Disney Diffuse and the Lambertian BRDF for transmission is negligible.

specularLighting *= (cookie.rgb * lightData.color) * (illuminance * lightData.specularScale);
}
[branch] if (bsdfData.transmissionType != TRANSMISSIONTYPE_NONE)
[branch] if (bsdfData.enableTransmission)
// Reverse the normal + do some wrap lighting to have a nicer transition between regular lighting and transmittance
// Ref: Steve McAuley - Energy-Conserving Wrapped Diffuse
const float w = 0.15;
float illuminance = saturate((dot(-bsdfData.normalWS, L) + w) / ((1.0 + w) * (1.0 + w)));
illuminance *= clipFactor;
float LdotV = dot(L, V); // Also computed in BSDF()
// Compute the normal at the back of the object as R = reflect(N, -V)
// float RdotL = NdotL - 2 * preLightData.NdotV * LdotV;
float illuminance = saturate(-LdotV * clipFactor);
// For thin material we can reuse the shadowing status for the back of the object.
shadow = (bsdfData.transmissionType == TRANSMISSIONTYPE_THIN_OBJECT) ? shadow : 1;
// For low thickness, we can reuse the shadowing status for the back of the object.
shadow = bsdfData.useThinObjectMode ? shadow : 1;
illuminance *= shadow * cookie.a;
// The difference between the Disney Diffuse and the Lambertian BRDF for transmission is negligible.

12
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/LitData.hlsl


ApplyDepthOffsetPositionInput(V, depthOffset, GetWorldToHClipMatrix(), posInput);
#endif
float3 interpolatedVertexNormal = input.worldToTangent[2].xyz;
// We perform the conversion to world of the normalTS outside of the GetSurfaceData
// so it allow us to correctly deal with detail normal map and optimize the code for the layered shaders
float3 normalTS;

surfaceData.specularOcclusion *= GetHorizonOcclusion(V, surfaceData.normalWS, input.worldToTangent[2].xyz, _HorizonFade);
surfaceData.specularOcclusion *= GetHorizonOcclusion(V, surfaceData.normalWS, interpolatedVertexNormal, _HorizonFade);
uint transmissionMode = BitFieldExtract(_TransmissionFlags, 2, 2 * surfaceData.subsurfaceProfile);
if (transmissionMode != SSS_TRSM_MODE_THIN)
{
// Convert thickness along the normal to thickness along the viewing direction.
surfaceData.thickness *= saturate(dot(interpolatedVertexNormal, V) + 0.01);
}
// Caution: surfaceData must be fully initialize before calling GetBuiltinData
GetBuiltinData(input, surfaceData, alpha, depthOffset, builtinData);

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
#pragma multi_compile _ DEBUG_DISPLAY
#define SSS_PASS
#define MILLIMETERS_PER_METER 1000
//-------------------------------------------------------------------------------------
// Include

#include "../../../Debug/DebugDisplay.hlsl"
#include "../../../ShaderConfig.cs.hlsl"
#include "../../../ShaderVariables.hlsl"
#define UNITY_MATERIAL_LIT // Needs to be defined before including Material.hlsl

// Inputs & outputs
//-------------------------------------------------------------------------------------
#define N_PROFILES 8
#define N_SAMPLES 11
float _WorldScales[SSS_N_PROFILES]; // Size of the world unit in meters
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
float4 _FilterKernels[N_PROFILES][N_SAMPLES]; // RGB = weights, A = radial distance
float4 _HalfRcpWeightedVariances[N_PROFILES]; // RGB for chromatic, A for achromatic
TEXTURE2D(_IrradianceSource); // RGB = irradiance on the back side of the object
DECLARE_GBUFFER_TEXTURE(_GBufferTexture); // Contains the albedo and SSS parameters
TEXTURE2D(_IrradianceSource); // Includes transmitted light
DECLARE_GBUFFER_TEXTURE(_GBufferTexture); // Contains the albedo and SSS parameters
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
// N.b.: the returned value is multiplied by 4. It is irrelevant due to weight renormalization.
float3 KernelValCircle(float r, float3 S)
{
float3 expOneThird = exp(((-1.0 / 3.0) * r) * S);
return /* 0.25 * */ S * (expOneThird + expOneThird * expOneThird * expOneThird);
}
// Computes F(x)/P(x), s.t. x = sqrt(r^2 + z^2).
float3 ComputeBilateralWeight(float3 S, float r, float z, float rcpDistScale, float rcpPdf)
{
// Reducing the integration distance is equivalent to stretching the integration axis.
float3 valX = KernelValCircle(sqrt(r * r + z * z) * rcpDistScale, S);
// 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);
return valX * rcpPdfX;
}
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;
float invDistScale = rcp(distScale);
int profileID = bsdfData.subsurfaceProfile;
float distScale = bsdfData.subsurfaceRadius;
float3 shapeParam = _ShapeParameters[profileID].rgb;
float maxDistance = _ShapeParameters[profileID].a;
float2 cornerPosSS = posInput.positionSS + 0.5 * _ScreenSize.zw;
// Compute the dimensions of the surface fragment viewed as a quad facing the camera.
float fragWidth = ddx_fine(centerPosVS.x);
float fragheight = ddy_fine(centerPosVS.y);
float stepSizeX = rcp(fragWidth);
float stepSizeY = rcp(fragheight);
// 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
float2 scaledDirection = distScale * stepSize * 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);
float3 cornerPosVS = ComputeViewSpacePosition(cornerPosSS, rawDepth, _InvProjMatrix);
// Load (1 / (2 * WeightedVariance)) for bilateral weighting.
#ifdef RBG_BILATERAL_WEIGHTS
float3 halfRcpVariance = _HalfRcpWeightedVariances[profileID].rgb;
#else
float halfRcpVariance = _HalfRcpWeightedVariances[profileID].a;
#endif
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
float2 unitsPerPixel = 2 * (cornerPosVS.xy - centerPosVS.xy);
float metersPerUnit = _WorldScales[profileID];
float millimPerUnit = MILLIMETERS_PER_METER * metersPerUnit;
float2 scaledPixPerMm = distScale * rcp(millimPerUnit * unitsPerPixel);
float3 sampleWeight = _FilterKernels[profileID][0].rgb;
// Accumulate filtered irradiance.
float3 totalIrradiance = sampleWeight * sampleIrradiance;
// 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)
{
return float4(bsdfData.diffuseColor * sampleIrradiance, 1);
}
// Make sure bilateral filtering does not cause energy loss.
// TODO: ask Morten if there is a better way to do this.
float3 totalWeight = sampleWeight;
bool useNearFieldKernel = true; // TODO
[unroll]
for (int i = 1; i < N_SAMPLES; i++)
if (useNearFieldKernel)
samplePosition = posInput.unPositionSS + rotatedDirection * _FilterKernels[profileID][i].a;
sampleWeight = _FilterKernels[profileID][i].rgb;
float sampleRcpPdf = _FilterKernelsNearField[profileID][0][1];
float3 sampleWeight = KernelValCircle(0, shapeParam) * sampleRcpPdf;
rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, samplePosition).r;
sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance = sampleWeight * sampleIrradiance;
float3 totalWeight = sampleWeight;
// 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);
// 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.
if (any(sampleIrradiance) == false)
[unroll]
for (uint i = 1; i < SSS_N_SAMPLES_NEAR_FIELD; i++)
// 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;
}
// Everything except for the radius is a compile-time constant.
float r = _FilterKernelsNearField[profileID][i][0];
float phi = TWO_PI * VanDerCorputBase2(i);
float2 pos = r * float2(cos(phi), sin(phi));
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;
}
samplePosition = posInput.unPositionSS + pos * scaledPixPerMm;
sampleRcpPdf = _FilterKernelsNearField[profileID][i][1];
#ifdef SSS_FILTER_HORIZONTAL_AND_COMBINE
bool performPostScatterTexturing = IsBitSet(_TexturingModeFlags, profileID);
rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, samplePosition).r;
sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// It's either post-scatter, or pre- and post-scatter texturing.
float3 diffuseContrib = performPostScatterTexturing ? bsdfData.diffuseColor
: sqrt(bsdfData.diffuseColor);
return float4(diffuseContrib * totalIrradiance / totalWeight, 1.0);
#else
return float4(totalIrradiance / totalWeight, 1.0);
#endif
[flatten]
if (any(sampleIrradiance) == false)
{
// The irradiance is 0. This could happen for 3 reasons.
// Most likely, the surface fragment does not have an SSS material.
// Alternatively, our sample comes from a region without any geometry.
// Finally, 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;
}
// Apply bilateral weighting.
float sampleZ = LinearEyeDepth(rawDepth, _ZBufferParams);
float z = millimPerUnit * sampleZ - (millimPerUnit * centerPosVS.z);
sampleWeight = ComputeBilateralWeight(shapeParam, r, z, rcp(distScale), sampleRcpPdf);
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;
}
return float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
}
else
{
return float4(0, 0, 0, 1); // TODO
}
}
ENDHLSL
}

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


using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor;
public class SSSConstants
public class SssConstants
public const int SSS_PROFILES_MAX = 8;
public const int SSS_N_PROFILES = 16; // 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
[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 Color surfaceAlbedo; // Color, 0 to 1
public float lenVolMeanFreePath; // Length of the volume mean free path (in millimeters)
public bool enableTransmission;
public bool enableThinObject;
public Color tintColor;
public Vector2 thicknessRemap;
public TransmissionMode transmissionMode;
public Vector2 thicknessRemap; // X = min, Y = max (in millimeters)
public float worldScale; // Size of the world unit in meters
public int settingsIndex;
public int settingsIndex; // SubsurfaceScatteringSettings.profiles[i]
Vector4[] m_FilterKernel;
Vector3 m_S; // RGB = shape parameter: S = 1 / D
[SerializeField]
float m_ScatteringDistance; // Filter radius (in millimeters)
Vector3[] m_HalfRcpVariances;
Vector2[] m_FilterKernelNearField; // X = radius, Y = reciprocal of the PDF
Vector4 m_HalfRcpWeightedVariances;
Vector2[] m_FilterKernelFarField; // X = radius, Y = reciprocal of the PDF
scatterDistance1 = new Color(0.3f, 0.3f, 0.3f, 0.0f);
scatterDistance2 = new Color(0.6f, 0.6f, 0.6f, 0.0f);
lerpWeight = 0.5f;
texturingMode = TexturingMode.PreAndPostScatter;
enableTransmission = false;
enableThinObject = false;
tintColor = Color.white;
thicknessRemap = new Vector2(0, 1);
settingsIndex = SubsurfaceScatteringSettings.neutralProfileID; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
UpdateKernelAndVarianceData();
}
surfaceAlbedo = Color.white;
lenVolMeanFreePath = 0.5f;
texturingMode = TexturingMode.PreAndPostScatter;
transmissionMode = TransmissionMode.None;
thicknessRemap = new Vector2(0.0f, 5.0f);
worldScale = 1.0f;
settingsIndex = SssConstants.SSS_NEUTRAL_PROFILE_ID; // Updated by SubsurfaceScatteringSettings.OnValidate() once assigned
public Vector4[] filterKernel
{
// Set via UpdateKernelAndVarianceData().
get { return m_FilterKernel; }
BuildKernel();
public Vector3[] halfRcpVariances
// Ref: Approximate Reflectance Profiles for Efficient Subsurface Scattering by Pixar.
public void BuildKernel()
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpVariances; }
}
public Vector4 halfRcpWeightedVariances
{
// Set via UpdateKernelAndVarianceData().
get { return m_HalfRcpWeightedVariances; }
}
public void UpdateKernelAndVarianceData()
{
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 Vector2[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 Vector2[SssConstants.SSS_N_SAMPLES_FAR_FIELD];
// Apply the three-sigma rule.
Color stdDev1 = scatterDistance1 * (1.0f / 3.0f);
Color stdDev2 = scatterDistance2 * (1.0f / 3.0f);
// 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.
m_S = new Vector3();
// 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);
// 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);
Vector3 weightSum = new Vector3(0, 0, 0);
// Compute { 1 / D = S / L } as you can substitute s = 1 / d in all formulas.
m_S.x *= 1.0f / lenVolMeanFreePath;
m_S.y *= 1.0f / lenVolMeanFreePath;
m_S.z *= 1.0f / lenVolMeanFreePath;
float rcpNumSamples = 1.0f / numSamples;
// 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 linear combination of two Gaussians.
for (uint i = 0; i < numSamples; i++)
// 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]
// ------------------------------------------------------------------------------------
// Importance sample the near field kernel.
for (int i = 0; i < SssConstants.SSS_N_SAMPLES_NEAR_FIELD; i++)
float u = (i <= numSamples / 2) ? 0.5f - i * rcpNumSamples // The center and to the left
: (i + 0.5f) * rcpNumSamples; // From the center to the right
float p = i * (1.0f / SssConstants.SSS_N_SAMPLES_NEAR_FIELD);
float r = KernelCdfInverse(p, s);
// 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);
}
float pos = GaussianCombinationCdfInverse(u, maxStdDev1, maxStdDev2, lerpWeight);
float pdf = GaussianCombination(pos, maxStdDev1, maxStdDev2, lerpWeight);
m_ScatteringDistance = m_FilterKernelNearField[SssConstants.SSS_N_SAMPLES_NEAR_FIELD - 1].x;
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);
// TODO: far field.
}
public Vector3 shapeParameter
{
// Set in BuildKernel().
get { return m_S; }
}
// 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;
public float scatteringDistance
{
// Set in BuildKernel().
get { return m_ScatteringDistance; }
}
weightSum.x += m_FilterKernel[i].x;
weightSum.y += m_FilterKernel[i].y;
weightSum.z += m_FilterKernel[i].z;
}
public Vector2[] filterKernelNearField
{
// Set in BuildKernel().
get { return m_FilterKernelNearField; }
}
public Vector2[] filterKernelFarField
{
// Set in BuildKernel().
get { return m_FilterKernelFarField; }
}
// Renormalize the weights to conserve energy.
for (uint i = 0; i < numSamples; i++)
{
m_FilterKernel[i].x *= 1 / weightSum.x;
m_FilterKernel[i].y *= 1 / weightSum.y;
m_FilterKernel[i].z *= 1 / weightSum.z;
}
// --- Private Methods ---
// 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);
static float FindFitForS(float A)
{
return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
}
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);
static float KernelVal(float r, float s)
{
return s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f))) / (8.0f * Mathf.PI * r);
}
// 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);
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
static float KernelValCircle(float r, float s)
{
return 0.25f * s * (Mathf.Exp(-r * s) + Mathf.Exp(-r * s * (1.0f / 3.0f)));
// --- Private Methods ---
static float KernelPdf(float r, float s)
{
return KernelValCircle(r, s);
}
static float Gaussian(float x, float stdDev)
static float KernelCdf(float r, float s)
float variance = stdDev * stdDev;
return Mathf.Exp(-x * x / (2 * variance)) / Mathf.Sqrt(2 * Mathf.PI * variance);
return 1.0f - 0.25f * Mathf.Exp(-r * s) - 0.75f * Mathf.Exp(-r * s * (1.0f / 3.0f));
static float GaussianCombination(float x, float stdDev1, float stdDev2, float lerpWeight)
static float KernelCdfDerivative1(float r, float s)
return Mathf.Lerp(Gaussian(x, stdDev1), Gaussian(x, stdDev2), lerpWeight);
return 0.25f * s * Mathf.Exp(-r * s) * (1.0f + Mathf.Exp(r * s * (2.0f / 3.0f)));
static float RationalApproximation(float t)
static float KernelCdfDerivative2(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 (-1.0f / 12.0f) * s * s * Mathf.Exp(-r * s) * (3.0f + Mathf.Exp(r * s * (2.0f / 3.0f)));
// Ref: https://www.johndcook.com/blog/csharp_phi_inverse/
static float NormalCdfInverse(float p, float stdDev)
// 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 }.
static float KernelCdfInverse(float p, float s)
float x;
// Supply the initial guess.
float r = (Mathf.Pow(10.0f, p) - 1.0f) / s;
float t = float.MaxValue;
if (p < 0.5)
{
// F^-1(p) = - G^-1(p)
x = -RationalApproximation(Mathf.Sqrt(-2.0f * Mathf.Log(p)));
}
else
while (true)
// F^-1(p) = G^-1(1-p)
x = RationalApproximation(Mathf.Sqrt(-2.0f * Mathf.Log(1.0f - p)));
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 x * stdDev;
}
static float GaussianCombinationCdfInverse(float p, float stdDev1, float stdDev2, float lerpWeight)
{
return Mathf.Lerp(NormalCdfInverse(p, stdDev1), NormalCdfInverse(p, stdDev2), lerpWeight);
return r;
}
}

public const int neutralProfileID = SSSConstants.SSS_PROFILES_MAX - 1;
public int numProfiles;
public int numProfiles; // Excluding the neutral profile
// Below is the cache filled during OnValidate().
[NonSerialized] public int texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public float[] transmissionType; // TODO: no int array suppport in shader in Unity :(
[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;
// Below are the cached values.
[NonSerialized] public uint texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public uint transmissionFlags; // 2 bit/profile; 0 = inf. thick, 1 = thin, 2 = regular
[NonSerialized] public float[] thicknessRemaps; // Remap: 0 = start, 1 = end - start
[NonSerialized] public Vector4[] shapeParameters; // RGB = S = 1 / D, A = filter radius
[NonSerialized] public Vector4[] surfaceAlbedos; // RGB = color, A = unused
[NonSerialized] public float[] worldScales; // Size of the world unit in meters
[NonSerialized] public float[] filterKernelsNearField; // 0 = radius, 1 = reciprocal of the PDF
[NonSerialized] public float[] filterKernelsFarField; // 0 = radius, 1 = reciprocal of the PDF
numProfiles = 1;
profiles = new SubsurfaceScatteringProfile[numProfiles];
profiles[0] = null;
transmissionType = null;
texturingModeFlags = 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, SSSConstants.SSS_PROFILES_MAX - 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].worldScale = Mathf.Max(profiles[i].worldScale, 0.001f);
profiles[i].UpdateKernelAndVarianceData();
profiles[i].BuildKernel();
}
UpdateCache();

{
texturingModeFlags = 0;
texturingModeFlags = transmissionFlags = 0;
if (transmissionType == null || transmissionType.Length != (SSSConstants.SSS_PROFILES_MAX))
const int thicknessRemapsLen = SssConstants.SSS_N_PROFILES * 2;
if (thicknessRemaps == null || thicknessRemaps.Length != thicknessRemapsLen)
transmissionType = new float[SSSConstants.SSS_PROFILES_MAX];
thicknessRemaps = new float[thicknessRemapsLen];
if (tintColors == null || tintColors.Length != SSSConstants.SSS_PROFILES_MAX)
const int worldScalesLen = SssConstants.SSS_N_PROFILES;
if (worldScales == null || worldScales.Length != worldScalesLen)
tintColors = new Vector4[SSSConstants.SSS_PROFILES_MAX];
worldScales = new float[worldScalesLen];
if (thicknessRemaps == null || thicknessRemaps.Length != (SSSConstants.SSS_PROFILES_MAX * 2))
const int shapeParametersLen = SssConstants.SSS_N_PROFILES;
if (shapeParameters == null || shapeParameters.Length != shapeParametersLen)
thicknessRemaps = new float[SSSConstants.SSS_PROFILES_MAX * 2];
shapeParameters = new Vector4[shapeParametersLen];
if (halfRcpVariancesAndLerpWeights == null || halfRcpVariancesAndLerpWeights.Length != (SSSConstants.SSS_PROFILES_MAX * 2))
const int surfaceAlbedosLen = SssConstants.SSS_N_PROFILES;
if (surfaceAlbedos == null || surfaceAlbedos.Length != surfaceAlbedosLen)
halfRcpVariancesAndLerpWeights = new Vector4[SSSConstants.SSS_PROFILES_MAX * 2];
surfaceAlbedos = new Vector4[surfaceAlbedosLen];
if (halfRcpWeightedVariances == null || halfRcpWeightedVariances.Length != SSSConstants.SSS_PROFILES_MAX)
const int filterKernelsNearFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD;
if (filterKernelsNearField == null || filterKernelsNearField.Length != filterKernelsNearFieldLen)
halfRcpWeightedVariances = new Vector4[SSSConstants.SSS_PROFILES_MAX];
filterKernelsNearField = new float[filterKernelsNearFieldLen];
if (filterKernels == null || filterKernels.Length != (SSSConstants.SSS_PROFILES_MAX * SubsurfaceScatteringProfile.numSamples))
const int filterKernelsFarFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_FAR_FIELD;
if (filterKernelsFarField == null || filterKernelsFarField.Length != filterKernelsFarFieldLen)
filterKernels = new Vector4[SSSConstants.SSS_PROFILES_MAX * SubsurfaceScatteringProfile.numSamples];
filterKernelsFarField = new float[filterKernelsFarFieldLen];
}
for (int i = 0; i < numProfiles; i++)

texturingModeFlags |= ((int)profiles[i].texturingMode) << i;
if (profiles[i].enableTransmission)
{
transmissionType[i] = (float)(profiles[i].enableThinObject ? Lit.TransmissionType.ThinObject :Lit.TransmissionType.Regular);
}
else
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;
worldScales[i] = profiles[i].worldScale;
shapeParameters[i] = profiles[i].shapeParameter;
shapeParameters[i].w = profiles[i].scatteringDistance;
surfaceAlbedos[i] = profiles[i].surfaceAlbedo;
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
transmissionType[i] = (float)Lit.TransmissionType.None;
filterKernelsNearField[2 * (n * i + j) + 0] = profiles[i].filterKernelNearField[j].x;
filterKernelsNearField[2 * (n * i + j) + 1] = profiles[i].filterKernelNearField[j].y;
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 = 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[2 * (n * i + j) + 0] = profiles[i].filterKernelFarField[j].x;
filterKernelsFarField[2 * (n * i + j) + 1] = profiles[i].filterKernelFarField[j].y;
int i = neutralProfileID;
int i = SssConstants.SSS_NEUTRAL_PROFILE_ID;
shapeParameters[i] = Vector4.zero;
surfaceAlbedos[i] = Vector4.zero;
worldScales[i] = 1.0f;
halfRcpWeightedVariances[i] = Vector4.one;
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_NEAR_FIELD; j < n; j++)
{
filterKernelsNearField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsNearField[2 * (n * i + j) + 1] = 1.0f;
}
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] = Vector4.one;
filterKernels[n * i + j].w = 0.0f;
filterKernelsFarField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsFarField[2 * (n * i + j) + 1] = 1.0f;
}
}
}

{
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 sssTransmittancePreview0 = new GUIContent("Transmittance Preview");
public readonly GUIContent sssTransmittancePreview1 = new GUIContent("Shows the fraction of light passing through the object for thickness values from the remap.");
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 sssTexturingMode = new GUIContent("Texturing Mode", "Specifies when the diffuse texture should be applied.");
public readonly GUIContent[] sssTexturingModeOptions = new GUIContent[2]
public readonly GUIContent sssProfilePreview0 = new GUIContent("Profile Preview");
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 sssTransmittancePreview0 = new GUIContent("Transmittance Preview");
public readonly GUIContent sssTransmittancePreview1 = new GUIContent("Shows the fraction of light passing through the object for thickness values from the remap.");
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", "Color which determines the shape of the profile. Alpha is ignored.");
public readonly GUIContent sssProfileLenVolMeanFreePath = new GUIContent("Volume Mean Free Path", "The length of the volume mean free path (in millimeters) describes the average distance a photon travels within the volume before an extinction event occurs. Determines the effective radius of the filter.");
public readonly GUIContent sssProfileScatteringDistance = new GUIContent("Scattering Distance", "Effective radius of the filter (in millimeters). The blur is energy-preserving, so a wide filter results in a large area with small contributions of individual samples. Reducing the distance increases the sharpness of the result.");
public readonly GUIContent sssTexturingMode = new GUIContent("Texturing Mode", "Specifies when the diffuse texture should be applied.");
public readonly GUIContent[] sssTexturingModeOptions = new GUIContent[2]
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 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 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 sssProfileThinObject = new GUIContent("Enable Thin Object", "Define is the object is thin (paper, leaf) or not. Allow to get cheap transmission and shadow.");
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 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 GUIContent sssProfileWorldScale = new GUIContent("World Scale", "Size of the world unit in meters.");
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_ThinObject,
m_TexturingMode, m_Transmission, m_ThicknessRemap;
private SerializedProperty m_LenVolMeanFreePath, m_ScatteringDistance, m_SurfaceAlbedo, m_S,
m_TexturingMode, m_TransmissionMode, m_ThicknessRemap, m_WorldScale;
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_ThinObject = serializedObject.FindProperty("enableThinObject");
m_TintColor = serializedObject.FindProperty("tintColor");
m_ThicknessRemap = serializedObject.FindProperty("thicknessRemap");
m_SurfaceAlbedo = serializedObject.FindProperty("surfaceAlbedo");
m_LenVolMeanFreePath = serializedObject.FindProperty("lenVolMeanFreePath");
m_ScatteringDistance = serializedObject.FindProperty("m_ScatteringDistance");
m_S = serializedObject.FindProperty("m_S");
m_TexturingMode = serializedObject.FindProperty("texturingMode");
m_TransmissionMode = serializedObject.FindProperty("transmissionMode");
m_ThicknessRemap = serializedObject.FindProperty("thicknessRemap");
m_WorldScale = serializedObject.FindProperty("worldScale");
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_LenVolMeanFreePath.floatValue = EditorGUILayout.Slider(styles.sssProfileLenVolMeanFreePath, m_LenVolMeanFreePath.floatValue, 0.01f, 1.0f);
GUI.enabled = false;
EditorGUILayout.PropertyField(m_ScatteringDistance, styles.sssProfileScatteringDistance);
GUI.enabled = true;
EditorGUILayout.PropertyField(m_ThinObject, styles.sssProfileThinObject);
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);
EditorGUILayout.PropertyField(m_WorldScale, styles.sssProfileWorldScale);
EditorGUILayout.LabelField(styles.sssProfilePreview3, EditorStyles.centeredGreyMiniLabel);
// Apply the three-sigma rule.
Color stdDev1 = m_ScatterDistance1.colorValue * (1.0f / 3.0f);
Color stdDev2 = m_ScatterDistance2.colorValue * (1.0f / 3.0f);
float d = m_ScatteringDistance.floatValue;
Vector4 A = m_SurfaceAlbedo.colorValue;
Vector3 S = m_S.vector3Value;
Vector2 R = m_ThicknessRemap.vector2Value;
m_ProfileMaterial.SetColor("_StdDev1", stdDev1);
m_ProfileMaterial.SetColor("_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.SetColor("_StdDev1", stdDev1);
m_TransmittanceMaterial.SetColor("_StdDev2", stdDev2);
m_TransmittanceMaterial.SetFloat("_LerpWeight", m_LerpWeight.floatValue);
m_TransmittanceMaterial.SetVector("_ThicknessRemap", m_ThicknessRemap.vector2Value);
m_TransmittanceMaterial.SetVector("_TintColor", m_TintColor.colorValue);
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
}
}

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


#ifndef SUBSURFACESCATTERINGPROFILE_CS_HLSL
#define SUBSURFACESCATTERINGPROFILE_CS_HLSL
//
// UnityEngine.Experimental.Rendering.HDPipeline.SSSConstants: static fields
// UnityEngine.Experimental.Rendering.HDPipeline.SssConstants: static fields
#define SSS_PROFILES_MAX (8)
#define SSS_N_PROFILES (16)
#define SSS_NEUTRAL_PROFILE_ID (15)
#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

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


fileFormatVersion: 2
guid: 0348d78eb59f5e143ab4aa357c26f2c4
timeCreated: 1494501874
guid: 5ecd8d3cc501ee64181689bfb64bba66
timeCreated: 1495110790
licenseType: Pro
ShaderImporter:
defaultTextures: []

25
Assets/ScriptableRenderPipeline/HDRenderPipeline/SceneSettings/DrawTransmittanceGraph.shader


{
Properties
{
[HideInInspector] _StdDev1("", Color) = (0, 0, 0)
[HideInInspector] _StdDev2("", Color) = (0, 0, 0)
[HideInInspector] _StdDev1("", Vector) = (0, 0, 0, 0)
[HideInInspector] _StdDev2("", Vector) = (0, 0, 0, 0)
[HideInInspector] _ThicknessScale("", Float) = 0
[HideInInspector] _TintColor("", Color) = (0, 0, 0)
[HideInInspector] _TintColor("", Vector) = (0, 0, 0, 0)
}
SubShader

// Inputs & outputs
//-------------------------------------------------------------------------------------
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);
float t2 = thickness * thickness;
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);
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));
return float4(transmittance * _TintColor.rgb, 1);
return float4(T * _SurfaceAlbedo.rgb, 1);
}
ENDHLSL
}

34
Assets/ScriptableRenderPipeline/ShaderLibrary/BSDF.hlsl


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

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
}
//-----------------------------------------------------------------------------

{
float facing = 0.5 + 0.5 * LdotV;
float rough = facing * (0.9 - 0.4 * facing) * ((0.5 + NdotH) / NdotH);
float transmitL = 1 - F_Schlick(0, 1, NdotL);
float transmitV = 1 - F_Schlick(0, 1, NdotV);
float transmitL = F_t_Schlick(0, NdotL);
float transmitV = F_t_Schlick(0, NdotV);
float smooth = transmitL * transmitV * 1.05; // Normalize F_t over the hemisphere
float single = lerp(smooth, rough, perceptualRoughness); // Rescaled by PI
// This constant is picked s.t. setting perceptualRoughness, albedo and all angles to 1

6
Assets/ScriptableRenderPipeline/ShaderLibrary/Common.hlsl


// unsigned integer bit field extract implementation
uint BitFieldExtract(uint data, uint size, uint offset)
{
return (data >> offset) & ((1u << size) - 1u);
return (data >> offset) & ((1 << size) - 1);
bool IsBitSet(uint number, uint bitPos)
bool IsBitSet(uint data, uint bitPos)
return ((number >> bitPos) & 1) != 0;
return BitFieldExtract(data, 1, bitPos) != 0;
}
#ifndef INTRINSIC_CLAMP

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:
正在加载...
取消
保存