470 lines
14 KiB
HLSL
470 lines
14 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#ifndef ALLOW_SSS_MATERIAL_OVERRIDE
|
|
#define ALLOW_SSS_MATERIAL_OVERRIDE 1
|
|
#endif
|
|
|
|
#include "SceneTexturesCommon.ush"
|
|
#include "SceneTextureParameters.ush"
|
|
#include "DeferredShadingCommon.ush"
|
|
|
|
float SquareInline(float X)
|
|
{
|
|
return X * X;
|
|
}
|
|
|
|
float3 EncodeNormalHelper(float3 SrcNormal, float QuantizationBias)
|
|
{
|
|
return SrcNormal * .5f + .5f;
|
|
}
|
|
|
|
float3 DecodeNormalHelper(float3 SrcNormal)
|
|
{
|
|
return SrcNormal * 2.0f - 1.0f;
|
|
}
|
|
|
|
|
|
uint EncodeQuantize6(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 63.0f + .5f + QuantizationBias),63u);
|
|
}
|
|
|
|
float DecodeQuantize6(uint Value)
|
|
{
|
|
return float(Value) / 63.0f;
|
|
}
|
|
|
|
uint EncodeQuantize6Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 63.0f + .5f + QuantizationBias),63u);
|
|
}
|
|
|
|
float DecodeQuantize6Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 63.0f);
|
|
}
|
|
|
|
uint EncodeQuantize5(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 31.0f + .5f + QuantizationBias),31u);
|
|
}
|
|
|
|
float DecodeQuantize5(uint Value)
|
|
{
|
|
return float(Value) / 31.0f;
|
|
}
|
|
|
|
uint EncodeQuantize5Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 31.0f + .5f + QuantizationBias),31u);
|
|
}
|
|
|
|
float DecodeQuantize5Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 31.0f);
|
|
}
|
|
|
|
uint EncodeQuantize4(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 15.0f + .5f + QuantizationBias),15u);
|
|
}
|
|
|
|
float DecodeQuantize4(uint Value)
|
|
{
|
|
return float(Value) / 15.0f;
|
|
}
|
|
|
|
uint EncodeQuantize4Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 15.0f + .5f + QuantizationBias),15u);
|
|
}
|
|
|
|
float DecodeQuantize4Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 15.0f);
|
|
}
|
|
|
|
|
|
uint EncodeQuantize3(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 7.0f + .5f + QuantizationBias),7u);
|
|
}
|
|
|
|
float DecodeQuantize3(uint Value)
|
|
{
|
|
return float(Value) / 7.0f;
|
|
}
|
|
|
|
uint EncodeQuantize3Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 7.0f + .5f + QuantizationBias),7u);
|
|
}
|
|
|
|
float DecodeQuantize3Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 7.0f);
|
|
}
|
|
|
|
uint EncodeQuantize2(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 3.0f + .5f + QuantizationBias),3u);
|
|
}
|
|
|
|
float DecodeQuantize2(uint Value)
|
|
{
|
|
return float(Value) / 3.0f;
|
|
}
|
|
|
|
uint EncodeQuantize2Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 3.0f + .5f + QuantizationBias),3u);
|
|
}
|
|
|
|
float DecodeQuantize2Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 3.0f);
|
|
}
|
|
|
|
uint EncodeQuantize1(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(saturate(Value) * 1.0f + .5f + QuantizationBias),1u);
|
|
}
|
|
|
|
float DecodeQuantize1(uint Value)
|
|
{
|
|
return float(Value) / 1.0f;
|
|
}
|
|
|
|
uint EncodeQuantize1Sqrt(float Value, float QuantizationBias)
|
|
{
|
|
return min(uint(sqrt(saturate(Value)) * 1.0f + .5f + QuantizationBias),1u);
|
|
}
|
|
|
|
float DecodeQuantize1Sqrt(uint Value)
|
|
{
|
|
return SquareInline(float(Value) / 1.0f);
|
|
}
|
|
|
|
|
|
uint3 EncodeQuantize565(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize5(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize6(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize5(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize565(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize5(Value.x);
|
|
Ret.y = DecodeQuantize6(Value.y);
|
|
Ret.z = DecodeQuantize5(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
uint3 EncodeQuantize565Sqrt(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize5Sqrt(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize6Sqrt(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize5Sqrt(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize565Sqrt(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize5Sqrt(Value.x);
|
|
Ret.y = DecodeQuantize6Sqrt(Value.y);
|
|
Ret.z = DecodeQuantize5Sqrt(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
|
|
uint3 EncodeQuantize444(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize4(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize4(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize4(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize444(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize4(Value.x);
|
|
Ret.y = DecodeQuantize4(Value.y);
|
|
Ret.z = DecodeQuantize4(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
uint3 EncodeQuantize444Sqrt(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize4Sqrt(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize4Sqrt(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize4Sqrt(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize444Sqrt(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize4Sqrt(Value.x);
|
|
Ret.y = DecodeQuantize4Sqrt(Value.y);
|
|
Ret.z = DecodeQuantize4Sqrt(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
|
|
uint3 EncodeQuantize332(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize3(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize3(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize2(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize332(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize3(Value.x);
|
|
Ret.y = DecodeQuantize3(Value.y);
|
|
Ret.z = DecodeQuantize2(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
uint3 EncodeQuantize332Sqrt(float3 Value, float QuantizationBias)
|
|
{
|
|
uint3 Ret;
|
|
Ret.x = EncodeQuantize3Sqrt(Value.x,QuantizationBias);
|
|
Ret.y = EncodeQuantize3Sqrt(Value.y,QuantizationBias);
|
|
Ret.z = EncodeQuantize2Sqrt(Value.z,QuantizationBias);
|
|
return Ret;
|
|
}
|
|
|
|
float3 DecodeQuantize332Sqrt(uint3 Value)
|
|
{
|
|
float3 Ret;
|
|
Ret.x = DecodeQuantize3Sqrt(Value.x);
|
|
Ret.y = DecodeQuantize3Sqrt(Value.y);
|
|
Ret.z = DecodeQuantize2Sqrt(Value.z);
|
|
return Ret;
|
|
}
|
|
|
|
|
|
void EnvBRDFApproxFullyRoughHelper(inout float3 DiffuseColor, inout float3 SpecularColor)
|
|
{
|
|
// Factors derived from EnvBRDFApprox( SpecularColor, 1, 1 ) == SpecularColor * 0.4524 - 0.0024
|
|
DiffuseColor += SpecularColor * 0.45;
|
|
SpecularColor = 0;
|
|
// We do not modify Roughness here as this is done differently at different places.
|
|
}
|
|
|
|
void EnvBRDFApproxFullyRoughHelper(inout float3 DiffuseColor, inout float SpecularColor)
|
|
{
|
|
DiffuseColor += SpecularColor * 0.45;
|
|
SpecularColor = 0;
|
|
}
|
|
|
|
|
|
#define USES_GBUFFER_HELPER (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !FORWARD_SHADING)
|
|
|
|
#define FORCE_FULLY_ROUGH_HELPER (MATERIAL_FULLY_ROUGH)
|
|
|
|
|
|
// TODO: Move the BasePassPixelShader extra changes into here.
|
|
void GBufferPreEncode(inout FGBufferData GBuffer, bool bChecker, float GeometricAARoughness, inout half3 OriginalBaseColor, inout half OriginalSpecular, inout half OriginalMetallic, float QuantizationBias)
|
|
{
|
|
#if MATERIAL_NORMAL_CURVATURE_TO_ROUGHNESS
|
|
GBuffer.Roughness = max(GBuffer.Roughness, GeometricAARoughness);
|
|
|
|
#if MATERIAL_SHADINGMODEL_CLEAR_COAT
|
|
if (GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT)
|
|
{
|
|
GBuffer.CustomData.y = max(GBuffer.CustomData.y, GeometricAARoughness);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if USES_GBUFFER_HELPER && (MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_EYE)
|
|
// SubsurfaceProfile applies the BaseColor in a later pass. Any lighting output in the base pass needs
|
|
// to separate specular and diffuse lighting in a checkerboard pattern
|
|
if (UseSubsurfaceProfile(GBuffer.ShadingModelID))
|
|
{
|
|
const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0;
|
|
OriginalBaseColor = View.bSubsurfacePostprocessEnabled ? float3(1, 1, 1) : OriginalBaseColor;
|
|
if (bCheckerboardRequired)
|
|
{
|
|
// because we adjust the BaseColor here, we need StoredBaseColor
|
|
|
|
// we apply the base color later in SubsurfaceRecombinePS()
|
|
OriginalBaseColor = bChecker;
|
|
// in SubsurfaceRecombinePS() does not multiply with Specular so we do it here
|
|
GBuffer.SpecularColor *= !bChecker;
|
|
OriginalSpecular *= !bChecker;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
GBuffer.DiffuseColor = OriginalBaseColor - OriginalBaseColor * OriginalMetallic;
|
|
|
|
#if USE_DEVELOPMENT_SHADERS
|
|
{
|
|
// this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help)
|
|
GBuffer.DiffuseColor = GBuffer.DiffuseColor * View.DiffuseOverrideParameter.w + View.DiffuseOverrideParameter.xyz;
|
|
GBuffer.SpecularColor = GBuffer.SpecularColor * View.SpecularOverrideParameter.w + View.SpecularOverrideParameter.xyz;
|
|
}
|
|
#endif
|
|
|
|
#if !FORCE_FULLY_ROUGH_HELPER
|
|
if (View.RenderingReflectionCaptureMask) // Force material rendered in reflection capture to have an expanded albedo to try to be energy conservative (when specular is removed).
|
|
#endif
|
|
{
|
|
EnvBRDFApproxFullyRoughHelper(GBuffer.DiffuseColor, GBuffer.SpecularColor);
|
|
// When rendering reflection captures, GBuffer.Roughness is already forced to 1 using RoughnessOverrideParameter in GetMaterialRoughness.
|
|
}
|
|
|
|
|
|
// should move this
|
|
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
|
|
GBuffer.GenericAO = float(GBuffer.DiffuseIndirectSampleOcclusion) * (1.0f / 255.0f);
|
|
#elif ALLOW_STATIC_LIGHTING
|
|
// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
|
|
GBuffer.GenericAO = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0);
|
|
#else
|
|
GBuffer.GenericAO = GBuffer.GBufferAO;
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
// Temporary extra copy of this function. The autogenerated GBuffer functions will require changing the include heirarchy, so copying this function to miminize
|
|
// the number of files that have to be touched on the initial checkin. Will remove duplicates once the first checkin is tested and stable.
|
|
void AdjustBaseColorAndSpecularColorForSubsurfaceProfileLightingCopyHack(inout float3 BaseColor, inout float3 SpecularColor, inout float Specular, bool bChecker)
|
|
{
|
|
// If this function is called in translucent pass, we should not modify the BaseColor, instead directly read the base color as it is.
|
|
// The translucent pass is called after the main post process subsurface scattering pass, it will not affect previous logic.
|
|
#if MATERIALBLENDING_TRANSLUCENT || !ALLOW_SSS_MATERIAL_OVERRIDE
|
|
return;
|
|
#else
|
|
|
|
#if SUBSURFACE_CHANNEL_MODE == 0
|
|
// If SUBSURFACE_CHANNEL_MODE is 0, we can't support full-resolution lighting, so we
|
|
// ignore View.bCheckerboardSubsurfaceProfileRendering
|
|
const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0;
|
|
#else
|
|
const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0;
|
|
BaseColor = View.bSubsurfacePostprocessEnabled ? float3(1, 1, 1) : BaseColor;
|
|
#endif
|
|
if (bCheckerboardRequired)
|
|
{
|
|
// because we adjust the BaseColor here, we need StoredBaseColor
|
|
|
|
// we apply the base color later in SubsurfaceRecombinePS()
|
|
BaseColor = bChecker;
|
|
// in SubsurfaceRecombinePS() does not multiply with Specular so we do it here
|
|
SpecularColor *= !bChecker;
|
|
Specular *= !bChecker;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifndef INDIRECT_SAMPLE_COUNT
|
|
#define INDIRECT_SAMPLE_COUNT 8
|
|
#endif
|
|
|
|
void GBufferPostDecode(inout FGBufferData Ret, bool bChecker, bool bGetNormalizedNormal)
|
|
{
|
|
Ret.CustomData = HasCustomGBufferData(Ret.ShadingModelID) ? Ret.CustomData : half(0.0f);
|
|
|
|
// FirstPerson uses a bit in SelectiveOutputMask that is aliased with ZERO_PRECSHADOW_MASK when !ALLOW_STATIC_LIGHTING, so we explicitly skip this logic here.
|
|
#if ALLOW_STATIC_LIGHTING
|
|
Ret.PrecomputedShadowFactors = !(Ret.SelectiveOutputMask & 0x2) ? Ret.PrecomputedShadowFactors : ((Ret.SelectiveOutputMask & 0x4) ? half(0.0f) : half(1.0f));
|
|
#else
|
|
Ret.PrecomputedShadowFactors = half(1.0f);
|
|
#endif
|
|
Ret.Velocity = !(Ret.SelectiveOutputMask & 0x8) ? Ret.Velocity : half(0.0f);
|
|
bool bHasAnisotropy = (Ret.SelectiveOutputMask & 0x1);
|
|
|
|
Ret.StoredBaseColor = Ret.BaseColor;
|
|
Ret.StoredMetallic = Ret.Metallic;
|
|
Ret.StoredSpecular = Ret.Specular;
|
|
|
|
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
|
|
Ret.DiffuseIndirectSampleOcclusion = 255 * Ret.GenericAO;
|
|
Ret.GBufferAO = saturate(1.0 - float(countbits(Ret.DiffuseIndirectSampleOcclusion)) * rcp(float(INDIRECT_SAMPLE_COUNT)));
|
|
Ret.IndirectIrradiance = 1;
|
|
#elif ALLOW_STATIC_LIGHTING
|
|
Ret.GBufferAO = 1;
|
|
Ret.DiffuseIndirectSampleOcclusion = 0x0;
|
|
Ret.IndirectIrradiance = half(DecodeIndirectIrradiance(Ret.GenericAO.x));
|
|
#else
|
|
Ret.GBufferAO = Ret.GenericAO;
|
|
Ret.DiffuseIndirectSampleOcclusion = 0x0;
|
|
Ret.IndirectIrradiance = 1;
|
|
#endif
|
|
|
|
if(bGetNormalizedNormal)
|
|
{
|
|
Ret.WorldNormal = normalize(Ret.WorldNormal);
|
|
}
|
|
|
|
FLATTEN
|
|
if( Ret.ShadingModelID == SHADINGMODELID_EYE )
|
|
{
|
|
Ret.Metallic = 0.0;
|
|
#if IRIS_NORMAL
|
|
Ret.Specular = 0.25;
|
|
#endif
|
|
}
|
|
|
|
// derived from BaseColor, Metalness, Specular
|
|
{
|
|
Ret.SpecularColor = ComputeF0(Ret.Specular, Ret.BaseColor, Ret.Metallic);
|
|
|
|
if (UseSubsurfaceProfile(Ret.ShadingModelID))
|
|
{
|
|
AdjustBaseColorAndSpecularColorForSubsurfaceProfileLightingCopyHack(Ret.BaseColor, Ret.SpecularColor, Ret.Specular, bChecker);
|
|
}
|
|
|
|
Ret.DiffuseColor = Ret.BaseColor - Ret.BaseColor * Ret.Metallic;
|
|
|
|
#if USE_DEVELOPMENT_SHADERS
|
|
{
|
|
// this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help)
|
|
Ret.DiffuseColor = Ret.DiffuseColor * View.DiffuseOverrideParameter.www + View.DiffuseOverrideParameter.xyz;
|
|
Ret.SpecularColor = Ret.SpecularColor * View.SpecularOverrideParameter.w + View.SpecularOverrideParameter.xyz;
|
|
}
|
|
#endif //USE_DEVELOPMENT_SHADERS
|
|
}
|
|
|
|
if (bHasAnisotropy)
|
|
{
|
|
Ret.WorldTangent = half3(DecodeNormal(Ret.WorldTangent));
|
|
Ret.Anisotropy = half(Ret.Anisotropy * 2.0f - 1.0f);
|
|
|
|
if(bGetNormalizedNormal)
|
|
{
|
|
Ret.WorldTangent = normalize(Ret.WorldTangent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ret.WorldTangent = 0;
|
|
Ret.Anisotropy = 0;
|
|
}
|
|
|
|
// This requires cleanup. Shader code that uses GBuffer.SelectiveOutputMask expects the outputmask to be in
|
|
// bits [4:7], but it gets packed as bits [0:3] in the flexible gbuffer since we might move it around.
|
|
Ret.SelectiveOutputMask = Ret.SelectiveOutputMask << 4;
|
|
}
|