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