Files
UnrealEngine/Engine/Shaders/Private/SubsurfaceProfileCommon.ush
2025-05-18 13:04:45 +08:00

195 lines
8.6 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
SubsurfaceProfileCommon.ush: Subsurface scattering parameter lookup / decoding constants.
=============================================================================================*/
#pragma once
///////////////////////////////////////////////////////////////////////////////////////////////
// Describe SSS profile texture (data offsets & size)
// NOTE: Changing offsets below requires updating all instances of #SSSS_CONSTANTS
// TODO: This needs to be defined in a single place and shared between C++ and shaders!
#define SSSS_TINT_SCALE_OFFSET 0
#define BSSS_SURFACEALBEDO_OFFSET (SSSS_TINT_SCALE_OFFSET+1)
#define BSSS_DMFP_OFFSET (BSSS_SURFACEALBEDO_OFFSET+1)
#define SSSS_TRANSMISSION_OFFSET (BSSS_DMFP_OFFSET+1)
#define SSSS_BOUNDARY_COLOR_BLEED_OFFSET (SSSS_TRANSMISSION_OFFSET+1)
#define SSSS_DUAL_SPECULAR_OFFSET (SSSS_BOUNDARY_COLOR_BLEED_OFFSET+1)
#define SSSS_KERNEL0_OFFSET (SSSS_DUAL_SPECULAR_OFFSET+1)
#define SSSS_KERNEL0_SIZE 13
#define SSSS_KERNEL1_OFFSET (SSSS_KERNEL0_OFFSET + SSSS_KERNEL0_SIZE)
#define SSSS_KERNEL1_SIZE 9
#define SSSS_KERNEL2_OFFSET (SSSS_KERNEL1_OFFSET + SSSS_KERNEL1_SIZE)
#define SSSS_KERNEL2_SIZE 6
#define SSSS_KERNEL_TOTAL_SIZE (SSSS_KERNEL0_SIZE + SSSS_KERNEL1_SIZE + SSSS_KERNEL2_SIZE)
#define BSSS_TRANSMISSION_PROFILE_OFFSET (SSSS_KERNEL0_OFFSET + SSSS_KERNEL_TOTAL_SIZE)
#define BSSS_TRANSMISSION_PROFILE_SIZE 32
#define SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE 5.0f // See MaxTransmissionProfileDistance in ComputeTransmissionProfile(), SeparableSSS.cpp
#define SSSS_MAX_DUAL_SPECULAR_ROUGHNESS 2.0f
// Threshold value at which model switches from SSS to default lit
#define SSSS_OPACITY_THRESHOLD_EPS 0.10
///////////////////////////////////////////////////////////////////////////////////////////////
// Burley constants
#define BURLEY_MM_2_CM 0.1f
#define BURLEY_CM_2_MM 10.0f
// exactly one of these should be true
#define LIGHT_PERPENDICULAR 0
#define LIGHT_DIFFUSESURFACE 0
#define LIGHT_PERPENDICULAR_DIFFUSE_SURFACE 1
// Consistent in BurleyNormalizedSSSCommon.ush and SubsurfaceProfile.cpp
#define SSS_TYPE_BURLEY 0
#define SSS_TYPE_SSSS 1
// Make sure UIMax|ClampMax of WorldUNitScale * ENC_WORLDUNITSCALE_IN_CM_TO_UNIT <= 1
#define ENC_WORLDUNITSCALE_IN_CM_TO_UNIT 0.02f
#define DEC_UNIT_TO_WORLDUNITSCALE_IN_CM 1/ENC_WORLDUNITSCALE_IN_CM_TO_UNIT
// Make sure UIMax|ClampMax of DiffuseMeanFreePath * ENC_DIFFUSEMEANFREEPATH_IN_MM_TO_UNIT <= 1
#define ENC_DIFFUSEMEANFREEPATH_IN_MM_TO_UNIT (0.01f*0.2f)
#define DEC_UNIT_TO_DIFFUSEMEANFREEPATH_IN_MM 1/ENC_DIFFUSEMEANFREEPATH_IN_MM_TO_UNIT
// Make sure UIMax|ClampMax of ExtinctionScale * ENC_EXTINCTIONSCALE_FACTOR <= 1
#define ENC_EXTINCTIONSCALE_FACTOR 0.01f
#define DEC_EXTINCTIONSCALE_FACTOR 1/ENC_EXTINCTIONSCALE_FACTOR
///////////////////////////////////////////////////////////////////////////////////////////////
// Accessors
half4 GetSubsurfaceProfileTexture(uint SampleIndex, uint SubsurfaceProfileInt)
{
// One profile per line, encoded using constants above. See FSubsurfaceProfileTexture::CreateTexture() in SubsurfaceProfile.cpp.
#if COMPILER_GLSL_ES3_1 // Force to use a point sampler for Texture2D.Load on OpenGLES platform
return View.SSProfilesTexture.SampleLevel(View.SSProfilesSampler, (uint2(SampleIndex, SubsurfaceProfileInt) + float2(0.5f, 0.5f)) * View.SSProfilesTextureSizeAndInvSize.zw, 0);
#else
return View.SSProfilesTexture.Load(int3(SampleIndex, SubsurfaceProfileInt, 0));
#endif
}
half4 GetSubsurfaceProfileTexture(Texture2D InSSProfilesTexture, SamplerState InSSProfilesSampler, float4 InSSProfilesTextureSizeAndInvSize, uint SampleIndex, uint SubsurfaceProfileInt)
{
// One profile per line, encoded using constants above. See FSubsurfaceProfileTexture::CreateTexture() in SubsurfaceProfile.cpp.
#if COMPILER_GLSL_ES3_1 // Force to use a point sampler for Texture2D.Load on OpenGLES platform
return InSSProfilesTexture.SampleLevel(InSSProfilesSampler, (uint2(SampleIndex, SubsurfaceProfileInt) + float2(0.5f, 0.5f)) * InSSProfilesTextureSizeAndInvSize.zw, 0);
#else
return InSSProfilesTexture.Load(int3(SampleIndex, SubsurfaceProfileInt, 0));
#endif
}
float GetSubsurfaceProfileOriginalRoughness(uint SubsurfaceProfileInt, half ModifiedLobeRoughness0, half Opacity)
{
const half4 Data = GetSubsurfaceProfileTexture(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileInt);
// Smooth blend out dual specular when opacity is low, we have the extra SSSS_OPACITY_THRESHOLD_EPS so that we fade out by the time we
// get to 0.01, as opposed to 0.0.
half MaterialRoughnessToLobeRoughness0 = lerp(1.0f, Data.x * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS, saturate((Opacity - SSSS_OPACITY_THRESHOLD_EPS) * 10.0f));
return ModifiedLobeRoughness0 / max(MaterialRoughnessToLobeRoughness0, 0.02f);
}
void GetSubsurfaceProfileDualSpecular(uint SubsurfaceProfileInt, half Roughness, half Opacity, out half LobeRoughness0, out half LobeRoughness1, out half LobeMix)
{
const half4 Data = GetSubsurfaceProfileTexture(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileInt);
// Smooth blend out dual specular when opacity is low, we have the extra SSSS_OPACITY_THRESHOLD_EPS so that we fade out by the time we
// get to 0.01, as opposed to 0.0.
half MaterialRoughnessToLobeRoughness0 = lerp(1.0f, Data.x * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS, saturate((Opacity - SSSS_OPACITY_THRESHOLD_EPS) * 10.0f));
half MaterialRoughnessToLobeRoughness1 = lerp(1.0f, Data.y * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS, saturate((Opacity - SSSS_OPACITY_THRESHOLD_EPS) * 10.0f));
LobeMix = Data.z;
// Avoid specular explosion, and approximate `area lights`.
LobeRoughness0 = max(saturate(Roughness * MaterialRoughnessToLobeRoughness0), 0.02f);
LobeRoughness1 = saturate(Roughness * MaterialRoughnessToLobeRoughness1);
}
// Surface albedo and mean free path length
float4 GetSubsurfaceProfileSurfaceAlbedo(uint SubsurfaceProfileInt)
{
return GetSubsurfaceProfileTexture(BSSS_SURFACEALBEDO_OFFSET, SubsurfaceProfileInt);
}
float4 GetSubsurfaceProfileDiffuseMeanFreePath(uint SubsurfaceProfileInt)
{
return GetSubsurfaceProfileTexture(BSSS_DMFP_OFFSET, SubsurfaceProfileInt);
}
float GetSubsurfaceProfileWorldUnitScale(uint SubsurfaceProfileInt)
{
return GetSubsurfaceProfileTexture(SSSS_TINT_SCALE_OFFSET, SubsurfaceProfileInt).a;
}
float DecodeWorldUnitScale(float EncodedWorldUnitScale)
{
return EncodedWorldUnitScale * DEC_UNIT_TO_WORLDUNITSCALE_IN_CM;
}
float4 DecodeDiffuseMeanFreePath(float4 EncodedDiffuseMeanFreePath)
{
return EncodedDiffuseMeanFreePath * DEC_UNIT_TO_DIFFUSEMEANFREEPATH_IN_MM;
}
float EncodeScatteringDistribution(float ScatteringDistribution)
{
return (ScatteringDistribution + 1.0f) * 0.5f;
}
float DecodeScatteringDistribution(float ScatteringDistribution)
{
return ScatteringDistribution * 2.0f - 1.0f;
}
float EncodeExtinctionScale(float ExtinctionScale)
{
return ExtinctionScale * ENC_EXTINCTIONSCALE_FACTOR;
}
float DecodeExtinctionScale(float ExtinctionScale)
{
return ExtinctionScale * DEC_EXTINCTIONSCALE_FACTOR;
}
bool GetSubsurfaceTransmittanceProfileUseBurley(uint SubsurfaceProfileInt)
{
half Type = GetSubsurfaceProfileTexture(SSSS_BOUNDARY_COLOR_BLEED_OFFSET, SubsurfaceProfileInt).a;
return abs(Type - SSS_TYPE_BURLEY) < 0.01f;
}
bool GetSubsurfaceProfileUseBurley(uint SubsurfaceProfileInt)
{
half Type = GetSubsurfaceProfileTexture(SSSS_BOUNDARY_COLOR_BLEED_OFFSET, SubsurfaceProfileInt).a;
return abs(Type - SSS_TYPE_BURLEY) < 0.01f;
}
bool GetSubsurfaceProfileUseSeparable(uint SubsurfaceProfileInt)
{
half Type = GetSubsurfaceProfileTexture(SSSS_BOUNDARY_COLOR_BLEED_OFFSET, SubsurfaceProfileInt).a;
return abs(Type - SSS_TYPE_SSSS) < 0.01f;
}
float4 GetSubsurfaceProfileDMFPInCm(int SubsurfaceProfileInt)
{
const float4 DiffuseMeanFreePath = DecodeDiffuseMeanFreePath(GetSubsurfaceProfileDiffuseMeanFreePath(SubsurfaceProfileInt));
const float WorldUnitScale = DecodeWorldUnitScale(GetSubsurfaceProfileWorldUnitScale(SubsurfaceProfileInt));
return DiffuseMeanFreePath * WorldUnitScale; // In cm
}
#if SHADING_PATH_MOBILE
half CalculateCurvature(half3 WorldNormal, float3 WorldPosition)
{
#if 0
half DeltaNormal = length(abs(DDX(WorldNormal)) + abs(DDY(WorldNormal)));
half DeltaPosition = length(abs(DDX(WorldPosition)) + abs(DDY(WorldPosition))) * BURLEY_CM_2_MM;
half CurvatureApprox = DeltaNormal / DeltaPosition;
#else
half3 dNdx = ddx((HALF3_TYPE)WorldNormal);
half3 dNdy = ddy((HALF3_TYPE)WorldNormal);
half x = dot(dNdx, dNdx);
half y = dot(dNdy, dNdy);
half CurvatureApprox = pow(max(x, y), ResolvedView.NormalCurvatureToRoughnessScaleBias.z);
#endif
return CurvatureApprox;
}
#endif