├── Docs └── 最终效果.png ├── ToonShader.uplugin ├── Resources └── Icon128.png ├── Source ├── ToonShaderBootstrap │ ├── Private │ │ ├── ToonShaderBootstrapSettings.cpp │ │ └── ToonShaderBootstrap.cpp │ ├── Public │ │ ├── ToonShaderBootstrap.h │ │ └── ToonShaderBootstrapSettings.h │ └── ToonShaderBootstrap.Build.cs └── ToonShader │ ├── Public │ ├── ToonShader.h │ └── ToonShadingModelOutput.h │ ├── Private │ ├── ToonShader.cpp │ └── ToonShadingModelOutput.cpp │ └── ToonShader.Build.cs ├── Config └── FilterPlugin.ini ├── .gitignore ├── ShadersOverride ├── Override │ └── Private │ │ ├── Toon │ │ └── ToonShaderModels.usf │ │ ├── ShadingCommon.ush │ │ ├── ShadingModelsMaterial.ush │ │ ├── BasePassCommon.ush │ │ ├── DeferredLightingCommon.ush │ │ ├── DeferredShadingCommon.ush │ │ └── ShadingModels.ush └── Default │ └── Private │ ├── ShadingCommon.ush │ ├── ShadingModelsMaterial.ush │ ├── BasePassCommon.ush │ ├── DeferredLightingCommon.ush │ ├── DeferredShadingCommon.ush │ └── ShadingModels.ush └── README.md /Docs/最终效果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eragon-Brisingr/ToonShader/HEAD/Docs/最终效果.png -------------------------------------------------------------------------------- /ToonShader.uplugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eragon-Brisingr/ToonShader/HEAD/ToonShader.uplugin -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eragon-Brisingr/ToonShader/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/ToonShaderBootstrap/Private/ToonShaderBootstrapSettings.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ToonShaderBootstrapSettings.h" 5 | 6 | -------------------------------------------------------------------------------- /Source/ToonShader/Public/ToonShader.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FToonShaderModule : public IModuleInterface 8 | { 9 | public: 10 | 11 | /** IModuleInterface implementation */ 12 | void StartupModule() override; 13 | void ShutdownModule() override; 14 | }; 15 | -------------------------------------------------------------------------------- /Source/ToonShaderBootstrap/Public/ToonShaderBootstrap.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | class FToonShaderBootstrapModule : public IModuleInterface 8 | { 9 | public: 10 | 11 | /** IModuleInterface implementation */ 12 | void StartupModule() override; 13 | void ShutdownModule() override; 14 | }; 15 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | 10 | /README.md 11 | /ShadersOverride/... 12 | /Docs/... -------------------------------------------------------------------------------- /Source/ToonShader/Private/ToonShader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ToonShader.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FToonShaderModule" 6 | 7 | void FToonShaderModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | 11 | } 12 | 13 | void FToonShaderModule::ShutdownModule() 14 | { 15 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 16 | // we call this function before unloading the module. 17 | 18 | } 19 | 20 | #undef LOCTEXT_NAMESPACE 21 | 22 | IMPLEMENT_MODULE(FToonShaderModule, ToonShader) -------------------------------------------------------------------------------- /Source/ToonShader/ToonShader.Build.cs: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ToonShader : ModuleRules 6 | { 7 | public ToonShader(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | "RenderCore", 42 | "Projects", 43 | // ... add private dependencies that you statically link with here ... 44 | } 45 | ); 46 | 47 | 48 | DynamicallyLoadedModuleNames.AddRange( 49 | new string[] 50 | { 51 | // ... add any modules that your module loads dynamically here ... 52 | } 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Source/ToonShaderBootstrap/ToonShaderBootstrap.Build.cs: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ToonShaderBootstrap : ModuleRules 6 | { 7 | public ToonShaderBootstrap(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Projects", 40 | "DeveloperSettings", 41 | // ... add private dependencies that you statically link with here ... 42 | } 43 | ); 44 | 45 | 46 | DynamicallyLoadedModuleNames.AddRange( 47 | new string[] 48 | { 49 | // ... add any modules that your module loads dynamically here ... 50 | } 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/ToonShaderBootstrap/Public/ToonShaderBootstrapSettings.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Engine/DeveloperSettings.h" 7 | #include "ToonShaderBootstrapSettings.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS(Config = "ToonShaderSettings", defaultconfig) 13 | class TOONSHADERBOOTSTRAP_API UToonShaderBootstrapSettings : public UDeveloperSettings 14 | { 15 | GENERATED_BODY() 16 | public: 17 | UToonShaderBootstrapSettings() 18 | : bRestoreDefaultShaderDirectory(true) 19 | , bAlwaysReplaceShaderDirectory(false) 20 | {} 21 | 22 | FName GetContainerName() const override { return TEXT("Project"); } 23 | FName GetCategoryName() const override { return TEXT("Plugins"); } 24 | FName GetSectionName() const override { return TEXT("ToonShaderSettings"); } 25 | 26 | // 关闭项目时会恢复为虚幻默认的Shader 27 | // 可能会导致引擎启动时shader编译 28 | UPROPERTY(EditAnywhere, Config, Category = "启动设置", meta = (DisplayName = "关闭项目时恢复默认Shader")) 29 | uint8 bRestoreDefaultShaderDirectory : 1; 30 | 31 | // 每次启动时都会将卡通材质shader替换进引擎的shader文件中 32 | // 建议修改shader调试时才打开 33 | UPROPERTY(EditAnywhere, Config, Category = "启动设置", meta = (DisplayName = "总是替换ToonShader文件夹", EditCondition = "bRestoreDefaultShaderDirectory == false")) 34 | uint8 bAlwaysReplaceShaderDirectory : 1; 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.ipa 36 | 37 | # These project files can be generated by the engine 38 | *.xcodeproj 39 | *.xcworkspace 40 | *.sln 41 | *.suo 42 | *.opensdf 43 | *.sdf 44 | *.VC.db 45 | *.VC.opendb 46 | 47 | # Precompiled Assets 48 | SourceArt/**/*.png 49 | SourceArt/**/*.tga 50 | 51 | # Binary Files 52 | Binaries/* 53 | Plugins/*/Binaries/* 54 | 55 | # Builds 56 | Build/* 57 | 58 | # Whitelist PakBlacklist-.txt files 59 | !Build/*/ 60 | Build/*/** 61 | !Build/*/PakBlacklist*.txt 62 | 63 | # Don't ignore icon files in Build 64 | !Build/**/*.ico 65 | 66 | # Built data for maps 67 | *_BuiltData.uasset 68 | 69 | # Configuration files generated by the Editor 70 | Saved/* 71 | 72 | # Compiled source files for the engine to use 73 | Intermediate/* 74 | Plugins/*/Intermediate/* 75 | 76 | # Cache files for the editor to use 77 | DerivedDataCache/* 78 | 79 | # python 80 | *.pyc 81 | 82 | # 打包目录 83 | Package/* 84 | 85 | # 编译临时文件 86 | enc_temp_folder/* 87 | *.TMP 88 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/Toon/ToonShaderModels.usf: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | float3 ToonStep(float Range, float Input) 4 | { 5 | return smoothstep(0.5 - Range, 0.5 + Range, Input); 6 | } 7 | 8 | FDirectLighting StylizedShadowBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow) 9 | { 10 | #if GBUFFER_HAS_TANGENT 11 | half3 X = GBuffer.WorldTangent; 12 | half3 Y = normalize(cross(N, X)); 13 | #else 14 | half3 X = 0; 15 | half3 Y = 0; 16 | #endif 17 | 18 | BxDFContext Context; 19 | Init(Context, N, X, Y, V, L); 20 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true); 21 | Context.NoV = saturate(abs(Context.NoV) + 1e-5); 22 | 23 | float SpecularOffset = 0.5; 24 | float SpecularRange = GBuffer.CustomData.x; 25 | 26 | float3 ShadowColor = 0; 27 | 28 | ShadowColor = GBuffer.DiffuseColor * ShadowColor; 29 | float offset = GBuffer.CustomData.y; 30 | float SoftScatterStrength = 0; 31 | 32 | offset = offset * 2 - 1; 33 | half3 H = normalize(V + L); 34 | float NoH = saturate(dot(N, H)); 35 | NoL = (dot(N, L) + 1) / 2; // overwrite NoL to get more range out of it 36 | half NoLOffset = saturate(NoL + offset); 37 | 38 | FDirectLighting Lighting; 39 | Lighting.Diffuse = AreaLight.FalloffColor * (smoothstep(0, 1, NoLOffset) * Falloff) * Diffuse_Lambert(GBuffer.DiffuseColor) * 2.2; 40 | float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, .1f, 1); 41 | float NormalContribution = saturate(dot(N, H)); 42 | float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2); 43 | Lighting.Specular = ToonStep(SpecularRange, (saturate(D_GGX(SpecularOffset, NoH)))) * (AreaLight.FalloffColor * GBuffer.SpecularColor * Falloff * 8); 44 | float3 TransmissionSoft = AreaLight.FalloffColor * (Falloff * lerp(BackScatter, 1, InScatter)) * ShadowColor * SoftScatterStrength; 45 | float3 ShadowLightener = 0; 46 | ShadowLightener = (saturate(smoothstep(0, 1, saturate(1 - NoLOffset))) * ShadowColor * 0.1); 47 | 48 | Lighting.Transmission = (ShadowLightener + TransmissionSoft) * Falloff; 49 | return Lighting; 50 | } 51 | -------------------------------------------------------------------------------- /Source/ToonShader/Private/ToonShadingModelOutput.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ToonShadingModelOutput.h" 4 | 5 | #if WITH_EDITOR 6 | #include "MaterialCompiler.h" 7 | #endif 8 | 9 | #define LOCTEXT_NAMESPACE "ToonShadingModelOutput" 10 | 11 | UToonShadingModelOutput::UToonShadingModelOutput(const FObjectInitializer& ObjectInitializer) 12 | : Super(ObjectInitializer) 13 | { 14 | #if WITH_EDITORONLY_DATA 15 | static FText NAME_Category = LOCTEXT("Toon", "Toon"); 16 | MenuCategories.Add(NAME_Category); 17 | 18 | bCollapsed = true; 19 | 20 | // No outputs 21 | Outputs.Reset(); 22 | #endif 23 | } 24 | 25 | #if WITH_EDITOR 26 | int32 UToonShadingModelOutput::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) 27 | { 28 | auto CompileExpressionInput = [&](FExpressionInput& ExpressionInput, float DefaultValue) 29 | { 30 | if (ExpressionInput.GetTracedInput().Expression) 31 | { 32 | return Compiler->CustomOutput(this, OutputIndex, ExpressionInput.Compile(Compiler)); 33 | } 34 | return Compiler->CustomOutput(this, OutputIndex, Compiler->Constant(DefaultValue)); 35 | }; 36 | 37 | switch (EToonShadingModelOutput(OutputIndex)) 38 | { 39 | case EToonShadingModelOutput::SpecularRange: 40 | return CompileExpressionInput(SpecularRange, 0.f); 41 | case EToonShadingModelOutput::Offset: 42 | return CompileExpressionInput(Offset, 1.f); 43 | } 44 | checkNoEntry(); 45 | return CompilerError(Compiler, TEXT("Input missing")); 46 | } 47 | 48 | void UToonShadingModelOutput::GetCaption(TArray& OutCaptions) const 49 | { 50 | OutCaptions.Add(FString(TEXT("ToonShading"))); 51 | } 52 | 53 | uint32 UToonShadingModelOutput::GetInputType(int32 InputIndex) 54 | { 55 | switch (EToonShadingModelOutput(InputIndex)) 56 | { 57 | case EToonShadingModelOutput::SpecularRange: 58 | return MCT_Float1; 59 | case EToonShadingModelOutput::Offset: 60 | return MCT_Float1; 61 | } 62 | checkNoEntry(); 63 | return MCT_Float1; 64 | } 65 | 66 | FExpressionInput* UToonShadingModelOutput::GetInput(int32 InputIndex) 67 | { 68 | switch (EToonShadingModelOutput(InputIndex)) 69 | { 70 | case EToonShadingModelOutput::SpecularRange: 71 | return &SpecularRange; 72 | case EToonShadingModelOutput::Offset: 73 | return &Offset; 74 | } 75 | checkNoEntry(); 76 | return nullptr; 77 | } 78 | #endif 79 | 80 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/ToonShader/Public/ToonShadingModelOutput.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Materials/MaterialExpressionCustomOutput.h" 6 | #include "ToonShadingModelOutput.generated.h" 7 | 8 | /* 9 | * 10 | */ 11 | UCLASS(collapsecategories, hidecategories = Object, MinimalAPI) 12 | class UToonShadingModelOutput : public UMaterialExpressionCustomOutput 13 | { 14 | GENERATED_BODY() 15 | public: 16 | UToonShadingModelOutput(const FObjectInitializer& ObjectInitializer); 17 | 18 | enum class EToonShadingModelOutput 19 | { 20 | SpecularRange = 0, 21 | Offset = 1, 22 | }; 23 | 24 | UPROPERTY(meta = (RequiredInput = "true")) 25 | FExpressionInput SpecularRange; 26 | 27 | UPROPERTY(meta = (RequiredInput = "true")) 28 | FExpressionInput Offset; 29 | 30 | #if WITH_EDITOR 31 | int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override; 32 | void GetCaption(TArray& OutCaptions) const override; 33 | uint32 GetInputType(int32 InputIndex) override; 34 | FExpressionInput* GetInput(int32 InputIndex) override; 35 | #endif 36 | int32 GetNumOutputs() const override { return 2; } 37 | FString GetFunctionName() const override { return TEXT("GetToonShading"); } 38 | FString GetDisplayName() const override { return TEXT("Toon Shading"); } 39 | }; 40 | 41 | // FMaterialShadingModelField::AddShadingModel 存在check(InShadingModel < MSM_NUM)检查导致没法Hack 42 | //UENUM() 43 | //enum class EToonMaterialShadingModel : uint8 44 | //{ 45 | // MSM_STYLIZED_SHADOW = 12 UMETA(DisplayName = "Style Shadow"), 46 | //}; 47 | // 48 | //UCLASS(collapsecategories, hidecategories = Object, MinimalAPI, meta = (DisplayName = "Toon Shading Model")) 49 | //class UToonMaterialShadingModelExpression : public UMaterialExpression 50 | //{ 51 | // GENERATED_BODY() 52 | //public: 53 | // UToonMaterialShadingModelExpression(const FObjectInitializer& ObjectInitializer); 54 | // 55 | // //~ Begin UMaterialExpression Interface 56 | //#if WITH_EDITOR 57 | // int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override; 58 | // uint32 GetOutputType(int32 OutputIndex) override; 59 | // void GetCaption(TArray& OutCaptions) const override; 60 | //#endif 61 | //public: 62 | // UPROPERTY(EditAnywhere, Category = ShadingModel) 63 | // EToonMaterialShadingModel ToonShadingModel = EToonMaterialShadingModel::MSM_STYLIZED_SHADOW; 64 | // //~ End UMaterialExpression Interface 65 | //}; 66 | -------------------------------------------------------------------------------- /Source/ToonShaderBootstrap/Private/ToonShaderBootstrap.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ToonShaderBootstrap.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #define LOCTEXT_NAMESPACE "FToonShaderBootstrapModule" 9 | 10 | void FToonShaderBootstrapModule::StartupModule() 11 | { 12 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 13 | 14 | check(GConfig); 15 | 16 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); 17 | const FString PluginShaderOverrideDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("ToonShader"))->GetBaseDir(), TEXT("ShadersOverride")); 18 | const FString ShaderDir = FPaths::Combine(FPaths::EngineDir(), TEXT("Shaders")); 19 | 20 | bool bAlwaysReplaceShaderDirectory = PlatformFile.DirectoryExists(*FPaths::Combine(ShaderDir, TEXT("Private"), TEXT("Toon"))) == false; 21 | if (bAlwaysReplaceShaderDirectory == false) 22 | { 23 | FConfigFile* ConfigFile = GConfig->Find(*FPaths::Combine(FPaths::ProjectConfigDir(), TEXT("DefaultToonShaderSettings.ini")), false); 24 | if (ConfigFile) 25 | { 26 | ConfigFile->GetBool(TEXT("/Script/ToonShaderBootstrap.ToonShaderBootstrapSettings"), TEXT("bAlwaysReplaceShaderDirectory"), bAlwaysReplaceShaderDirectory); 27 | } 28 | } 29 | 30 | if (bAlwaysReplaceShaderDirectory) 31 | { 32 | const bool Succeed = PlatformFile.CopyDirectoryTree(*ShaderDir, *FPaths::Combine(PluginShaderOverrideDir, TEXT("Override")), true); 33 | if (IsRunningCommandlet() == false && Succeed == false) 34 | { 35 | FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("替换卡通材质失败警告", "向引擎目录[Engine/Shaders]下替换文件失败\n可能是文件访问权限问题")); 36 | } 37 | } 38 | } 39 | 40 | void FToonShaderBootstrapModule::ShutdownModule() 41 | { 42 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 43 | // we call this function before unloading the module. 44 | 45 | check(GConfig); 46 | 47 | bool bRestoreDefaultShaderDirectory = true; 48 | { 49 | FConfigFile* ConfigFile = GConfig->Find(*FPaths::Combine(FPaths::ProjectConfigDir(), TEXT("DefaultToonShaderSettings.ini")), false); 50 | if (ConfigFile) 51 | { 52 | ConfigFile->GetBool(TEXT("/Script/ToonShaderBootstrap.ToonShaderBootstrapSettings"), TEXT("bRestoreDefaultShaderDirectory"), bRestoreDefaultShaderDirectory); 53 | } 54 | } 55 | 56 | if (bRestoreDefaultShaderDirectory) 57 | { 58 | const FString PluginShaderOverrideDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("ToonShader"))->GetBaseDir(), TEXT("ShadersOverride")); 59 | const FString ShaderDir = FPaths::Combine(FPaths::EngineDir(), TEXT("Shaders")); 60 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); 61 | 62 | bool Succeed = PlatformFile.DeleteDirectoryRecursively(*FPaths::Combine(ShaderDir, TEXT("Private"), TEXT("Toon"))); 63 | Succeed &= PlatformFile.CopyDirectoryTree(*ShaderDir, *FPaths::Combine(PluginShaderOverrideDir, TEXT("Default")), true); 64 | if (IsRunningCommandlet() == false && Succeed == false) 65 | { 66 | FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("还原默认材质失败警告", "还原引擎目录[Engine/Shaders]文件失败\n可能是文件访问权限问题")); 67 | } 68 | } 69 | } 70 | 71 | #undef LOCTEXT_NAMESPACE 72 | 73 | IMPLEMENT_MODULE(FToonShaderBootstrapModule, ToonShaderBootstrap) -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/ShadingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | // SHADINGMODELID_* occupy the 4 low bits of an 8bit channel and SKIP_* occupy the 4 high bits 6 | #define SHADINGMODELID_UNLIT 0 7 | #define SHADINGMODELID_DEFAULT_LIT 1 8 | #define SHADINGMODELID_SUBSURFACE 2 9 | #define SHADINGMODELID_PREINTEGRATED_SKIN 3 10 | #define SHADINGMODELID_CLEAR_COAT 4 11 | #define SHADINGMODELID_SUBSURFACE_PROFILE 5 12 | #define SHADINGMODELID_TWOSIDED_FOLIAGE 6 13 | #define SHADINGMODELID_HAIR 7 14 | #define SHADINGMODELID_CLOTH 8 15 | #define SHADINGMODELID_EYE 9 16 | #define SHADINGMODELID_SINGLELAYERWATER 10 17 | #define SHADINGMODELID_THIN_TRANSLUCENT 11 18 | #define SHADINGMODELID_NUM 12 19 | #define SHADINGMODELID_MASK 0xF // 4 bits reserved for ShadingModelID 20 | 21 | // The flags are defined so that 0 value has no effect! 22 | // These occupy the 4 high bits in the same channel as the SHADINGMODELID_* 23 | #define HAS_ANISOTROPY_MASK (1 << 4) 24 | #define SKIP_PRECSHADOW_MASK (1 << 5) 25 | #define ZERO_PRECSHADOW_MASK (1 << 6) 26 | #define SKIP_VELOCITY_MASK (1 << 7) 27 | 28 | // Hair reflectance component (R, TT, TRT, Local Scattering, Global Scattering, Multi Scattering,...) 29 | #define HAIR_COMPONENT_R 0x1u 30 | #define HAIR_COMPONENT_TT 0x2u 31 | #define HAIR_COMPONENT_TRT 0x4u 32 | #define HAIR_COMPONENT_LS 0x8u 33 | #define HAIR_COMPONENT_GS 0x10u 34 | #define HAIR_COMPONENT_MULTISCATTER 0x20u 35 | #define HAIR_COMPONENT_TT_MODEL 0x40u 36 | 37 | // for debugging and to visualize 38 | float3 GetShadingModelColor(uint ShadingModelID) 39 | { 40 | // TODO: PS4 doesn't optimize out correctly the switch(), so it thinks it needs all the Samplers even if they get compiled out 41 | // This will get fixed after launch per Sony... 42 | #if PS4_PROFILE 43 | if (ShadingModelID == SHADINGMODELID_UNLIT) return float3(0.1f, 0.1f, 0.2f); // Dark Blue 44 | else if (ShadingModelID == SHADINGMODELID_DEFAULT_LIT) return float3(0.1f, 1.0f, 0.1f); // Green 45 | else if (ShadingModelID == SHADINGMODELID_SUBSURFACE) return float3(1.0f, 0.1f, 0.1f); // Red 46 | else if (ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) return float3(0.6f, 0.4f, 0.1f); // Brown 47 | else if (ShadingModelID == SHADINGMODELID_CLEAR_COAT) return float3(0.1f, 0.4f, 0.4f); 48 | else if (ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) return float3(0.2f, 0.6f, 0.5f); // Cyan 49 | else if (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) return float3(0.2f, 0.2f, 0.8f); // Blue 50 | else if (ShadingModelID == SHADINGMODELID_HAIR) return float3(0.6f, 0.1f, 0.5f); 51 | else if (ShadingModelID == SHADINGMODELID_CLOTH) return float3(0.7f, 1.0f, 1.0f); 52 | else if (ShadingModelID == SHADINGMODELID_EYE) return float3(0.3f, 1.0f, 1.0f); 53 | else if (ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) return float3(0.5f, 0.5f, 1.0f); 54 | else if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) return float3(1.0f, 0.8f, 0.3f); 55 | else return float3(1.0f, 1.0f, 1.0f); // White 56 | #else 57 | switch(ShadingModelID) 58 | { 59 | case SHADINGMODELID_UNLIT: return float3(0.1f, 0.1f, 0.2f); // Dark Blue 60 | case SHADINGMODELID_DEFAULT_LIT: return float3(0.1f, 1.0f, 0.1f); // Green 61 | case SHADINGMODELID_SUBSURFACE: return float3(1.0f, 0.1f, 0.1f); // Red 62 | case SHADINGMODELID_PREINTEGRATED_SKIN: return float3(0.6f, 0.4f, 0.1f); // Brown 63 | case SHADINGMODELID_CLEAR_COAT: return float3(0.1f, 0.4f, 0.4f); // Brown 64 | case SHADINGMODELID_SUBSURFACE_PROFILE: return float3(0.2f, 0.6f, 0.5f); // Cyan 65 | case SHADINGMODELID_TWOSIDED_FOLIAGE: return float3(0.2f, 0.2f, 0.8f); // Cyan 66 | case SHADINGMODELID_HAIR: return float3(0.6f, 0.1f, 0.5f); 67 | case SHADINGMODELID_CLOTH: return float3(0.7f, 1.0f, 1.0f); 68 | case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f); 69 | case SHADINGMODELID_SINGLELAYERWATER: return float3(0.5f, 0.5f, 1.0f); 70 | case SHADINGMODELID_THIN_TRANSLUCENT: return float3(1.0f, 0.8f, 0.3f); 71 | default: return float3(1.0f, 1.0f, 1.0f); // White 72 | } 73 | #endif 74 | } 75 | 76 | 77 | float DielectricSpecularToF0(float Specular) 78 | { 79 | return 0.08f * Specular; 80 | } 81 | 82 | // [Burley, "Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering"] 83 | float DielectricF0ToIor(float F0) 84 | { 85 | return 2.0f / (1.0f - sqrt(F0)) - 1.0f; 86 | } 87 | 88 | float DielectricIorToF0(float Ior) 89 | { 90 | const float F0Sqrt = (Ior-1)/(Ior+1); 91 | const float F0 = F0Sqrt*F0Sqrt; 92 | return F0; 93 | } 94 | 95 | float3 ComputeF0(float Specular, float3 BaseColor, float Metallic) 96 | { 97 | return lerp(DielectricSpecularToF0(Specular).xxx, BaseColor, Metallic.xxx); 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/ShadingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | // SHADINGMODELID_* occupy the 4 low bits of an 8bit channel and SKIP_* occupy the 4 high bits 6 | #define SHADINGMODELID_UNLIT 0 7 | #define SHADINGMODELID_DEFAULT_LIT 1 8 | #define SHADINGMODELID_SUBSURFACE 2 9 | #define SHADINGMODELID_PREINTEGRATED_SKIN 3 10 | #define SHADINGMODELID_CLEAR_COAT 4 11 | #define SHADINGMODELID_SUBSURFACE_PROFILE 5 12 | #define SHADINGMODELID_TWOSIDED_FOLIAGE 6 13 | #define SHADINGMODELID_HAIR 7 14 | #define SHADINGMODELID_CLOTH 8 15 | #define SHADINGMODELID_EYE 9 16 | #define SHADINGMODELID_SINGLELAYERWATER 10 17 | #define SHADINGMODELID_THIN_TRANSLUCENT 11 18 | #define SHADINGMODELID_STYLIZED_SHADOW 12 19 | #define SHADINGMODELID_NUM 13 20 | #define SHADINGMODELID_MASK 0xF // 4 bits reserved for ShadingModelID 21 | 22 | // The flags are defined so that 0 value has no effect! 23 | // These occupy the 4 high bits in the same channel as the SHADINGMODELID_* 24 | #define HAS_ANISOTROPY_MASK (1 << 4) 25 | #define SKIP_PRECSHADOW_MASK (1 << 5) 26 | #define ZERO_PRECSHADOW_MASK (1 << 6) 27 | #define SKIP_VELOCITY_MASK (1 << 7) 28 | 29 | // Hair reflectance component (R, TT, TRT, Local Scattering, Global Scattering, Multi Scattering,...) 30 | #define HAIR_COMPONENT_R 0x1u 31 | #define HAIR_COMPONENT_TT 0x2u 32 | #define HAIR_COMPONENT_TRT 0x4u 33 | #define HAIR_COMPONENT_LS 0x8u 34 | #define HAIR_COMPONENT_GS 0x10u 35 | #define HAIR_COMPONENT_MULTISCATTER 0x20u 36 | #define HAIR_COMPONENT_TT_MODEL 0x40u 37 | 38 | // for debugging and to visualize 39 | float3 GetShadingModelColor(uint ShadingModelID) 40 | { 41 | // TODO: PS4 doesn't optimize out correctly the switch(), so it thinks it needs all the Samplers even if they get compiled out 42 | // This will get fixed after launch per Sony... 43 | #if PS4_PROFILE 44 | if (ShadingModelID == SHADINGMODELID_UNLIT) return float3(0.1f, 0.1f, 0.2f); // Dark Blue 45 | else if (ShadingModelID == SHADINGMODELID_DEFAULT_LIT) return float3(0.1f, 1.0f, 0.1f); // Green 46 | else if (ShadingModelID == SHADINGMODELID_SUBSURFACE) return float3(1.0f, 0.1f, 0.1f); // Red 47 | else if (ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) return float3(0.6f, 0.4f, 0.1f); // Brown 48 | else if (ShadingModelID == SHADINGMODELID_CLEAR_COAT) return float3(0.1f, 0.4f, 0.4f); 49 | else if (ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) return float3(0.2f, 0.6f, 0.5f); // Cyan 50 | else if (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) return float3(0.2f, 0.2f, 0.8f); // Blue 51 | else if (ShadingModelID == SHADINGMODELID_HAIR) return float3(0.6f, 0.1f, 0.5f); 52 | else if (ShadingModelID == SHADINGMODELID_CLOTH) return float3(0.7f, 1.0f, 1.0f); 53 | else if (ShadingModelID == SHADINGMODELID_EYE) return float3(0.3f, 1.0f, 1.0f); 54 | else if (ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) return float3(0.5f, 0.5f, 1.0f); 55 | else if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) return float3(1.0f, 0.8f, 0.3f); 56 | else if (ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW) return float3(0.7f, 0.2f, 0.2f); 57 | else return float3(1.0f, 1.0f, 1.0f); // White 58 | #else 59 | switch(ShadingModelID) 60 | { 61 | case SHADINGMODELID_UNLIT: return float3(0.1f, 0.1f, 0.2f); // Dark Blue 62 | case SHADINGMODELID_DEFAULT_LIT: return float3(0.1f, 1.0f, 0.1f); // Green 63 | case SHADINGMODELID_SUBSURFACE: return float3(1.0f, 0.1f, 0.1f); // Red 64 | case SHADINGMODELID_PREINTEGRATED_SKIN: return float3(0.6f, 0.4f, 0.1f); // Brown 65 | case SHADINGMODELID_CLEAR_COAT: return float3(0.1f, 0.4f, 0.4f); // Brown 66 | case SHADINGMODELID_SUBSURFACE_PROFILE: return float3(0.2f, 0.6f, 0.5f); // Cyan 67 | case SHADINGMODELID_TWOSIDED_FOLIAGE: return float3(0.2f, 0.2f, 0.8f); // Cyan 68 | case SHADINGMODELID_HAIR: return float3(0.6f, 0.1f, 0.5f); 69 | case SHADINGMODELID_CLOTH: return float3(0.7f, 1.0f, 1.0f); 70 | case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f); 71 | case SHADINGMODELID_SINGLELAYERWATER: return float3(0.5f, 0.5f, 1.0f); 72 | case SHADINGMODELID_THIN_TRANSLUCENT: return float3(1.0f, 0.8f, 0.3f); 73 | case SHADINGMODELID_STYLIZED_SHADOW: return float3(0.7f, 0.2f, 0.2f); 74 | default: return float3(1.0f, 1.0f, 1.0f); // White 75 | } 76 | #endif 77 | } 78 | 79 | 80 | float DielectricSpecularToF0(float Specular) 81 | { 82 | return 0.08f * Specular; 83 | } 84 | 85 | // [Burley, "Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering"] 86 | float DielectricF0ToIor(float F0) 87 | { 88 | return 2.0f / (1.0f - sqrt(F0)) - 1.0f; 89 | } 90 | 91 | float DielectricIorToF0(float Ior) 92 | { 93 | const float F0Sqrt = (Ior-1)/(Ior+1); 94 | const float F0 = F0Sqrt*F0Sqrt; 95 | return F0; 96 | } 97 | 98 | float3 ComputeF0(float Specular, float3 BaseColor, float Metallic) 99 | { 100 | return lerp(DielectricSpecularToF0(Specular).xxx, BaseColor, Metallic.xxx); 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 卡通材质 2 | 3 | ![最终效果](Docs/最终效果.png) 4 | 5 | ## 使用方式 6 | 7 | 在材质编辑器中添加**Toon Shading**节点,ShadingModel即切换为**SHADINGMODELID_STYLIZED_SHADOW** 8 | 注意:**添加节点后不再受编辑器中的ShadingModel变量控制** 9 | 10 | ## How to use 11 | 12 | Add **Toon Shading** node in material editor, and ShadingModel property will be replaced as toon shading model. 13 | 14 | ## 插件添加ShadingModel大致流程 15 | 16 | 注意该教程在引擎版本**4.26**时作成 17 | 18 | * 继承UMaterialExpressionCustomOutput 19 | 使用虚幻中**UMaterialExpressionCustomOutput**类型可在代码编译时插入**条件编译宏**的特性将自定义的ShadingModelId添加进Shader编译流程 20 | 插件中重写的GetFunctionName返回的**GetToonShading**在shader对应的宏为**NUM_MATERIAL_OUTPUTS_GETTOONSHADING** 21 | 22 | * 进入引擎**Engine/Shader目录**修改和添加Shader文件 23 | 24 | * 修改**ShadingCommon.ush**中添加新的ShadingModel宏定义 25 | * 添加宏**SHADINGMODELID_STYLIZED_SHADOW**,且修改**SHADINGMODELID_NUM**的数量(注意当前最多支持16个) 26 | 27 | > ```c++ 28 | > #define SHADINGMODELID_STYLIZED_SHADOW 12 29 | > #define SHADINGMODELID_NUM 13 30 | > ``` 31 | 32 | * 修改GetShadingModelColor函数,新增**SHADINGMODELID_STYLIZED_SHADOW**的调试颜色 33 | 34 | * 修改**BasePassCommon.ush**与**DeferredShadingCommon.ush**开启自定义GBuffer的CustomData写入 35 | * **BasePassCommon.ush** 36 | **WRITES_CUSTOMDATA_TO_GBUFFER**宏,支持**SHADINGMODELID_STYLIZED_SHADOW**写入 37 | 38 | > ```c++ 39 | > #define WRITES_CUSTOMDATA_TO_GBUFFER (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE || SHADINGMODELID_STYLIZED_SHADOW)) 40 | > ``` 41 | 42 | * **DeferredShadingCommon.ush** 43 | 修改**HasCustomGBufferData**函数,支持**SHADINGMODELID_STYLIZED_SHADOW**写入 44 | 45 | > ```c++ 46 | > bool HasCustomGBufferData(int ShadingModelID) 47 | > { 48 | > return ShadingModelID == SHADINGMODELID_SUBSURFACE 49 | > || ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN 50 | > || ShadingModelID == SHADINGMODELID_CLEAR_COAT 51 | > || ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE 52 | > || ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE 53 | > || ShadingModelID == SHADINGMODELID_HAIR 54 | > || ShadingModelID == SHADINGMODELID_CLOTH 55 | > || ShadingModelID == SHADINGMODELID_EYE 56 | > || ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW; 57 | > } 58 | > ``` 59 | 60 | * 修改**BasePassPixelShader.usf**中ShadingModel的初始化 61 | 62 | > ```c++ 63 | > #if NUM_MATERIAL_OUTPUTS_GETTOONSHADING 64 | > uint ShadingModel = SHADINGMODELID_STYLIZED_SHADOW; 65 | > #else 66 | > uint ShadingModel = GetMaterialShadingModel(PixelMaterialInputs); 67 | > #endif 68 | > ``` 69 | 70 | * 修改**ShadingModelsMaterial.ush**将额外的数据写入GBuffer的CustomData 71 | 72 | > ```c++ 73 | > // 支持 MaterialExpressionCustomOutput 形式 74 | > #if NUM_MATERIAL_OUTPUTS_GETTOONSHADING 75 | > else if (ShadingModel == SHADINGMODELID_STYLIZED_SHADOW) 76 | > { 77 | > GBuffer.CustomData.x = saturate( GetToonShading0(MaterialParameters) ); // SpecularRange 78 | > GBuffer.CustomData.y = saturate( GetToonShading1(MaterialParameters) ); // Offset 79 | > } 80 | > // 支持 ShadingModelId 形式 81 | > #elif SHADINGMODELID_STYLIZED_SHADOW 82 | > else if (ShadingModel == SHADINGMODELID_STYLIZED_SHADOW) 83 | > { 84 | > GBuffer.CustomData.x = saturate( GetMaterialCustomData0(MaterialParameters) ); // SpecularRange 85 | > GBuffer.CustomData.y = saturate( GetMaterialCustomData1(MaterialParameters) ); // Offset 86 | > } 87 | > #endif 88 | > ``` 89 | 90 | * 修改**ShadingModels.ush**支持添加的ShadingModel的自定义BxDF函数 91 | **IntegrateBxDF**函数中添加**SHADINGMODELID_STYLIZED_SHADOW**的case 92 | 93 | > ```c++ 94 | > case SHADINGMODELID_STYLIZED_SHADOW: 95 | > return StylizedShadowBxDF(GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow); 96 | > ``` 97 | 98 | * 修改**DeferredLightingCommon.ush**自定义光照 99 | 修改**GetDynamicLightingSplit**函数,通过GBuffer.ShadingModelID判断SHADINGMODELID_STYLIZED_SHADOW执行自定义光照逻辑 100 | 101 | > ```c++ 102 | > // STYLIZEDSHADOW SHADING 103 | > if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW) 104 | > { 105 | > float3 Attenuation = 1; 106 | > 107 | > float offset = GBuffer.CustomData.y; 108 | > float TerminatorRange = saturate(GBuffer.Roughness - 0.5); 109 | > 110 | > offset = offset * 2 - 1; 111 | > 112 | > BRANCH 113 | > if (offset >= 1) 114 | > { 115 | > Attenuation = 1; 116 | > } 117 | > else 118 | > { 119 | > float NoL = (dot(N, L) + 1) / 2; 120 | > float NoLOffset = saturate(NoL + offset); 121 | > float LightAttenuationOffset = saturate( Shadow.SurfaceShadow + offset); 122 | > float ToonSurfaceShadow = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, LightAttenuationOffset); 123 | > 124 | > Attenuation = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, NoLOffset) * ToonSurfaceShadow; 125 | > } 126 | > 127 | > LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation * 0.25, > bNeedsSeparateSubsurfaceLightAccumulation); 128 | > } 129 | > else 130 | > { 131 | > LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation ); 132 | > } 133 | > ``` 134 | 135 | ### 无法支持From Material Expression的原因 136 | 137 | FMaterialShadingModelField::AddShadingModel 存在check(InShadingModel < MSM_NUM)检查导致没法Hack,导致新增的ShadingModel数量肯定超过MSM_NUM,引发断言导致编辑器crash 138 | 139 | ## 插件支持ShadingModel的方式 140 | 141 | * ToonShader模块 142 | 该模块实现了**UMaterialExpressionCustomOutput**的子类,为运行时模块 143 | 144 | * ToonShaderBootstrap模块 145 | 该模块会在引擎启动Shader编译前加载,用以向虚幻引擎Shader文件夹中替换默认Shader文件 146 | ShadersOverride文件夹下存放了shader文件 147 | * Default文件用来恢复 148 | * Override用来做替换,新增的Shader文件都需要存放在Toon目录下 149 | -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/ShadingModelsMaterial.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | ShadingModelsMaterial.usf: Pixel shader function for computing a GBuffer from shading model. 5 | =============================================================================*/ 6 | 7 | #include "SubsurfaceProfileCommon.ush" 8 | 9 | // Optimization: if opacity is 0 then revert to default shading model 10 | #define SUBSURFACE_PROFILE_OPACITY_THRESHOLD 1 11 | 12 | void SetGBufferForShadingModel( 13 | in out FGBufferData GBuffer, 14 | in const FMaterialPixelParameters MaterialParameters, 15 | const float Opacity, 16 | const half3 BaseColor, 17 | const half Metallic, 18 | const half Specular, 19 | const float Roughness, 20 | const float Anisotropy, 21 | const float3 SubsurfaceColor, 22 | const float SubsurfaceProfile, 23 | const float Dither, 24 | const uint ShadingModel) 25 | { 26 | GBuffer.WorldNormal = MaterialParameters.WorldNormal; 27 | GBuffer.WorldTangent = MaterialParameters.WorldTangent; 28 | GBuffer.BaseColor = BaseColor; 29 | GBuffer.Metallic = Metallic; 30 | GBuffer.Specular = Specular; 31 | GBuffer.Roughness = Roughness; 32 | GBuffer.Anisotropy = Anisotropy; 33 | GBuffer.ShadingModelID = ShadingModel; 34 | 35 | // Calculate and set custom data for the shading models that need it 36 | 37 | // Dummy initial if statement to play nicely with the #ifdef logic 38 | if (false) 39 | { 40 | } 41 | #if MATERIAL_SHADINGMODEL_SUBSURFACE 42 | else if (ShadingModel == SHADINGMODELID_SUBSURFACE) 43 | { 44 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 45 | GBuffer.CustomData.a = Opacity; 46 | } 47 | #endif 48 | #if MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN 49 | else if (ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN) 50 | { 51 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 52 | GBuffer.CustomData.a = Opacity; 53 | } 54 | #endif 55 | #if MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE 56 | else if (ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE) 57 | { 58 | // Optimization: if opacity is 0 then revert to default shading model 59 | #if SUBSURFACE_PROFILE_OPACITY_THRESHOLD 60 | if (Opacity > SSSS_OPACITY_THRESHOLD_EPS) 61 | #endif 62 | { 63 | GBuffer.CustomData.rgb = EncodeSubsurfaceProfile(SubsurfaceProfile); 64 | GBuffer.CustomData.a = Opacity; 65 | 66 | // Average roughness for dual specular. 67 | uint SubsurfaceProfileUInt = uint(SubsurfaceProfile * 255.0f + 0.5f); 68 | float MaterialRoughnessToAverage = ActualSSProfilesTexture.Load(int3(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileUInt, 0)).w * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 69 | 70 | // 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 71 | // get to 0.01, as opposed to 0.0. 72 | MaterialRoughnessToAverage = lerp(1.0f, MaterialRoughnessToAverage, saturate((Opacity - SSSS_OPACITY_THRESHOLD_EPS) * 10.0f)); 73 | 74 | GBuffer.Roughness = saturate(GBuffer.Roughness * MaterialRoughnessToAverage); 75 | } 76 | #if SUBSURFACE_PROFILE_OPACITY_THRESHOLD 77 | else 78 | { 79 | GBuffer.ShadingModelID = SHADINGMODELID_DEFAULT_LIT; 80 | GBuffer.CustomData = 0; 81 | } 82 | #endif 83 | } 84 | #endif 85 | #if MATERIAL_SHADINGMODEL_CLEAR_COAT 86 | else if (ShadingModel == SHADINGMODELID_CLEAR_COAT) 87 | { 88 | float ClearCoat = saturate( GetMaterialCustomData0(MaterialParameters) ); 89 | float ClearCoatRoughness = saturate( GetMaterialCustomData1(MaterialParameters) ); 90 | GBuffer.CustomData.x = ClearCoat; 91 | GBuffer.CustomData.y = ClearCoatRoughness; 92 | 93 | // Clamp roughness to guarantee functional inverse when computing SphereSinAlpha for multiple layers 94 | GBuffer.Roughness = clamp(GBuffer.Roughness, 0.0, 254.0 / 255.0); 95 | 96 | #if CLEAR_COAT_BOTTOM_NORMAL 97 | { 98 | float2 oct2 = UnitVectorToOctahedron(GBuffer.WorldNormal); 99 | 100 | #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 101 | #if MATERIAL_TANGENTSPACENORMAL 102 | float3 tempnormal = normalize(TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, ClearCoatBottomNormal0(MaterialParameters) )); 103 | #else 104 | float3 tempnormal = ClearCoatBottomNormal0(MaterialParameters); 105 | #endif 106 | 107 | float2 oct1 = UnitVectorToOctahedron(tempnormal); 108 | float2 oct3 = ( (oct1 - oct2) * 0.5 ) + (128.0/255.0); 109 | GBuffer.CustomData.a = oct3.x; 110 | GBuffer.CustomData.z = oct3.y; 111 | #else 112 | GBuffer.CustomData.a = 128.0/255.0; 113 | GBuffer.CustomData.z = 128.0/255.0; 114 | #endif 115 | } 116 | #endif 117 | } 118 | #endif 119 | #if MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE 120 | else if (ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE) 121 | { 122 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 123 | GBuffer.CustomData.a = Opacity; 124 | } 125 | #endif 126 | #if MATERIAL_SHADINGMODEL_HAIR 127 | else if (ShadingModel == SHADINGMODELID_HAIR) 128 | { 129 | GBuffer.CustomData.xy = UnitVectorToOctahedron( MaterialParameters.WorldNormal ) * 0.5 + 0.5; 130 | GBuffer.CustomData.z = saturate( GetMaterialCustomData0(MaterialParameters) ); // Backlit 131 | } 132 | #endif 133 | #if MATERIAL_SHADINGMODEL_CLOTH 134 | else if (ShadingModel == SHADINGMODELID_CLOTH) 135 | { 136 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 137 | GBuffer.CustomData.a = saturate( GetMaterialCustomData0(MaterialParameters) ); // Cloth 138 | GBuffer.IndirectIrradiance *= 1 - GBuffer.CustomData.a; 139 | } 140 | #endif 141 | #if MATERIAL_SHADINGMODEL_EYE 142 | else if (ShadingModel == SHADINGMODELID_EYE) 143 | { 144 | GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x; 145 | GBuffer.CustomData.w = 1.0f - saturate(GetMaterialCustomData0(MaterialParameters)); // Opacity = 1.0 - Iris Mask 146 | GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance 147 | 148 | #if IRIS_NORMAL 149 | float IrisMask = saturate( GetMaterialCustomData0(MaterialParameters) ); 150 | float IrisDistance = saturate( GetMaterialCustomData1(MaterialParameters) ); 151 | 152 | GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x; 153 | GBuffer.CustomData.w = 1.0 - IrisMask; // Opacity 154 | 155 | float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal ); 156 | 157 | // CausticNormal stored as octahedron 158 | #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0 159 | // Blend in the negative intersection normal to create some concavity 160 | // Not great as it ties the concavity to the convexity of the cornea surface 161 | // No good justification for that. On the other hand, if we're just looking to 162 | // introduce some concavity, this does the job. 163 | float3 PlaneNormal = normalize( GetTangentOutput0(MaterialParameters) ); 164 | float3 CausticNormal = normalize( lerp( PlaneNormal, -GBuffer.WorldNormal, IrisMask*IrisDistance ) ); 165 | float2 CausticNormalOct = UnitVectorToOctahedron( CausticNormal ); 166 | float2 CausticNormalDelta = ( CausticNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0); 167 | GBuffer.Metallic = CausticNormalDelta.x; 168 | GBuffer.Specular = CausticNormalDelta.y; 169 | #else 170 | float3 PlaneNormal = GBuffer.WorldNormal; 171 | GBuffer.Metallic = 128.0/255.0; 172 | GBuffer.Specular = 128.0/255.0; 173 | #endif 174 | 175 | // IrisNormal CustomData.yz 176 | #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 177 | float3 IrisNormal = normalize( ClearCoatBottomNormal0(MaterialParameters) ); 178 | #if MATERIAL_TANGENTSPACENORMAL 179 | IrisNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, IrisNormal ) ); 180 | #endif 181 | #else 182 | float3 IrisNormal = PlaneNormal; 183 | #endif 184 | 185 | float2 IrisNormalOct = UnitVectorToOctahedron( IrisNormal ); 186 | float2 IrisNormalDelta = ( IrisNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0); 187 | GBuffer.CustomData.yz = IrisNormalDelta; 188 | #else 189 | GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance 190 | 191 | #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0 192 | float3 Tangent = GetTangentOutput0(MaterialParameters); 193 | GBuffer.CustomData.yz = UnitVectorToOctahedron( normalize(Tangent) ) * 0.5 + 0.5; 194 | #endif 195 | #endif 196 | } 197 | #endif 198 | } -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/ShadingModelsMaterial.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | ShadingModelsMaterial.usf: Pixel shader function for computing a GBuffer from shading model. 5 | =============================================================================*/ 6 | 7 | #include "SubsurfaceProfileCommon.ush" 8 | 9 | // Optimization: if opacity is 0 then revert to default shading model 10 | #define SUBSURFACE_PROFILE_OPACITY_THRESHOLD 1 11 | 12 | void SetGBufferForShadingModel( 13 | in out FGBufferData GBuffer, 14 | in const FMaterialPixelParameters MaterialParameters, 15 | const float Opacity, 16 | const half3 BaseColor, 17 | const half Metallic, 18 | const half Specular, 19 | const float Roughness, 20 | const float Anisotropy, 21 | const float3 SubsurfaceColor, 22 | const float SubsurfaceProfile, 23 | const float Dither, 24 | const uint ShadingModel) 25 | { 26 | GBuffer.WorldNormal = MaterialParameters.WorldNormal; 27 | GBuffer.WorldTangent = MaterialParameters.WorldTangent; 28 | GBuffer.BaseColor = BaseColor; 29 | GBuffer.Metallic = Metallic; 30 | GBuffer.Specular = Specular; 31 | GBuffer.Roughness = Roughness; 32 | GBuffer.Anisotropy = Anisotropy; 33 | GBuffer.ShadingModelID = ShadingModel; 34 | 35 | // Calculate and set custom data for the shading models that need it 36 | 37 | // Dummy initial if statement to play nicely with the #ifdef logic 38 | if (false) 39 | { 40 | } 41 | #if MATERIAL_SHADINGMODEL_SUBSURFACE 42 | else if (ShadingModel == SHADINGMODELID_SUBSURFACE) 43 | { 44 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 45 | GBuffer.CustomData.a = Opacity; 46 | } 47 | #endif 48 | #if MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN 49 | else if (ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN) 50 | { 51 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 52 | GBuffer.CustomData.a = Opacity; 53 | } 54 | #endif 55 | #if MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE 56 | else if (ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE) 57 | { 58 | // Optimization: if opacity is 0 then revert to default shading model 59 | #if SUBSURFACE_PROFILE_OPACITY_THRESHOLD 60 | if (Opacity > SSSS_OPACITY_THRESHOLD_EPS) 61 | #endif 62 | { 63 | GBuffer.CustomData.rgb = EncodeSubsurfaceProfile(SubsurfaceProfile); 64 | GBuffer.CustomData.a = Opacity; 65 | 66 | // Average roughness for dual specular. 67 | uint SubsurfaceProfileUInt = uint(SubsurfaceProfile * 255.0f + 0.5f); 68 | float MaterialRoughnessToAverage = ActualSSProfilesTexture.Load(int3(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileUInt, 0)).w * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 69 | 70 | // 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 71 | // get to 0.01, as opposed to 0.0. 72 | MaterialRoughnessToAverage = lerp(1.0f, MaterialRoughnessToAverage, saturate((Opacity - SSSS_OPACITY_THRESHOLD_EPS) * 10.0f)); 73 | 74 | GBuffer.Roughness = saturate(GBuffer.Roughness * MaterialRoughnessToAverage); 75 | } 76 | #if SUBSURFACE_PROFILE_OPACITY_THRESHOLD 77 | else 78 | { 79 | GBuffer.ShadingModelID = SHADINGMODELID_DEFAULT_LIT; 80 | GBuffer.CustomData = 0; 81 | } 82 | #endif 83 | } 84 | #endif 85 | #if MATERIAL_SHADINGMODEL_CLEAR_COAT 86 | else if (ShadingModel == SHADINGMODELID_CLEAR_COAT) 87 | { 88 | float ClearCoat = saturate( GetMaterialCustomData0(MaterialParameters) ); 89 | float ClearCoatRoughness = saturate( GetMaterialCustomData1(MaterialParameters) ); 90 | GBuffer.CustomData.x = ClearCoat; 91 | GBuffer.CustomData.y = ClearCoatRoughness; 92 | 93 | // Clamp roughness to guarantee functional inverse when computing SphereSinAlpha for multiple layers 94 | GBuffer.Roughness = clamp(GBuffer.Roughness, 0.0, 254.0 / 255.0); 95 | 96 | #if CLEAR_COAT_BOTTOM_NORMAL 97 | { 98 | float2 oct2 = UnitVectorToOctahedron(GBuffer.WorldNormal); 99 | 100 | #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 101 | #if MATERIAL_TANGENTSPACENORMAL 102 | float3 tempnormal = normalize(TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, ClearCoatBottomNormal0(MaterialParameters) )); 103 | #else 104 | float3 tempnormal = ClearCoatBottomNormal0(MaterialParameters); 105 | #endif 106 | 107 | float2 oct1 = UnitVectorToOctahedron(tempnormal); 108 | float2 oct3 = ( (oct1 - oct2) * 0.5 ) + (128.0/255.0); 109 | GBuffer.CustomData.a = oct3.x; 110 | GBuffer.CustomData.z = oct3.y; 111 | #else 112 | GBuffer.CustomData.a = 128.0/255.0; 113 | GBuffer.CustomData.z = 128.0/255.0; 114 | #endif 115 | } 116 | #endif 117 | } 118 | #endif 119 | #if MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE 120 | else if (ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE) 121 | { 122 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 123 | GBuffer.CustomData.a = Opacity; 124 | } 125 | #endif 126 | #if MATERIAL_SHADINGMODEL_HAIR 127 | else if (ShadingModel == SHADINGMODELID_HAIR) 128 | { 129 | GBuffer.CustomData.xy = UnitVectorToOctahedron( MaterialParameters.WorldNormal ) * 0.5 + 0.5; 130 | GBuffer.CustomData.z = saturate( GetMaterialCustomData0(MaterialParameters) ); // Backlit 131 | } 132 | #endif 133 | #if MATERIAL_SHADINGMODEL_CLOTH 134 | else if (ShadingModel == SHADINGMODELID_CLOTH) 135 | { 136 | GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor); 137 | GBuffer.CustomData.a = saturate( GetMaterialCustomData0(MaterialParameters) ); // Cloth 138 | GBuffer.IndirectIrradiance *= 1 - GBuffer.CustomData.a; 139 | } 140 | #endif 141 | #if MATERIAL_SHADINGMODEL_EYE 142 | else if (ShadingModel == SHADINGMODELID_EYE) 143 | { 144 | GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x; 145 | GBuffer.CustomData.w = 1.0f - saturate(GetMaterialCustomData0(MaterialParameters)); // Opacity = 1.0 - Iris Mask 146 | GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance 147 | 148 | #if IRIS_NORMAL 149 | float IrisMask = saturate( GetMaterialCustomData0(MaterialParameters) ); 150 | float IrisDistance = saturate( GetMaterialCustomData1(MaterialParameters) ); 151 | 152 | GBuffer.CustomData.x = EncodeSubsurfaceProfile(SubsurfaceProfile).x; 153 | GBuffer.CustomData.w = 1.0 - IrisMask; // Opacity 154 | 155 | float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal ); 156 | 157 | // CausticNormal stored as octahedron 158 | #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0 159 | // Blend in the negative intersection normal to create some concavity 160 | // Not great as it ties the concavity to the convexity of the cornea surface 161 | // No good justification for that. On the other hand, if we're just looking to 162 | // introduce some concavity, this does the job. 163 | float3 PlaneNormal = normalize( GetTangentOutput0(MaterialParameters) ); 164 | float3 CausticNormal = normalize( lerp( PlaneNormal, -GBuffer.WorldNormal, IrisMask*IrisDistance ) ); 165 | float2 CausticNormalOct = UnitVectorToOctahedron( CausticNormal ); 166 | float2 CausticNormalDelta = ( CausticNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0); 167 | GBuffer.Metallic = CausticNormalDelta.x; 168 | GBuffer.Specular = CausticNormalDelta.y; 169 | #else 170 | float3 PlaneNormal = GBuffer.WorldNormal; 171 | GBuffer.Metallic = 128.0/255.0; 172 | GBuffer.Specular = 128.0/255.0; 173 | #endif 174 | 175 | // IrisNormal CustomData.yz 176 | #if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0 177 | float3 IrisNormal = normalize( ClearCoatBottomNormal0(MaterialParameters) ); 178 | #if MATERIAL_TANGENTSPACENORMAL 179 | IrisNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, IrisNormal ) ); 180 | #endif 181 | #else 182 | float3 IrisNormal = PlaneNormal; 183 | #endif 184 | 185 | float2 IrisNormalOct = UnitVectorToOctahedron( IrisNormal ); 186 | float2 IrisNormalDelta = ( IrisNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0); 187 | GBuffer.CustomData.yz = IrisNormalDelta; 188 | #else 189 | GBuffer.Metallic = saturate(GetMaterialCustomData1(MaterialParameters)); // Iris Distance 190 | 191 | #if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0 192 | float3 Tangent = GetTangentOutput0(MaterialParameters); 193 | GBuffer.CustomData.yz = UnitVectorToOctahedron( normalize(Tangent) ) * 0.5 + 0.5; 194 | #endif 195 | #endif 196 | } 197 | #endif 198 | // 支持 MaterialExpressionCustomOutput 形式 199 | #if NUM_MATERIAL_OUTPUTS_GETTOONSHADING 200 | else if (ShadingModel == SHADINGMODELID_STYLIZED_SHADOW) 201 | { 202 | GBuffer.CustomData.x = saturate( GetToonShading0(MaterialParameters) ); // SpecularRange 203 | GBuffer.CustomData.y = saturate( GetToonShading1(MaterialParameters) ); // Offset 204 | } 205 | // 支持 ShadingModelId 形式 206 | #elif SHADINGMODELID_STYLIZED_SHADOW 207 | else if (ShadingModel == SHADINGMODELID_STYLIZED_SHADOW) 208 | { 209 | GBuffer.CustomData.x = saturate( GetMaterialCustomData0(MaterialParameters) ); // SpecularRange 210 | GBuffer.CustomData.y = saturate( GetMaterialCustomData1(MaterialParameters) ); // Offset 211 | } 212 | #endif 213 | } -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/BasePassCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | BasePassCommon.ush: Base pass definitions used by both vertex and pixel shader 5 | =============================================================================*/ 6 | 7 | // Defining SSProfilesTexture here overrides the default definition in SubsurfaceProfileCommon.ush 8 | #if MATERIALBLENDING_ANY_TRANSLUCENT 9 | #define ForwardLightData TranslucentBasePass.Shared.Forward 10 | #define ReflectionStruct TranslucentBasePass.Shared.Reflection 11 | #define PlanarReflectionStruct TranslucentBasePass.Shared.PlanarReflection 12 | #define FogStruct TranslucentBasePass.Shared.Fog 13 | #define ActualSSProfilesTexture TranslucentBasePass.Shared.SSProfilesTexture 14 | #else 15 | #define ForwardLightData OpaqueBasePass.Shared.Forward 16 | #define ReflectionStruct OpaqueBasePass.Shared.Reflection 17 | #define PlanarReflectionStruct OpaqueBasePass.Shared.PlanarReflection 18 | #define FogStruct OpaqueBasePass.Shared.Fog 19 | #define ActualSSProfilesTexture OpaqueBasePass.Shared.SSProfilesTexture 20 | #endif 21 | 22 | #undef NEEDS_LIGHTMAP_COORDINATE 23 | 24 | #define NEEDS_LIGHTMAP_COORDINATE (HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP) 25 | // Translucent materials need to compute fogging in the forward shading pass 26 | // Materials that read from scene color skip getting fogged, because the contents of the scene color lookup have already been fogged 27 | // This is not foolproof, as any additional color the material adds will then not be fogged correctly 28 | #define TRANSLUCENCY_NEEDS_BASEPASS_FOGGING (MATERIAL_ENABLE_TRANSLUCENCY_FOGGING && MATERIALBLENDING_ANY_TRANSLUCENT && !MATERIAL_USES_SCENE_COLOR_COPY) 29 | // With forward shading, fog always needs to be computed in the base pass to work correctly with MSAA 30 | #define OPAQUE_NEEDS_BASEPASS_FOGGING (!MATERIALBLENDING_ANY_TRANSLUCENT && FORWARD_SHADING) 31 | 32 | #define NEEDS_BASEPASS_VERTEX_FOGGING (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && !MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && PROJECT_VERTEX_FOGGING_FOR_OPAQUE) 33 | #define NEEDS_BASEPASS_PIXEL_FOGGING (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && !PROJECT_VERTEX_FOGGING_FOR_OPAQUE) 34 | 35 | // Volumetric fog interpolated per vertex gives very poor results, always sample the volumetric fog texture per-pixel 36 | // Opaque materials in the deferred renderer get volumetric fog applied in a deferred fog pass 37 | #define NEEDS_BASEPASS_PIXEL_VOLUMETRIC_FOGGING (MATERIALBLENDING_ANY_TRANSLUCENT || FORWARD_SHADING) 38 | 39 | #define NEEDS_LIGHTMAP (NEEDS_LIGHTMAP_COORDINATE) 40 | 41 | #define USES_GBUFFER (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING) 42 | 43 | // Only some shader models actually need custom data. 44 | #define WRITES_CUSTOMDATA_TO_GBUFFER (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE)) 45 | 46 | // Based on GetPrecomputedShadowMasks() 47 | // Note: WRITES_PRECSHADOWFACTOR_TO_GBUFFER is currently disabled because we use the precomputed shadow factor GBuffer outside of STATICLIGHTING_TEXTUREMASK to store UseSingleSampleShadowFromStationaryLights 48 | #define GBUFFER_HAS_PRECSHADOWFACTOR (USES_GBUFFER && ALLOW_STATIC_LIGHTING) 49 | #define WRITES_PRECSHADOWFACTOR_ZERO (!(STATICLIGHTING_TEXTUREMASK && STATICLIGHTING_SIGNEDDISTANCEFIELD) && (HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP)) 50 | #define WRITES_PRECSHADOWFACTOR_TO_GBUFFER (GBUFFER_HAS_PRECSHADOWFACTOR && !WRITES_PRECSHADOWFACTOR_ZERO) 51 | 52 | // If a primitive has static lighting, we assume it is not moving. If it is, it will be rerendered in an extra renderpass. 53 | #define SUPPORTS_WRITING_VELOCITY_TO_BASE_PASS (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !SIMPLE_FORWARD_SHADING) 54 | #define WRITES_VELOCITY_TO_GBUFFER ((SUPPORTS_WRITING_VELOCITY_TO_BASE_PASS || USES_GBUFFER) && GBUFFER_HAS_VELOCITY && (!SELECTIVE_BASEPASS_OUTPUTS || !(STATICLIGHTING_TEXTUREMASK || STATICLIGHTING_SIGNEDDISTANCEFIELD || HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP || WATER_MESH_FACTORY))) 55 | 56 | 57 | // Use an extra interpolator for position 58 | #define WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR (WRITES_VELOCITY_TO_GBUFFER && USING_TESSELLATION) 59 | 60 | #define TRANSLUCENCY_ANY_PERVERTEX_LIGHTING (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL) 61 | #define TRANSLUCENCY_ANY_VOLUMETRIC (TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL || TRANSLUCENCY_ANY_PERVERTEX_LIGHTING) 62 | #define TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME (!FORWARD_SHADING && (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL)) 63 | #define TRANSLUCENCY_PERVERTEX_FORWARD_SHADING (FORWARD_SHADING && (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL)) 64 | 65 | #define TESSELLATION_SUPPORTED (PC_D3D || SWITCH_PROFILE || SWITCH_PROFILE_FORWARD || METAL_TESSELLATION_PROFILE || VULKAN_PROFILE_SM5) 66 | 67 | struct FSharedBasePassInterpolants 68 | { 69 | //for texture-lightmapped translucency we can pass the vertex fog in its own interpolator 70 | #if NEEDS_BASEPASS_VERTEX_FOGGING 71 | float4 VertexFog : TEXCOORD7; 72 | #endif 73 | 74 | #if !TESSELLATION_SUPPORTED 75 | // Note: TEXCOORD8 is unused 76 | 77 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 78 | float3 PixelPositionExcludingWPO : TEXCOORD9; 79 | #endif 80 | #endif 81 | 82 | #if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME 83 | float3 AmbientLightingVector : TEXCOORD12; 84 | #endif 85 | 86 | #if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME && TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL 87 | float3 DirectionalLightingVector : TEXCOORD13; 88 | #endif 89 | 90 | #if TRANSLUCENCY_PERVERTEX_FORWARD_SHADING 91 | float3 VertexDiffuseLighting : TEXCOORD12; 92 | #endif 93 | 94 | #if PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING 95 | #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL 96 | float3 VertexIndirectAmbient : TEXCOORD14; 97 | #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL 98 | float4 VertexIndirectSH[3] : TEXCOORD14; 99 | #endif 100 | #endif 101 | 102 | #if WRITES_VELOCITY_TO_GBUFFER 103 | // .xy is clip position, pre divide by w; .w is clip W; .z is 0 or 1 to mask out the velocity output 104 | float4 VelocityPrevScreenPosition : VELOCITY_PREV_POS; 105 | #if WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR 106 | float4 VelocityScreenPosition : VELOCITY_POS; 107 | #endif 108 | #endif 109 | }; 110 | 111 | #if TESSELLATION_SUPPORTED 112 | 113 | /** Interpolants passed from the vertex shader to the pixel shader. */ 114 | struct FBasePassInterpolantsVSToPS : FSharedBasePassInterpolants 115 | { 116 | // Note: TEXCOORD8 is unused 117 | 118 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 119 | float3 PixelPositionExcludingWPO : TEXCOORD9; 120 | #endif 121 | }; 122 | 123 | /** Interpolants passed from the vertex shader to the domain shader. */ 124 | struct FBasePassInterpolantsVSToDS : FSharedBasePassInterpolants 125 | { 126 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 127 | float3 WorldPositionExcludingWPO : TEXCOORD9; 128 | #endif 129 | }; 130 | 131 | #else 132 | 133 | // Only the PC shader compiler supports HLSL inheritance 134 | #define FBasePassInterpolantsVSToPS FSharedBasePassInterpolants 135 | 136 | #endif 137 | 138 | 139 | #if SUPPORTS_INDEPENDENT_SAMPLERS 140 | #define SharedAmbientInnerSampler View.SharedBilinearClampedSampler 141 | #define SharedAmbientOuterSampler View.SharedBilinearClampedSampler 142 | #define SharedDirectionalInnerSampler View.SharedBilinearClampedSampler 143 | #define SharedDirectionalOuterSampler View.SharedBilinearClampedSampler 144 | #else 145 | #define SharedAmbientInnerSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientInnerSampler 146 | #define SharedAmbientOuterSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientOuterSampler 147 | #define SharedDirectionalInnerSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalInnerSampler 148 | #define SharedDirectionalOuterSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalOuterSampler 149 | #endif 150 | 151 | void ComputeVolumeUVs(float3 WorldPosition, float3 LightingPositionOffset, out float3 InnerVolumeUVs, out float3 OuterVolumeUVs, out float FinalLerpFactor) 152 | { 153 | // Apply a stable offset to the world position used for lighting, which breaks up artifacts from using a low res volume texture 154 | InnerVolumeUVs = (WorldPosition + LightingPositionOffset - View.TranslucencyLightingVolumeMin[0].xyz) * View.TranslucencyLightingVolumeInvSize[0].xyz; 155 | OuterVolumeUVs = (WorldPosition + LightingPositionOffset - View.TranslucencyLightingVolumeMin[1].xyz) * View.TranslucencyLightingVolumeInvSize[1].xyz; 156 | 157 | // Controls how fast the lerp between the inner and outer cascades happens 158 | // Larger values result in a shorter transition distance 159 | float TransitionScale = 6; 160 | // Setup a 3d lerp factor going to 0 at the edge of the inner volume 161 | float3 LerpFactors = saturate((.5f - abs(InnerVolumeUVs - .5f)) * TransitionScale); 162 | FinalLerpFactor = LerpFactors.x * LerpFactors.y * LerpFactors.z; 163 | } 164 | 165 | float4 GetAmbientLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor) 166 | { 167 | // Lookup the inner and outer cascade ambient lighting values 168 | float4 InnerLighting = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeAmbientInner, SharedAmbientInnerSampler, InnerVolumeUVs, 0); 169 | float4 OuterLighting = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeAmbientOuter, SharedAmbientOuterSampler, OuterVolumeUVs, 0); 170 | 171 | // Lerp between cascades 172 | return lerp(OuterLighting, InnerLighting, FinalLerpFactor); 173 | } 174 | 175 | float3 GetDirectionalLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor) 176 | { 177 | // Fetch both the ambient and directional values for both cascades 178 | float3 InnerVector1 = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeDirectionalInner, SharedDirectionalInnerSampler, InnerVolumeUVs, 0).rgb; 179 | float3 OuterVector1 = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeDirectionalOuter, SharedDirectionalOuterSampler, OuterVolumeUVs, 0).rgb; 180 | 181 | // Lerp between cascades 182 | return lerp(OuterVector1, InnerVector1, FinalLerpFactor); 183 | } 184 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/BasePassCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | BasePassCommon.ush: Base pass definitions used by both vertex and pixel shader 5 | =============================================================================*/ 6 | 7 | // Defining SSProfilesTexture here overrides the default definition in SubsurfaceProfileCommon.ush 8 | #if MATERIALBLENDING_ANY_TRANSLUCENT 9 | #define ForwardLightData TranslucentBasePass.Shared.Forward 10 | #define ReflectionStruct TranslucentBasePass.Shared.Reflection 11 | #define PlanarReflectionStruct TranslucentBasePass.Shared.PlanarReflection 12 | #define FogStruct TranslucentBasePass.Shared.Fog 13 | #define ActualSSProfilesTexture TranslucentBasePass.Shared.SSProfilesTexture 14 | #else 15 | #define ForwardLightData OpaqueBasePass.Shared.Forward 16 | #define ReflectionStruct OpaqueBasePass.Shared.Reflection 17 | #define PlanarReflectionStruct OpaqueBasePass.Shared.PlanarReflection 18 | #define FogStruct OpaqueBasePass.Shared.Fog 19 | #define ActualSSProfilesTexture OpaqueBasePass.Shared.SSProfilesTexture 20 | #endif 21 | 22 | #undef NEEDS_LIGHTMAP_COORDINATE 23 | 24 | #define NEEDS_LIGHTMAP_COORDINATE (HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP) 25 | // Translucent materials need to compute fogging in the forward shading pass 26 | // Materials that read from scene color skip getting fogged, because the contents of the scene color lookup have already been fogged 27 | // This is not foolproof, as any additional color the material adds will then not be fogged correctly 28 | #define TRANSLUCENCY_NEEDS_BASEPASS_FOGGING (MATERIAL_ENABLE_TRANSLUCENCY_FOGGING && MATERIALBLENDING_ANY_TRANSLUCENT && !MATERIAL_USES_SCENE_COLOR_COPY) 29 | // With forward shading, fog always needs to be computed in the base pass to work correctly with MSAA 30 | #define OPAQUE_NEEDS_BASEPASS_FOGGING (!MATERIALBLENDING_ANY_TRANSLUCENT && FORWARD_SHADING) 31 | 32 | #define NEEDS_BASEPASS_VERTEX_FOGGING (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && !MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && PROJECT_VERTEX_FOGGING_FOR_OPAQUE) 33 | #define NEEDS_BASEPASS_PIXEL_FOGGING (TRANSLUCENCY_NEEDS_BASEPASS_FOGGING && MATERIAL_COMPUTE_FOG_PER_PIXEL || OPAQUE_NEEDS_BASEPASS_FOGGING && !PROJECT_VERTEX_FOGGING_FOR_OPAQUE) 34 | 35 | // Volumetric fog interpolated per vertex gives very poor results, always sample the volumetric fog texture per-pixel 36 | // Opaque materials in the deferred renderer get volumetric fog applied in a deferred fog pass 37 | #define NEEDS_BASEPASS_PIXEL_VOLUMETRIC_FOGGING (MATERIALBLENDING_ANY_TRANSLUCENT || FORWARD_SHADING) 38 | 39 | #define NEEDS_LIGHTMAP (NEEDS_LIGHTMAP_COORDINATE) 40 | 41 | #define USES_GBUFFER (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING) 42 | 43 | // Only some shader models actually need custom data. 44 | #define WRITES_CUSTOMDATA_TO_GBUFFER (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE || SHADINGMODELID_STYLIZED_SHADOW)) 45 | 46 | // Based on GetPrecomputedShadowMasks() 47 | // Note: WRITES_PRECSHADOWFACTOR_TO_GBUFFER is currently disabled because we use the precomputed shadow factor GBuffer outside of STATICLIGHTING_TEXTUREMASK to store UseSingleSampleShadowFromStationaryLights 48 | #define GBUFFER_HAS_PRECSHADOWFACTOR (USES_GBUFFER && ALLOW_STATIC_LIGHTING) 49 | #define WRITES_PRECSHADOWFACTOR_ZERO (!(STATICLIGHTING_TEXTUREMASK && STATICLIGHTING_SIGNEDDISTANCEFIELD) && (HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP)) 50 | #define WRITES_PRECSHADOWFACTOR_TO_GBUFFER (GBUFFER_HAS_PRECSHADOWFACTOR && !WRITES_PRECSHADOWFACTOR_ZERO) 51 | 52 | // If a primitive has static lighting, we assume it is not moving. If it is, it will be rerendered in an extra renderpass. 53 | #define SUPPORTS_WRITING_VELOCITY_TO_BASE_PASS (FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && (MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED) && !SIMPLE_FORWARD_SHADING) 54 | #define WRITES_VELOCITY_TO_GBUFFER ((SUPPORTS_WRITING_VELOCITY_TO_BASE_PASS || USES_GBUFFER) && GBUFFER_HAS_VELOCITY && (!SELECTIVE_BASEPASS_OUTPUTS || !(STATICLIGHTING_TEXTUREMASK || STATICLIGHTING_SIGNEDDISTANCEFIELD || HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP || WATER_MESH_FACTORY))) 55 | 56 | 57 | // Use an extra interpolator for position 58 | #define WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR (WRITES_VELOCITY_TO_GBUFFER && USING_TESSELLATION) 59 | 60 | #define TRANSLUCENCY_ANY_PERVERTEX_LIGHTING (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL) 61 | #define TRANSLUCENCY_ANY_VOLUMETRIC (TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_DIRECTIONAL || TRANSLUCENCY_ANY_PERVERTEX_LIGHTING) 62 | #define TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME (!FORWARD_SHADING && (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL)) 63 | #define TRANSLUCENCY_PERVERTEX_FORWARD_SHADING (FORWARD_SHADING && (TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL)) 64 | 65 | #define TESSELLATION_SUPPORTED (PC_D3D || SWITCH_PROFILE || SWITCH_PROFILE_FORWARD || METAL_TESSELLATION_PROFILE || VULKAN_PROFILE_SM5) 66 | 67 | struct FSharedBasePassInterpolants 68 | { 69 | //for texture-lightmapped translucency we can pass the vertex fog in its own interpolator 70 | #if NEEDS_BASEPASS_VERTEX_FOGGING 71 | float4 VertexFog : TEXCOORD7; 72 | #endif 73 | 74 | #if !TESSELLATION_SUPPORTED 75 | // Note: TEXCOORD8 is unused 76 | 77 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 78 | float3 PixelPositionExcludingWPO : TEXCOORD9; 79 | #endif 80 | #endif 81 | 82 | #if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME 83 | float3 AmbientLightingVector : TEXCOORD12; 84 | #endif 85 | 86 | #if TRANSLUCENCY_PERVERTEX_LIGHTING_VOLUME && TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL 87 | float3 DirectionalLightingVector : TEXCOORD13; 88 | #endif 89 | 90 | #if TRANSLUCENCY_PERVERTEX_FORWARD_SHADING 91 | float3 VertexDiffuseLighting : TEXCOORD12; 92 | #endif 93 | 94 | #if PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING 95 | #if TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL 96 | float3 VertexIndirectAmbient : TEXCOORD14; 97 | #elif TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_DIRECTIONAL 98 | float4 VertexIndirectSH[3] : TEXCOORD14; 99 | #endif 100 | #endif 101 | 102 | #if WRITES_VELOCITY_TO_GBUFFER 103 | // .xy is clip position, pre divide by w; .w is clip W; .z is 0 or 1 to mask out the velocity output 104 | float4 VelocityPrevScreenPosition : VELOCITY_PREV_POS; 105 | #if WRITES_VELOCITY_TO_GBUFFER_USE_POS_INTERPOLATOR 106 | float4 VelocityScreenPosition : VELOCITY_POS; 107 | #endif 108 | #endif 109 | }; 110 | 111 | #if TESSELLATION_SUPPORTED 112 | 113 | /** Interpolants passed from the vertex shader to the pixel shader. */ 114 | struct FBasePassInterpolantsVSToPS : FSharedBasePassInterpolants 115 | { 116 | // Note: TEXCOORD8 is unused 117 | 118 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 119 | float3 PixelPositionExcludingWPO : TEXCOORD9; 120 | #endif 121 | }; 122 | 123 | /** Interpolants passed from the vertex shader to the domain shader. */ 124 | struct FBasePassInterpolantsVSToDS : FSharedBasePassInterpolants 125 | { 126 | #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS 127 | float3 WorldPositionExcludingWPO : TEXCOORD9; 128 | #endif 129 | }; 130 | 131 | #else 132 | 133 | // Only the PC shader compiler supports HLSL inheritance 134 | #define FBasePassInterpolantsVSToPS FSharedBasePassInterpolants 135 | 136 | #endif 137 | 138 | 139 | #if SUPPORTS_INDEPENDENT_SAMPLERS 140 | #define SharedAmbientInnerSampler View.SharedBilinearClampedSampler 141 | #define SharedAmbientOuterSampler View.SharedBilinearClampedSampler 142 | #define SharedDirectionalInnerSampler View.SharedBilinearClampedSampler 143 | #define SharedDirectionalOuterSampler View.SharedBilinearClampedSampler 144 | #else 145 | #define SharedAmbientInnerSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientInnerSampler 146 | #define SharedAmbientOuterSampler TranslucentBasePass.TranslucencyLightingVolumeAmbientOuterSampler 147 | #define SharedDirectionalInnerSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalInnerSampler 148 | #define SharedDirectionalOuterSampler TranslucentBasePass.TranslucencyLightingVolumeDirectionalOuterSampler 149 | #endif 150 | 151 | void ComputeVolumeUVs(float3 WorldPosition, float3 LightingPositionOffset, out float3 InnerVolumeUVs, out float3 OuterVolumeUVs, out float FinalLerpFactor) 152 | { 153 | // Apply a stable offset to the world position used for lighting, which breaks up artifacts from using a low res volume texture 154 | InnerVolumeUVs = (WorldPosition + LightingPositionOffset - View.TranslucencyLightingVolumeMin[0].xyz) * View.TranslucencyLightingVolumeInvSize[0].xyz; 155 | OuterVolumeUVs = (WorldPosition + LightingPositionOffset - View.TranslucencyLightingVolumeMin[1].xyz) * View.TranslucencyLightingVolumeInvSize[1].xyz; 156 | 157 | // Controls how fast the lerp between the inner and outer cascades happens 158 | // Larger values result in a shorter transition distance 159 | float TransitionScale = 6; 160 | // Setup a 3d lerp factor going to 0 at the edge of the inner volume 161 | float3 LerpFactors = saturate((.5f - abs(InnerVolumeUVs - .5f)) * TransitionScale); 162 | FinalLerpFactor = LerpFactors.x * LerpFactors.y * LerpFactors.z; 163 | } 164 | 165 | float4 GetAmbientLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor) 166 | { 167 | // Lookup the inner and outer cascade ambient lighting values 168 | float4 InnerLighting = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeAmbientInner, SharedAmbientInnerSampler, InnerVolumeUVs, 0); 169 | float4 OuterLighting = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeAmbientOuter, SharedAmbientOuterSampler, OuterVolumeUVs, 0); 170 | 171 | // Lerp between cascades 172 | return lerp(OuterLighting, InnerLighting, FinalLerpFactor); 173 | } 174 | 175 | float3 GetDirectionalLightingVectorFromTranslucentLightingVolume(float3 InnerVolumeUVs, float3 OuterVolumeUVs, float FinalLerpFactor) 176 | { 177 | // Fetch both the ambient and directional values for both cascades 178 | float3 InnerVector1 = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeDirectionalInner, SharedDirectionalInnerSampler, InnerVolumeUVs, 0).rgb; 179 | float3 OuterVector1 = Texture3DSampleLevel(TranslucentBasePass.TranslucencyLightingVolumeDirectionalOuter, SharedDirectionalOuterSampler, OuterVolumeUVs, 0).rgb; 180 | 181 | // Lerp between cascades 182 | return lerp(OuterVector1, InnerVector1, FinalLerpFactor); 183 | } 184 | -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/DeferredLightingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | DeferredLightingCommon.usf: Common definitions for deferred lighting. 5 | =============================================================================*/ 6 | 7 | #pragma once 8 | 9 | #include "DeferredShadingCommon.ush" 10 | #include "DynamicLightingCommon.ush" 11 | #include "IESLightProfilesCommon.ush" 12 | #include "CapsuleLightIntegrate.ush" 13 | #include "RectLightIntegrate.ush" 14 | 15 | /** 16 | * Data about a single light. 17 | * Putting the light data in this struct allows the same lighting code to be used between standard deferred, 18 | * Where many light properties are known at compile time, and tiled deferred, where all light properties have to be fetched from a buffer. 19 | */ 20 | // TODO: inherit or compose FLightShaderParameters 21 | struct FDeferredLightData 22 | { 23 | float3 Position; 24 | float InvRadius; 25 | float3 Color; 26 | float FalloffExponent; 27 | float3 Direction; 28 | float3 Tangent; 29 | float SoftSourceRadius; 30 | float2 SpotAngles; 31 | float SourceRadius; 32 | float SourceLength; 33 | float SpecularScale; 34 | float ContactShadowLength; 35 | /** Intensity of non-shadow-casting contact shadows */ 36 | float ContactShadowNonShadowCastingIntensity; 37 | float2 DistanceFadeMAD; 38 | float4 ShadowMapChannelMask; 39 | /** Whether ContactShadowLength is in World Space or in Screen Space. */ 40 | bool ContactShadowLengthInWS; 41 | /** Whether to use inverse squared falloff. */ 42 | bool bInverseSquared; 43 | /** Whether this is a light with radial attenuation, aka point or spot light. */ 44 | bool bRadialLight; 45 | /** Whether this light needs spotlight attenuation. */ 46 | bool bSpotLight; 47 | bool bRectLight; 48 | /** Whether the light should apply shadowing. */ 49 | uint ShadowedBits; 50 | float RectLightBarnCosAngle; 51 | float RectLightBarnLength; 52 | 53 | FHairTransmittanceData HairTransmittance; 54 | }; 55 | 56 | /** Data about a single light to be shaded with the simple shading model, designed for speed and limited feature set. */ 57 | struct FSimpleDeferredLightData 58 | { 59 | float3 Position; 60 | float InvRadius; 61 | float3 Color; 62 | float FalloffExponent; 63 | /** Whether to use inverse squared falloff. */ 64 | bool bInverseSquared; 65 | }; 66 | 67 | #define REFERENCE_QUALITY 0 68 | 69 | /** Returns 0 for positions closer than the fade near distance from the camera, and 1 for positions further than the fade far distance. */ 70 | float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData, float3 WorldPosition, float3 CameraPosition) 71 | { 72 | // depth (non radial) based fading over distance 73 | float Fade = saturate(SceneDepth * LightData.DistanceFadeMAD.x + LightData.DistanceFadeMAD.y); 74 | return Fade * Fade; 75 | } 76 | 77 | // Returns distance along ray that the first hit occurred, or negative on miss 78 | // Sets bOutHitCastDynamicShadow if the hit point is marked as a dynamic shadow caster 79 | float ShadowRayCast( 80 | float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength, 81 | int NumSteps, float StepOffset, out bool bOutHitCastContactShadow ) 82 | { 83 | float4 RayStartClip = mul( float4( RayOriginTranslatedWorld, 1 ), View.TranslatedWorldToClip ); 84 | float4 RayDirClip = mul( float4( RayDirection * RayLength, 0 ), View.TranslatedWorldToClip ); 85 | float4 RayEndClip = RayStartClip + RayDirClip; 86 | 87 | float3 RayStartScreen = RayStartClip.xyz / RayStartClip.w; 88 | float3 RayEndScreen = RayEndClip.xyz / RayEndClip.w; 89 | 90 | float3 RayStepScreen = RayEndScreen - RayStartScreen; 91 | 92 | float3 RayStartUVz = float3( RayStartScreen.xy * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz, RayStartScreen.z ); 93 | float3 RayStepUVz = float3( RayStepScreen.xy * View.ScreenPositionScaleBias.xy, RayStepScreen.z ); 94 | 95 | float4 RayDepthClip = RayStartClip + mul( float4( 0, 0, RayLength, 0 ), View.ViewToClip ); 96 | float3 RayDepthScreen = RayDepthClip.xyz / RayDepthClip.w; 97 | 98 | const float Step = 1.0 / NumSteps; 99 | 100 | // *2 to get less moire pattern in extreme cases, larger values make object appear not grounded in reflections 101 | const float CompareTolerance = abs( RayDepthScreen.z - RayStartScreen.z ) * Step * 2; 102 | 103 | float SampleTime = StepOffset * Step + Step; 104 | 105 | float FirstHitTime = -1.0; 106 | 107 | UNROLL 108 | for( int i = 0; i < NumSteps; i++ ) 109 | { 110 | float3 SampleUVz = RayStartUVz + RayStepUVz * SampleTime; 111 | float SampleDepth = SceneTexturesStruct.SceneDepthTexture.SampleLevel( SceneTexturesStruct_SceneDepthTextureSampler, SampleUVz.xy, 0 ).r; 112 | 113 | float DepthDiff = SampleUVz.z - SampleDepth; 114 | bool Hit = abs( DepthDiff + CompareTolerance ) < CompareTolerance; 115 | 116 | FirstHitTime = (Hit && FirstHitTime < 0.0) ? SampleTime : FirstHitTime; 117 | 118 | SampleTime += Step; 119 | } 120 | 121 | float HitDistance = -1.0; 122 | bOutHitCastContactShadow = false; 123 | if ( FirstHitTime > 0.0 ) 124 | { 125 | // Ignore hits that come from non-shadow-casting pixels 126 | float3 SampleUVz = RayStartUVz + RayStepUVz * FirstHitTime; 127 | FGBufferData SampleGBuffer = GetGBufferData( SampleUVz.xy ); 128 | bOutHitCastContactShadow = CastContactShadow( SampleGBuffer ); 129 | 130 | // Off screen masking 131 | float3 HitUVz = RayStartUVz + RayStepUVz * FirstHitTime; 132 | bool bValidUV = all( 0.0 < HitUVz.xy && HitUVz.xy < 1.0 ); 133 | HitDistance = bValidUV ? ( FirstHitTime * RayLength ) : -1.0; 134 | } 135 | 136 | return HitDistance; 137 | } 138 | 139 | #ifndef SUPPORT_CONTACT_SHADOWS 140 | #error "Must set SUPPORT_CONTACT_SHADOWS" 141 | #endif 142 | 143 | void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow) 144 | { 145 | float ContactShadowLength = 0.0f; 146 | const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth; 147 | 148 | BRANCH 149 | if (LightData.ShadowedBits) 150 | { 151 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 152 | 153 | // LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w, 154 | // Whole scene directional light shadows in x, whole scene directional light SSS shadows in y 155 | // Get static shadowing from the appropriate GBuffer channel 156 | float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1)); 157 | float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap); 158 | 159 | if (LightData.bRadialLight) 160 | { 161 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 162 | 163 | Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing; 164 | // SSS uses a separate shadowing term that allows light to penetrate the surface 165 | //@todo - how to do static shadowing of SSS correctly? 166 | Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing; 167 | 168 | Shadow.TransmissionThickness = LightAttenuation.w; 169 | } 170 | else 171 | { 172 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 173 | // Also fix up the fade between dynamic and static shadows 174 | // to work with plane splits rather than spheres. 175 | 176 | float DynamicShadowFraction = DistanceFromCameraFade(GBuffer.Depth, LightData, WorldPosition, View.WorldCameraOrigin); 177 | // For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows 178 | Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction); 179 | // Fade between SSS dynamic shadowing and static shadowing based on distance 180 | Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w); 181 | 182 | Shadow.SurfaceShadow *= LightAttenuation.z; 183 | Shadow.TransmissionShadow *= LightAttenuation.z; 184 | 185 | // Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light) 186 | Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w); 187 | } 188 | 189 | FLATTEN 190 | if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0) 191 | { 192 | ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale); 193 | } 194 | } 195 | 196 | #if SUPPORT_CONTACT_SHADOWS 197 | if (LightData.ShadowedBits < 2 && (GBuffer.ShadingModelID == SHADINGMODELID_HAIR)) 198 | { 199 | ContactShadowLength = 0.2 * ContactShadowLengthScreenScale; 200 | } 201 | // World space distance to cover eyelids and eyelashes but not beyond 202 | if (GBuffer.ShadingModelID == SHADINGMODELID_EYE) 203 | { 204 | ContactShadowLength = 0.5; 205 | 206 | } 207 | 208 | #if MATERIAL_CONTACT_SHADOWS 209 | ContactShadowLength = 0.2 * ContactShadowLengthScreenScale; 210 | #endif 211 | 212 | BRANCH 213 | if (ContactShadowLength > 0.0) 214 | { 215 | float StepOffset = Dither - 0.5; 216 | bool bHitCastContactShadow = false; 217 | float HitDistance = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset, bHitCastContactShadow ); 218 | 219 | if ( HitDistance > 0.0 ) 220 | { 221 | float ContactShadowOcclusion = bHitCastContactShadow ? 1.0 : LightData.ContactShadowNonShadowCastingIntensity; 222 | 223 | // Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated 224 | // thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior) 225 | BRANCH 226 | if (ContactShadowOcclusion > 0.0 && 227 | IsSubsurfaceModel(GBuffer.ShadingModelID) && 228 | GBuffer.ShadingModelID != SHADINGMODELID_HAIR && 229 | GBuffer.ShadingModelID != SHADINGMODELID_EYE && 230 | GBuffer.ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE) 231 | { 232 | // Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path 233 | // Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least 234 | // ensure that we don't darken-out the subsurface term with the contact shadows 235 | float Opacity = GBuffer.CustomData.a; 236 | float Density = SubsurfaceDensityFromOpacity( Opacity ); 237 | ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) ); 238 | } 239 | 240 | float ContactShadow = 1.0 - ContactShadowOcclusion; 241 | 242 | Shadow.SurfaceShadow *= ContactShadow; 243 | Shadow.TransmissionShadow *= ContactShadow; 244 | } 245 | 246 | } 247 | #endif 248 | 249 | Shadow.HairTransmittance = LightData.HairTransmittance; 250 | Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow; 251 | } 252 | 253 | float GetLocalLightAttenuation( 254 | float3 WorldPosition, 255 | FDeferredLightData LightData, 256 | inout float3 ToLight, 257 | inout float3 L) 258 | { 259 | ToLight = LightData.Position - WorldPosition; 260 | 261 | float DistanceSqr = dot( ToLight, ToLight ); 262 | L = ToLight * rsqrt( DistanceSqr ); 263 | 264 | float LightMask; 265 | if (LightData.bInverseSquared) 266 | { 267 | LightMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) ); 268 | } 269 | else 270 | { 271 | LightMask = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent); 272 | } 273 | 274 | if (LightData.bSpotLight) 275 | { 276 | LightMask *= SpotAttenuation(L, -LightData.Direction, LightData.SpotAngles); 277 | } 278 | 279 | if( LightData.bRectLight ) 280 | { 281 | // Rect normal points away from point 282 | LightMask = dot( LightData.Direction, L ) < 0 ? 0 : LightMask; 283 | } 284 | 285 | return LightMask; 286 | } 287 | 288 | #define RECLIGHT_BARNDOOR 1 289 | // Wrapper for FDeferredLightData for computing visible rect light (i.e., unoccluded by barn doors) 290 | FRect GetRect(float3 ToLight, FDeferredLightData LightData) 291 | { 292 | return GetRect( 293 | ToLight, 294 | LightData.Direction, 295 | LightData.Tangent, 296 | LightData.SourceRadius, 297 | LightData.SourceLength, 298 | LightData.RectLightBarnCosAngle, 299 | LightData.RectLightBarnLength, 300 | RECLIGHT_BARNDOOR); 301 | } 302 | 303 | FCapsuleLight GetCapsule( float3 ToLight, FDeferredLightData LightData ) 304 | { 305 | FCapsuleLight Capsule; 306 | Capsule.Length = LightData.SourceLength; 307 | Capsule.Radius = LightData.SourceRadius; 308 | Capsule.SoftRadius = LightData.SoftSourceRadius; 309 | Capsule.DistBiasSqr = 1; 310 | Capsule.LightPos[0] = ToLight - 0.5 * Capsule.Length * LightData.Tangent; 311 | Capsule.LightPos[1] = ToLight + 0.5 * Capsule.Length * LightData.Tangent; 312 | return Capsule; 313 | } 314 | 315 | /** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */ 316 | FDeferredLightingSplit GetDynamicLightingSplit( 317 | float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 318 | FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture, 319 | inout float SurfaceShadow) 320 | { 321 | FLightAccumulator LightAccumulator = (FLightAccumulator)0; 322 | 323 | float3 V = -CameraVector; 324 | float3 N = GBuffer.WorldNormal; 325 | BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL) 326 | { 327 | const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal); 328 | N = OctahedronToUnitVector(oct1); 329 | } 330 | 331 | float3 L = LightData.Direction; // Already normalized 332 | float3 ToLight = L; 333 | 334 | float LightMask = 1; 335 | if (LightData.bRadialLight) 336 | { 337 | LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L ); 338 | } 339 | 340 | LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost 341 | 342 | BRANCH 343 | if( LightMask > 0 ) 344 | { 345 | FShadowTerms Shadow; 346 | Shadow.SurfaceShadow = AmbientOcclusion; 347 | Shadow.TransmissionShadow = 1; 348 | Shadow.TransmissionThickness = 1; 349 | Shadow.HairTransmittance.OpaqueVisibility = 1; 350 | GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow); 351 | SurfaceShadow = Shadow.SurfaceShadow; 352 | 353 | LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms 354 | 355 | BRANCH 356 | if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 ) 357 | { 358 | const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID); 359 | float3 LightColor = LightData.Color; 360 | 361 | #if NON_DIRECTIONAL_DIRECT_LIGHTING 362 | float Lighting; 363 | 364 | if( LightData.bRectLight ) 365 | { 366 | FRect Rect = GetRect( ToLight, LightData ); 367 | 368 | Lighting = IntegrateLight( Rect, SourceTexture); 369 | } 370 | else 371 | { 372 | FCapsuleLight Capsule = GetCapsule( ToLight, LightData ); 373 | 374 | Lighting = IntegrateLight( Capsule, LightData.bInverseSquared ); 375 | } 376 | 377 | float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting; 378 | LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation); 379 | #else 380 | FDirectLighting Lighting; 381 | 382 | if (LightData.bRectLight) 383 | { 384 | FRect Rect = GetRect( ToLight, LightData ); 385 | 386 | #if REFERENCE_QUALITY 387 | Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos ); 388 | #else 389 | Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture); 390 | #endif 391 | } 392 | else 393 | { 394 | FCapsuleLight Capsule = GetCapsule( ToLight, LightData ); 395 | 396 | #if REFERENCE_QUALITY 397 | Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos ); 398 | #else 399 | Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared ); 400 | #endif 401 | } 402 | 403 | Lighting.Specular *= LightData.SpecularScale; 404 | 405 | LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation ); 406 | LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation ); 407 | 408 | LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light) 409 | #endif 410 | } 411 | } 412 | 413 | return LightAccumulator_GetResultSplit(LightAccumulator); 414 | } 415 | 416 | float4 GetDynamicLighting( 417 | float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 418 | FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture, 419 | inout float SurfaceShadow) 420 | { 421 | FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit( 422 | WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 423 | LightData, LightAttenuation, Dither, SVPos, SourceTexture, 424 | SurfaceShadow); 425 | 426 | return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting; 427 | } 428 | 429 | /** 430 | * Calculates lighting for a given position, normal, etc with a simple lighting model designed for speed. 431 | * All lights rendered through this method are unshadowed point lights with no shadowing or light function or IES. 432 | * A cheap specular is used instead of the more correct area specular, no fresnel. 433 | */ 434 | float3 GetSimpleDynamicLighting(float3 WorldPosition, float3 CameraVector, float3 WorldNormal, float AmbientOcclusion, float3 DiffuseColor, float3 SpecularColor, float Roughness, FSimpleDeferredLightData LightData) 435 | { 436 | float3 V = -CameraVector; 437 | float3 N = WorldNormal; 438 | float3 ToLight = LightData.Position - WorldPosition; 439 | float DistanceAttenuation = 1; 440 | 441 | float DistanceSqr = dot( ToLight, ToLight ); 442 | float3 L = ToLight * rsqrt( DistanceSqr ); 443 | float NoL = saturate( dot( N, L ) ); 444 | 445 | if (LightData.bInverseSquared) 446 | { 447 | // Sphere falloff (technically just 1/d2 but this avoids inf) 448 | DistanceAttenuation = 1 / ( DistanceSqr + 1 ); 449 | 450 | float LightRadiusMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) ); 451 | DistanceAttenuation *= LightRadiusMask; 452 | } 453 | else 454 | { 455 | DistanceAttenuation = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent); 456 | } 457 | 458 | float3 OutLighting = 0; 459 | 460 | BRANCH 461 | if (DistanceAttenuation > 0) 462 | { 463 | const float3 LightColor = LightData.Color; 464 | 465 | // Apply SSAO to the direct lighting since we're not going to have any other shadowing 466 | float Attenuation = DistanceAttenuation * AmbientOcclusion; 467 | 468 | #if NON_DIRECTIONAL_DIRECT_LIGHTING 469 | float3 VolumeLighting = Diffuse_Lambert(DiffuseColor); 470 | OutLighting += LightColor * Attenuation * VolumeLighting; 471 | #else 472 | OutLighting += LightColor * (NoL * Attenuation) * SimpleShading(DiffuseColor, SpecularColor, max(Roughness, .04f), L, V, N); 473 | #endif 474 | } 475 | 476 | return OutLighting; 477 | } 478 | 479 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/DeferredLightingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | DeferredLightingCommon.usf: Common definitions for deferred lighting. 5 | =============================================================================*/ 6 | 7 | #pragma once 8 | 9 | #include "DeferredShadingCommon.ush" 10 | #include "DynamicLightingCommon.ush" 11 | #include "IESLightProfilesCommon.ush" 12 | #include "CapsuleLightIntegrate.ush" 13 | #include "RectLightIntegrate.ush" 14 | 15 | /** 16 | * Data about a single light. 17 | * Putting the light data in this struct allows the same lighting code to be used between standard deferred, 18 | * Where many light properties are known at compile time, and tiled deferred, where all light properties have to be fetched from a buffer. 19 | */ 20 | // TODO: inherit or compose FLightShaderParameters 21 | struct FDeferredLightData 22 | { 23 | float3 Position; 24 | float InvRadius; 25 | float3 Color; 26 | float FalloffExponent; 27 | float3 Direction; 28 | float3 Tangent; 29 | float SoftSourceRadius; 30 | float2 SpotAngles; 31 | float SourceRadius; 32 | float SourceLength; 33 | float SpecularScale; 34 | float ContactShadowLength; 35 | /** Intensity of non-shadow-casting contact shadows */ 36 | float ContactShadowNonShadowCastingIntensity; 37 | float2 DistanceFadeMAD; 38 | float4 ShadowMapChannelMask; 39 | /** Whether ContactShadowLength is in World Space or in Screen Space. */ 40 | bool ContactShadowLengthInWS; 41 | /** Whether to use inverse squared falloff. */ 42 | bool bInverseSquared; 43 | /** Whether this is a light with radial attenuation, aka point or spot light. */ 44 | bool bRadialLight; 45 | /** Whether this light needs spotlight attenuation. */ 46 | bool bSpotLight; 47 | bool bRectLight; 48 | /** Whether the light should apply shadowing. */ 49 | uint ShadowedBits; 50 | float RectLightBarnCosAngle; 51 | float RectLightBarnLength; 52 | 53 | FHairTransmittanceData HairTransmittance; 54 | }; 55 | 56 | /** Data about a single light to be shaded with the simple shading model, designed for speed and limited feature set. */ 57 | struct FSimpleDeferredLightData 58 | { 59 | float3 Position; 60 | float InvRadius; 61 | float3 Color; 62 | float FalloffExponent; 63 | /** Whether to use inverse squared falloff. */ 64 | bool bInverseSquared; 65 | }; 66 | 67 | #define REFERENCE_QUALITY 0 68 | 69 | /** Returns 0 for positions closer than the fade near distance from the camera, and 1 for positions further than the fade far distance. */ 70 | float DistanceFromCameraFade(float SceneDepth, FDeferredLightData LightData, float3 WorldPosition, float3 CameraPosition) 71 | { 72 | // depth (non radial) based fading over distance 73 | float Fade = saturate(SceneDepth * LightData.DistanceFadeMAD.x + LightData.DistanceFadeMAD.y); 74 | return Fade * Fade; 75 | } 76 | 77 | // Returns distance along ray that the first hit occurred, or negative on miss 78 | // Sets bOutHitCastDynamicShadow if the hit point is marked as a dynamic shadow caster 79 | float ShadowRayCast( 80 | float3 RayOriginTranslatedWorld, float3 RayDirection, float RayLength, 81 | int NumSteps, float StepOffset, out bool bOutHitCastContactShadow ) 82 | { 83 | float4 RayStartClip = mul( float4( RayOriginTranslatedWorld, 1 ), View.TranslatedWorldToClip ); 84 | float4 RayDirClip = mul( float4( RayDirection * RayLength, 0 ), View.TranslatedWorldToClip ); 85 | float4 RayEndClip = RayStartClip + RayDirClip; 86 | 87 | float3 RayStartScreen = RayStartClip.xyz / RayStartClip.w; 88 | float3 RayEndScreen = RayEndClip.xyz / RayEndClip.w; 89 | 90 | float3 RayStepScreen = RayEndScreen - RayStartScreen; 91 | 92 | float3 RayStartUVz = float3( RayStartScreen.xy * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz, RayStartScreen.z ); 93 | float3 RayStepUVz = float3( RayStepScreen.xy * View.ScreenPositionScaleBias.xy, RayStepScreen.z ); 94 | 95 | float4 RayDepthClip = RayStartClip + mul( float4( 0, 0, RayLength, 0 ), View.ViewToClip ); 96 | float3 RayDepthScreen = RayDepthClip.xyz / RayDepthClip.w; 97 | 98 | const float Step = 1.0 / NumSteps; 99 | 100 | // *2 to get less moire pattern in extreme cases, larger values make object appear not grounded in reflections 101 | const float CompareTolerance = abs( RayDepthScreen.z - RayStartScreen.z ) * Step * 2; 102 | 103 | float SampleTime = StepOffset * Step + Step; 104 | 105 | float FirstHitTime = -1.0; 106 | 107 | UNROLL 108 | for( int i = 0; i < NumSteps; i++ ) 109 | { 110 | float3 SampleUVz = RayStartUVz + RayStepUVz * SampleTime; 111 | float SampleDepth = SceneTexturesStruct.SceneDepthTexture.SampleLevel( SceneTexturesStruct_SceneDepthTextureSampler, SampleUVz.xy, 0 ).r; 112 | 113 | float DepthDiff = SampleUVz.z - SampleDepth; 114 | bool Hit = abs( DepthDiff + CompareTolerance ) < CompareTolerance; 115 | 116 | FirstHitTime = (Hit && FirstHitTime < 0.0) ? SampleTime : FirstHitTime; 117 | 118 | SampleTime += Step; 119 | } 120 | 121 | float HitDistance = -1.0; 122 | bOutHitCastContactShadow = false; 123 | if ( FirstHitTime > 0.0 ) 124 | { 125 | // Ignore hits that come from non-shadow-casting pixels 126 | float3 SampleUVz = RayStartUVz + RayStepUVz * FirstHitTime; 127 | FGBufferData SampleGBuffer = GetGBufferData( SampleUVz.xy ); 128 | bOutHitCastContactShadow = CastContactShadow( SampleGBuffer ); 129 | 130 | // Off screen masking 131 | float3 HitUVz = RayStartUVz + RayStepUVz * FirstHitTime; 132 | bool bValidUV = all( 0.0 < HitUVz.xy && HitUVz.xy < 1.0 ); 133 | HitDistance = bValidUV ? ( FirstHitTime * RayLength ) : -1.0; 134 | } 135 | 136 | return HitDistance; 137 | } 138 | 139 | #ifndef SUPPORT_CONTACT_SHADOWS 140 | #error "Must set SUPPORT_CONTACT_SHADOWS" 141 | #endif 142 | 143 | void GetShadowTerms(FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float3 L, float4 LightAttenuation, float Dither, inout FShadowTerms Shadow) 144 | { 145 | float ContactShadowLength = 0.0f; 146 | const float ContactShadowLengthScreenScale = View.ClipToView[1][1] * GBuffer.Depth; 147 | 148 | BRANCH 149 | if (LightData.ShadowedBits) 150 | { 151 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 152 | 153 | // LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w, 154 | // Whole scene directional light shadows in x, whole scene directional light SSS shadows in y 155 | // Get static shadowing from the appropriate GBuffer channel 156 | float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1)); 157 | float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap); 158 | 159 | if (LightData.bRadialLight) 160 | { 161 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 162 | 163 | Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing; 164 | // SSS uses a separate shadowing term that allows light to penetrate the surface 165 | //@todo - how to do static shadowing of SSS correctly? 166 | Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing; 167 | 168 | Shadow.TransmissionThickness = LightAttenuation.w; 169 | } 170 | else 171 | { 172 | // Remapping the light attenuation buffer (see ShadowRendering.cpp) 173 | // Also fix up the fade between dynamic and static shadows 174 | // to work with plane splits rather than spheres. 175 | 176 | float DynamicShadowFraction = DistanceFromCameraFade(GBuffer.Depth, LightData, WorldPosition, View.WorldCameraOrigin); 177 | // For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows 178 | Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction); 179 | // Fade between SSS dynamic shadowing and static shadowing based on distance 180 | Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w); 181 | 182 | Shadow.SurfaceShadow *= LightAttenuation.z; 183 | Shadow.TransmissionShadow *= LightAttenuation.z; 184 | 185 | // Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light) 186 | Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w); 187 | } 188 | 189 | FLATTEN 190 | if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0) 191 | { 192 | ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale); 193 | } 194 | } 195 | 196 | #if SUPPORT_CONTACT_SHADOWS 197 | if (LightData.ShadowedBits < 2 && (GBuffer.ShadingModelID == SHADINGMODELID_HAIR)) 198 | { 199 | ContactShadowLength = 0.2 * ContactShadowLengthScreenScale; 200 | } 201 | // World space distance to cover eyelids and eyelashes but not beyond 202 | if (GBuffer.ShadingModelID == SHADINGMODELID_EYE) 203 | { 204 | ContactShadowLength = 0.5; 205 | 206 | } 207 | 208 | #if MATERIAL_CONTACT_SHADOWS 209 | ContactShadowLength = 0.2 * ContactShadowLengthScreenScale; 210 | #endif 211 | 212 | BRANCH 213 | if (ContactShadowLength > 0.0) 214 | { 215 | float StepOffset = Dither - 0.5; 216 | bool bHitCastContactShadow = false; 217 | float HitDistance = ShadowRayCast( WorldPosition + View.PreViewTranslation, L, ContactShadowLength, 8, StepOffset, bHitCastContactShadow ); 218 | 219 | if ( HitDistance > 0.0 ) 220 | { 221 | float ContactShadowOcclusion = bHitCastContactShadow ? 1.0 : LightData.ContactShadowNonShadowCastingIntensity; 222 | 223 | // Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated 224 | // thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior) 225 | BRANCH 226 | if (ContactShadowOcclusion > 0.0 && 227 | IsSubsurfaceModel(GBuffer.ShadingModelID) && 228 | GBuffer.ShadingModelID != SHADINGMODELID_HAIR && 229 | GBuffer.ShadingModelID != SHADINGMODELID_EYE && 230 | GBuffer.ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE) 231 | { 232 | // Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path 233 | // Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least 234 | // ensure that we don't darken-out the subsurface term with the contact shadows 235 | float Opacity = GBuffer.CustomData.a; 236 | float Density = SubsurfaceDensityFromOpacity( Opacity ); 237 | ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) ); 238 | } 239 | 240 | float ContactShadow = 1.0 - ContactShadowOcclusion; 241 | 242 | Shadow.SurfaceShadow *= ContactShadow; 243 | Shadow.TransmissionShadow *= ContactShadow; 244 | } 245 | 246 | } 247 | #endif 248 | 249 | Shadow.HairTransmittance = LightData.HairTransmittance; 250 | Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow; 251 | } 252 | 253 | float GetLocalLightAttenuation( 254 | float3 WorldPosition, 255 | FDeferredLightData LightData, 256 | inout float3 ToLight, 257 | inout float3 L) 258 | { 259 | ToLight = LightData.Position - WorldPosition; 260 | 261 | float DistanceSqr = dot( ToLight, ToLight ); 262 | L = ToLight * rsqrt( DistanceSqr ); 263 | 264 | float LightMask; 265 | if (LightData.bInverseSquared) 266 | { 267 | LightMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) ); 268 | } 269 | else 270 | { 271 | LightMask = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent); 272 | } 273 | 274 | if (LightData.bSpotLight) 275 | { 276 | LightMask *= SpotAttenuation(L, -LightData.Direction, LightData.SpotAngles); 277 | } 278 | 279 | if( LightData.bRectLight ) 280 | { 281 | // Rect normal points away from point 282 | LightMask = dot( LightData.Direction, L ) < 0 ? 0 : LightMask; 283 | } 284 | 285 | return LightMask; 286 | } 287 | 288 | #define RECLIGHT_BARNDOOR 1 289 | // Wrapper for FDeferredLightData for computing visible rect light (i.e., unoccluded by barn doors) 290 | FRect GetRect(float3 ToLight, FDeferredLightData LightData) 291 | { 292 | return GetRect( 293 | ToLight, 294 | LightData.Direction, 295 | LightData.Tangent, 296 | LightData.SourceRadius, 297 | LightData.SourceLength, 298 | LightData.RectLightBarnCosAngle, 299 | LightData.RectLightBarnLength, 300 | RECLIGHT_BARNDOOR); 301 | } 302 | 303 | FCapsuleLight GetCapsule( float3 ToLight, FDeferredLightData LightData ) 304 | { 305 | FCapsuleLight Capsule; 306 | Capsule.Length = LightData.SourceLength; 307 | Capsule.Radius = LightData.SourceRadius; 308 | Capsule.SoftRadius = LightData.SoftSourceRadius; 309 | Capsule.DistBiasSqr = 1; 310 | Capsule.LightPos[0] = ToLight - 0.5 * Capsule.Length * LightData.Tangent; 311 | Capsule.LightPos[1] = ToLight + 0.5 * Capsule.Length * LightData.Tangent; 312 | return Capsule; 313 | } 314 | 315 | /** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */ 316 | FDeferredLightingSplit GetDynamicLightingSplit( 317 | float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 318 | FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture, 319 | inout float SurfaceShadow) 320 | { 321 | FLightAccumulator LightAccumulator = (FLightAccumulator)0; 322 | 323 | float3 V = -CameraVector; 324 | float3 N = GBuffer.WorldNormal; 325 | BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL) 326 | { 327 | const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal); 328 | N = OctahedronToUnitVector(oct1); 329 | } 330 | 331 | float3 L = LightData.Direction; // Already normalized 332 | float3 ToLight = L; 333 | 334 | float LightMask = 1; 335 | if (LightData.bRadialLight) 336 | { 337 | LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L ); 338 | } 339 | 340 | LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost 341 | 342 | BRANCH 343 | if( LightMask > 0 ) 344 | { 345 | FShadowTerms Shadow; 346 | Shadow.SurfaceShadow = AmbientOcclusion; 347 | Shadow.TransmissionShadow = 1; 348 | Shadow.TransmissionThickness = 1; 349 | Shadow.HairTransmittance.OpaqueVisibility = 1; 350 | GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow); 351 | SurfaceShadow = Shadow.SurfaceShadow; 352 | 353 | LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms 354 | 355 | BRANCH 356 | if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 ) 357 | { 358 | const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID); 359 | float3 LightColor = LightData.Color; 360 | 361 | #if NON_DIRECTIONAL_DIRECT_LIGHTING 362 | float Lighting; 363 | 364 | if( LightData.bRectLight ) 365 | { 366 | FRect Rect = GetRect( ToLight, LightData ); 367 | 368 | Lighting = IntegrateLight( Rect, SourceTexture); 369 | } 370 | else 371 | { 372 | FCapsuleLight Capsule = GetCapsule( ToLight, LightData ); 373 | 374 | Lighting = IntegrateLight( Capsule, LightData.bInverseSquared ); 375 | } 376 | 377 | float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting; 378 | LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation); 379 | #else 380 | FDirectLighting Lighting; 381 | 382 | if (LightData.bRectLight) 383 | { 384 | FRect Rect = GetRect( ToLight, LightData ); 385 | 386 | #if REFERENCE_QUALITY 387 | Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos ); 388 | #else 389 | Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture); 390 | #endif 391 | } 392 | else 393 | { 394 | FCapsuleLight Capsule = GetCapsule( ToLight, LightData ); 395 | 396 | #if REFERENCE_QUALITY 397 | Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos ); 398 | #else 399 | Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared ); 400 | #endif 401 | } 402 | 403 | Lighting.Specular *= LightData.SpecularScale; 404 | 405 | // STYLIZEDSHADOW SHADING 406 | if (GBuffer.ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW) 407 | { 408 | float3 Attenuation = 1; 409 | 410 | float offset = GBuffer.CustomData.y; 411 | float TerminatorRange = saturate(GBuffer.Roughness - 0.5); 412 | 413 | offset = offset * 2 - 1; 414 | 415 | BRANCH 416 | if (offset >= 1) 417 | { 418 | Attenuation = 1; 419 | } 420 | else 421 | { 422 | float NoL = (dot(N, L) + 1) / 2; 423 | float NoLOffset = saturate(NoL + offset); 424 | float LightAttenuationOffset = saturate( Shadow.SurfaceShadow + offset); 425 | float ToonSurfaceShadow = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, LightAttenuationOffset); 426 | 427 | Attenuation = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, NoLOffset) * ToonSurfaceShadow; 428 | } 429 | 430 | LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation * 0.25, bNeedsSeparateSubsurfaceLightAccumulation); 431 | } 432 | else 433 | { 434 | LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation ); 435 | } 436 | LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation ); 437 | 438 | LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light) 439 | #endif 440 | } 441 | } 442 | 443 | return LightAccumulator_GetResultSplit(LightAccumulator); 444 | } 445 | 446 | float4 GetDynamicLighting( 447 | float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 448 | FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture, 449 | inout float SurfaceShadow) 450 | { 451 | FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit( 452 | WorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 453 | LightData, LightAttenuation, Dither, SVPos, SourceTexture, 454 | SurfaceShadow); 455 | 456 | return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting; 457 | } 458 | 459 | /** 460 | * Calculates lighting for a given position, normal, etc with a simple lighting model designed for speed. 461 | * All lights rendered through this method are unshadowed point lights with no shadowing or light function or IES. 462 | * A cheap specular is used instead of the more correct area specular, no fresnel. 463 | */ 464 | float3 GetSimpleDynamicLighting(float3 WorldPosition, float3 CameraVector, float3 WorldNormal, float AmbientOcclusion, float3 DiffuseColor, float3 SpecularColor, float Roughness, FSimpleDeferredLightData LightData) 465 | { 466 | float3 V = -CameraVector; 467 | float3 N = WorldNormal; 468 | float3 ToLight = LightData.Position - WorldPosition; 469 | float DistanceAttenuation = 1; 470 | 471 | float DistanceSqr = dot( ToLight, ToLight ); 472 | float3 L = ToLight * rsqrt( DistanceSqr ); 473 | float NoL = saturate( dot( N, L ) ); 474 | 475 | if (LightData.bInverseSquared) 476 | { 477 | // Sphere falloff (technically just 1/d2 but this avoids inf) 478 | DistanceAttenuation = 1 / ( DistanceSqr + 1 ); 479 | 480 | float LightRadiusMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) ); 481 | DistanceAttenuation *= LightRadiusMask; 482 | } 483 | else 484 | { 485 | DistanceAttenuation = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent); 486 | } 487 | 488 | float3 OutLighting = 0; 489 | 490 | BRANCH 491 | if (DistanceAttenuation > 0) 492 | { 493 | const float3 LightColor = LightData.Color; 494 | 495 | // Apply SSAO to the direct lighting since we're not going to have any other shadowing 496 | float Attenuation = DistanceAttenuation * AmbientOcclusion; 497 | 498 | #if NON_DIRECTIONAL_DIRECT_LIGHTING 499 | float3 VolumeLighting = Diffuse_Lambert(DiffuseColor); 500 | OutLighting += LightColor * Attenuation * VolumeLighting; 501 | #else 502 | OutLighting += LightColor * (NoL * Attenuation) * SimpleShading(DiffuseColor, SpecularColor, max(Roughness, .04f), L, V, N); 503 | #endif 504 | } 505 | 506 | return OutLighting; 507 | } 508 | 509 | -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/DeferredShadingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | DeferredShadingCommon.usf: Common definitions for deferred shading. 5 | =============================================================================*/ 6 | 7 | #pragma once 8 | 9 | #include "ShadingCommon.ush" 10 | #include "LightAccumulator.ush" 11 | #include "SceneTexturesCommon.ush" 12 | 13 | // TODO: for CustomGBufferResolvePS() MSAA_SAMPLE_COUNT is defined by C++ code as 2 or 4 14 | // bot not for any other shaders! 15 | #ifndef MSAA_SAMPLE_COUNT 16 | #define MSAA_SAMPLE_COUNT 2 17 | #endif 18 | 19 | float3 RGBToYCoCg( float3 RGB ) 20 | { 21 | float Y = dot( RGB, float3( 1, 2, 1 ) ) * 0.25; 22 | float Co = dot( RGB, float3( 2, 0, -2 ) ) * 0.25 + ( 0.5 * 256.0 / 255.0 ); 23 | float Cg = dot( RGB, float3( -1, 2, -1 ) ) * 0.25 + ( 0.5 * 256.0 / 255.0 ); 24 | 25 | float3 YCoCg = float3( Y, Co, Cg ); 26 | return YCoCg; 27 | } 28 | 29 | float3 YCoCgToRGB( float3 YCoCg ) 30 | { 31 | float Y = YCoCg.x; 32 | float Co = YCoCg.y - ( 0.5 * 256.0 / 255.0 ); 33 | float Cg = YCoCg.z - ( 0.5 * 256.0 / 255.0 ); 34 | 35 | float R = Y + Co - Cg; 36 | float G = Y + Cg; 37 | float B = Y - Co - Cg; 38 | 39 | float3 RGB = float3( R, G, B ); 40 | return RGB; 41 | } 42 | 43 | // Octahedron Normal Vectors 44 | // [Cigolle 2014, "A Survey of Efficient Representations for Independent Unit Vectors"] 45 | // Mean Max 46 | // oct 8:8 0.33709 0.94424 47 | // snorm 8:8:8 0.17015 0.38588 48 | // oct 10:10 0.08380 0.23467 49 | // snorm 10:10:10 0.04228 0.09598 50 | // oct 12:12 0.02091 0.05874 51 | 52 | float2 UnitVectorToOctahedron( float3 N ) 53 | { 54 | N.xy /= dot( 1, abs(N) ); 55 | if( N.z <= 0 ) 56 | { 57 | N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? float2(1,1) : float2(-1,-1) ); 58 | } 59 | return N.xy; 60 | } 61 | 62 | float3 OctahedronToUnitVector( float2 Oct ) 63 | { 64 | float3 N = float3( Oct, 1 - dot( 1, abs(Oct) ) ); 65 | if( N.z < 0 ) 66 | { 67 | N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? float2(1,1) : float2(-1,-1) ); 68 | } 69 | return normalize(N); 70 | } 71 | 72 | float2 UnitVectorToHemiOctahedron( float3 N ) 73 | { 74 | N.xy /= dot( 1, abs(N) ); 75 | return float2( N.x + N.y, N.x - N.y ); 76 | } 77 | 78 | float3 HemiOctahedronToUnitVector( float2 Oct ) 79 | { 80 | Oct = float2( Oct.x + Oct.y, Oct.x - Oct.y ) * 0.5; 81 | float3 N = float3( Oct, 1 - dot( 1, abs(Oct) ) ); 82 | return normalize(N); 83 | } 84 | 85 | float3 Pack1212To888( float2 x ) 86 | { 87 | // Pack 12:12 to 8:8:8 88 | #if 0 89 | uint2 x1212 = (uint2)( x * 4095.0 ); 90 | uint2 High = x1212 >> 8; 91 | uint2 Low = x1212 & 255; 92 | uint3 x888 = uint3( Low, High.x | (High.y << 4) ); 93 | return x888 / 255.0; 94 | #else 95 | float2 x1212 = floor( x * 4095 ); 96 | float2 High = floor( x1212 / 256 ); // x1212 >> 8 97 | float2 Low = x1212 - High * 256; // x1212 & 255 98 | float3 x888 = float3( Low, High.x + High.y * 16 ); 99 | return saturate( x888 / 255 ); 100 | #endif 101 | } 102 | 103 | float2 Pack888To1212( float3 x ) 104 | { 105 | // Pack 8:8:8 to 12:12 106 | #if 0 107 | uint3 x888 = (uint3)( x * 255.0 ); 108 | uint High = x888.z >> 4; 109 | uint Low = x888.z & 15; 110 | uint2 x1212 = x888.xy | uint2( Low << 8, High << 8 ); 111 | return x1212 / 4095.0; 112 | #else 113 | float3 x888 = floor( x * 255 ); 114 | float High = floor( x888.z / 16 ); // x888.z >> 4 115 | float Low = x888.z - High * 16; // x888.z & 15 116 | float2 x1212 = x888.xy + float2( Low, High ) * 256; 117 | return saturate( x1212 / 4095 ); 118 | #endif 119 | } 120 | 121 | float3 EncodeNormal( float3 N ) 122 | { 123 | return N * 0.5 + 0.5; 124 | //return Pack1212To888( UnitVectorToOctahedron( N ) * 0.5 + 0.5 ); 125 | } 126 | 127 | float3 DecodeNormal( float3 N ) 128 | { 129 | return N * 2 - 1; 130 | //return OctahedronToUnitVector( Pack888To1212( N ) * 2 - 1 ); 131 | } 132 | 133 | void EncodeNormal( inout float3 N, out uint Face ) 134 | { 135 | #if 1 136 | uint Axis = 2; 137 | if( abs(N.x) >= abs(N.y) && abs(N.x) >= abs(N.z) ) 138 | { 139 | Axis = 0; 140 | } 141 | else if( abs(N.y) > abs(N.z) ) 142 | { 143 | Axis = 1; 144 | } 145 | Face = Axis * 2; 146 | #else 147 | // TODO GCN 148 | Face = v_cubeid_f32( N ); 149 | uint Axis = Face >> 1; 150 | #endif 151 | 152 | N = Axis == 0 ? N.yzx : N; 153 | N = Axis == 1 ? N.xzy : N; 154 | 155 | float MaxAbs = 1.0 / sqrt(2.0); 156 | 157 | Face += N.z > 0 ? 0 : 1; 158 | N.xy *= N.z > 0 ? 1 : -1; 159 | N.xy = N.xy * (0.5 / MaxAbs) + 0.5; 160 | } 161 | 162 | void DecodeNormal( inout float3 N, in uint Face ) 163 | { 164 | uint Axis = Face >> 1; 165 | 166 | float MaxAbs = 1.0 / sqrt(2.0); 167 | 168 | N.xy = N.xy * (2 * MaxAbs) - (1 * MaxAbs); 169 | N.z = sqrt( 1 - dot( N.xy, N.xy ) ); 170 | 171 | N = Axis == 0 ? N.zxy : N; 172 | N = Axis == 1 ? N.xzy : N; 173 | N *= (Face & 1) ? -1 : 1; 174 | } 175 | 176 | float3 EncodeBaseColor(float3 BaseColor) 177 | { 178 | // we use sRGB on the render target to give more precision to the darks 179 | return BaseColor; 180 | } 181 | 182 | float3 DecodeBaseColor(float3 BaseColor) 183 | { 184 | // we use sRGB on the render target to give more precision to the darks 185 | return BaseColor; 186 | } 187 | 188 | float3 EncodeSubsurfaceColor(float3 SubsurfaceColor) 189 | { 190 | return sqrt(saturate(SubsurfaceColor)); 191 | } 192 | 193 | // @param SubsurfaceProfile 0..1, SubsurfaceProfileId = int(x * 255) 194 | float3 EncodeSubsurfaceProfile(float SubsurfaceProfile) 195 | { 196 | return float3(SubsurfaceProfile, 0, 0); 197 | } 198 | 199 | // Derive density from a heuristic using opacity, tweaked for useful falloff ranges and to give a linear depth falloff with opacity 200 | float SubsurfaceDensityFromOpacity(float Opacity) 201 | { 202 | return (-0.05f * log(1.0f - min(Opacity, 0.999f))); 203 | } 204 | 205 | float EncodeIndirectIrradiance(float IndirectIrradiance) 206 | { 207 | float L = IndirectIrradiance; 208 | #if USE_PREEXPOSURE 209 | L *= View.PreExposure; // Apply pre-exposure as a mean to prevent compression overflow. 210 | #endif 211 | const float LogBlackPoint = 0.00390625; // exp2(-8); 212 | return log2( L + LogBlackPoint ) / 16 + 0.5; 213 | } 214 | 215 | float DecodeIndirectIrradiance(float IndirectIrradiance) 216 | { 217 | #if USE_PREEXPOSURE 218 | const float OneOverPreExposure = View.OneOverPreExposure; 219 | #else 220 | const float OneOverPreExposure = 1.f; 221 | #endif 222 | 223 | // LogL -> L 224 | float LogL = IndirectIrradiance; 225 | const float LogBlackPoint = 0.00390625; // exp2(-8); 226 | return OneOverPreExposure * (exp2( LogL * 16 - 8 ) - LogBlackPoint); // 1 exp2, 1 smad, 1 ssub 227 | } 228 | 229 | float4 EncodeWorldTangentAndAnisotropy(float3 WorldTangent, float Anisotropy) 230 | { 231 | return float4( 232 | EncodeNormal(WorldTangent), 233 | Anisotropy * 0.5f + 0.5f 234 | ); 235 | } 236 | 237 | float ComputeAngleFromRoughness( float Roughness, const float Threshold = 0.04f ) 238 | { 239 | #if 1 240 | float Angle = 3 * Square( Roughness ); 241 | #else 242 | const float LogThreshold = log2( Threshold ); 243 | float Power = 0.5 / pow( Roughness, 4 ) - 0.5; 244 | float Angle = acos( exp2( LogThreshold / Power ) ); 245 | #endif 246 | return Angle; 247 | } 248 | 249 | float ComputeRoughnessFromAngle( float Angle, const float Threshold = 0.04f ) 250 | { 251 | #if 1 252 | float Roughness = sqrt( 0.33333 * Angle ); 253 | #else 254 | const float LogThreshold = log2( Threshold ); 255 | float Power = LogThreshold / log2( cos( Angle ) ); 256 | float Roughness = sqrt( sqrt( 2 / (Power * 4 + 2) ) ); 257 | #endif 258 | return Roughness; 259 | } 260 | 261 | float AddAngleToRoughness( float Angle, float Roughness ) 262 | { 263 | return saturate( sqrt( Square( Roughness ) + 0.33333 * Angle ) ); 264 | } 265 | 266 | // @param Scalar clamped in 0..1 range 267 | // @param Mask 0..1 268 | // @return 8bit in range 0..1 269 | float Encode71(float Scalar, uint Mask) 270 | { 271 | return 272 | 127.0f / 255.0f * saturate(Scalar) + 273 | 128.0f / 255.0f * Mask; 274 | } 275 | 276 | // 8bit reinterpretation as 7bit,1bit 277 | // @param Scalar 0..1 278 | // @param Mask 0..1 279 | // @return 7bit in 0.1 280 | float Decode71(float Scalar, out uint Mask) 281 | { 282 | Mask = (uint)(Scalar > 0.5f); 283 | 284 | return (Scalar - 0.5f * Mask) * 2.0f; 285 | } 286 | 287 | float EncodeShadingModelIdAndSelectiveOutputMask(uint ShadingModelId, uint SelectiveOutputMask) 288 | { 289 | uint Value = (ShadingModelId & SHADINGMODELID_MASK) | SelectiveOutputMask; 290 | return (float)Value / (float)0xFF; 291 | } 292 | 293 | uint DecodeShadingModelId(float InPackedChannel) 294 | { 295 | return ((uint)round(InPackedChannel * (float)0xFF)) & SHADINGMODELID_MASK; 296 | } 297 | 298 | uint DecodeSelectiveOutputMask(float InPackedChannel) 299 | { 300 | return ((uint)round(InPackedChannel * (float)0xFF)) & ~SHADINGMODELID_MASK; 301 | } 302 | 303 | bool IsSubsurfaceModel(int ShadingModel) 304 | { 305 | return ShadingModel == SHADINGMODELID_SUBSURFACE 306 | || ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN 307 | || ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE 308 | || ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE 309 | || ShadingModel == SHADINGMODELID_HAIR 310 | || ShadingModel == SHADINGMODELID_EYE; 311 | } 312 | 313 | bool UseSubsurfaceProfile(int ShadingModel) 314 | { 315 | return ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE || ShadingModel == SHADINGMODELID_EYE; 316 | } 317 | 318 | bool HasCustomGBufferData(int ShadingModelID) 319 | { 320 | return ShadingModelID == SHADINGMODELID_SUBSURFACE 321 | || ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN 322 | || ShadingModelID == SHADINGMODELID_CLEAR_COAT 323 | || ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE 324 | || ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE 325 | || ShadingModelID == SHADINGMODELID_HAIR 326 | || ShadingModelID == SHADINGMODELID_CLOTH 327 | || ShadingModelID == SHADINGMODELID_EYE; 328 | } 329 | 330 | bool HasAnisotropy(int SelectiveOutputMask) 331 | { 332 | return (SelectiveOutputMask & HAS_ANISOTROPY_MASK) != 0; 333 | } 334 | 335 | // all values that are output by the forward rendering pass 336 | struct FGBufferData 337 | { 338 | // normalized 339 | float3 WorldNormal; 340 | // normalized, only valid if HAS_ANISOTROPY_MASK in SelectiveOutputMask 341 | float3 WorldTangent; 342 | // 0..1 (derived from BaseColor, Metalness, Specular) 343 | float3 DiffuseColor; 344 | // 0..1 (derived from BaseColor, Metalness, Specular) 345 | float3 SpecularColor; 346 | // 0..1, white for SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE (apply BaseColor after scattering is more correct and less blurry) 347 | float3 BaseColor; 348 | // 0..1 349 | float Metallic; 350 | // 0..1 351 | float Specular; 352 | // 0..1 353 | float4 CustomData; 354 | // Indirect irradiance luma 355 | float IndirectIrradiance; 356 | // Static shadow factors for channels assigned by Lightmass 357 | // Lights using static shadowing will pick up the appropriate channel in their deferred pass 358 | float4 PrecomputedShadowFactors; 359 | // 0..1 360 | float Roughness; 361 | // -1..1, only valid if only valid if HAS_ANISOTROPY_MASK in SelectiveOutputMask 362 | float Anisotropy; 363 | // 0..1 ambient occlusion e.g.SSAO, wet surface mask, skylight mask, ... 364 | float GBufferAO; 365 | // 0..255 366 | uint ShadingModelID; 367 | // 0..255 368 | uint SelectiveOutputMask; 369 | // 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract 370 | float PerObjectGBufferData; 371 | // in world units 372 | float CustomDepth; 373 | // Custom depth stencil value 374 | uint CustomStencil; 375 | // in unreal units (linear), can be used to reconstruct world position, 376 | // only valid when decoding the GBuffer as the value gets reconstructed from the Z buffer 377 | float Depth; 378 | // Velocity for motion blur (only used when WRITES_VELOCITY_TO_GBUFFER is enabled) 379 | float4 Velocity; 380 | 381 | // 0..1, only needed by SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE which apply BaseColor later 382 | float3 StoredBaseColor; 383 | // 0..1, only needed by SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE which apply Specular later 384 | float StoredSpecular; 385 | // 0..1, only needed by SHADINGMODELID_EYE which encodes Iris Distance inside Metallic 386 | float StoredMetallic; 387 | }; 388 | 389 | bool CastContactShadow(FGBufferData GBufferData) 390 | { 391 | uint PackedAlpha = (uint)(GBufferData.PerObjectGBufferData * 3.999f); 392 | bool bCastContactShadowBit = PackedAlpha & 1; 393 | // Exclude eye materials from ever casting contact shadows 394 | bool bShadingModelCastContactShadows = (GBufferData.ShadingModelID != SHADINGMODELID_EYE); 395 | return bCastContactShadowBit && bShadingModelCastContactShadows; 396 | } 397 | 398 | bool HasDynamicIndirectShadowCasterRepresentation(FGBufferData GBufferData) 399 | { 400 | uint PackedAlpha = (uint)(GBufferData.PerObjectGBufferData * 3.999f); 401 | return (PackedAlpha & 2) != 0; 402 | } 403 | 404 | struct FScreenSpaceData 405 | { 406 | // GBuffer (material attributes from forward rendering pass) 407 | FGBufferData GBuffer; 408 | // 0..1, only valid in some passes, 1 if off 409 | float AmbientOcclusion; 410 | }; 411 | 412 | /** Sets up the Gbuffer for an unlit material. */ 413 | void SetGBufferForUnlit(out float4 OutGBufferB) 414 | { 415 | OutGBufferB = 0; 416 | OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(SHADINGMODELID_UNLIT, 0); 417 | } 418 | 419 | /** Populates OutGBufferA, B and C */ 420 | void EncodeGBuffer( 421 | FGBufferData GBuffer, 422 | out float4 OutGBufferA, 423 | out float4 OutGBufferB, 424 | out float4 OutGBufferC, 425 | out float4 OutGBufferD, 426 | out float4 OutGBufferE, 427 | out float4 OutGBufferVelocity, 428 | float QuantizationBias = 0 // -0.5 to 0.5 random float. Used to bias quantization. 429 | ) 430 | { 431 | if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT) 432 | { 433 | OutGBufferA = 0; 434 | SetGBufferForUnlit(OutGBufferB); 435 | OutGBufferC = 0; 436 | OutGBufferD = 0; 437 | OutGBufferE = 0; 438 | } 439 | else 440 | { 441 | #if MOBILE_DEFERRED_SHADING 442 | OutGBufferA.rg = UnitVectorToOctahedron( normalize(GBuffer.WorldNormal) ) * 0.5f + 0.5f; 443 | OutGBufferA.b = GBuffer.PrecomputedShadowFactors.x; 444 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 445 | #elif 1 446 | OutGBufferA.rgb = EncodeNormal( GBuffer.WorldNormal ); 447 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 448 | #else 449 | float3 Normal = GBuffer.WorldNormal; 450 | uint NormalFace = 0; 451 | EncodeNormal( Normal, NormalFace ); 452 | 453 | OutGBufferA.rg = Normal.xy; 454 | OutGBufferA.b = 0; 455 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 456 | #endif 457 | 458 | OutGBufferB.r = GBuffer.Metallic; 459 | OutGBufferB.g = GBuffer.Specular; 460 | OutGBufferB.b = GBuffer.Roughness; 461 | OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask); 462 | 463 | OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor ); 464 | 465 | #if ALLOW_STATIC_LIGHTING 466 | // No space for AO. Multiply IndirectIrradiance by AO instead of storing. 467 | OutGBufferC.a = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0); 468 | #else 469 | OutGBufferC.a = GBuffer.GBufferAO; 470 | #endif 471 | 472 | OutGBufferD = GBuffer.CustomData; 473 | OutGBufferE = GBuffer.PrecomputedShadowFactors; 474 | } 475 | 476 | #if WRITES_VELOCITY_TO_GBUFFER 477 | OutGBufferVelocity = GBuffer.Velocity; 478 | #else 479 | OutGBufferVelocity = 0; 480 | #endif 481 | } 482 | 483 | // High frequency Checkerboard pattern 484 | // @param PixelPos relative to left top of the rendertarget (not viewport) 485 | // @return true/false, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 486 | bool CheckerFromPixelPos(uint2 PixelPos) 487 | { 488 | // todo: Index is float and by staying float we can optimize this 489 | // We alternate the pattern to get 2x supersampling on the lower res data to get more near to full res 490 | uint TemporalAASampleIndex = View.TemporalAAParams.x; 491 | 492 | #if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 493 | return (PixelPos.x + PixelPos.y + TemporalAASampleIndex) % 2; 494 | #else 495 | return (uint)(fmod(PixelPos.x + PixelPos.y + TemporalAASampleIndex, 2)) != 0; 496 | #endif 497 | } 498 | 499 | // High frequency Checkerboard pattern 500 | // @param UVSceneColor at pixel center 501 | // @return true/false, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 502 | bool CheckerFromSceneColorUV(float2 UVSceneColor) 503 | { 504 | // relative to left top of the rendertarget (not viewport) 505 | uint2 PixelPos = uint2(UVSceneColor * View.BufferSizeAndInvSize.xy); 506 | 507 | return CheckerFromPixelPos(PixelPos); 508 | } 509 | 510 | // SubsurfaceProfile does deferred lighting with a checker board pixel pattern 511 | // we separate the view from the non view dependent lighting and later recombine the two color constributions in a postprocess 512 | // We have the option to apply the BaseColor/Specular in the base pass or do it later in the postprocess (has implications to texture detail, fresnel and performance) 513 | void AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(inout float3 BaseColor, inout float3 SpecularColor, inout float Specular, bool bChecker) 514 | { 515 | #if SUBSURFACE_CHANNEL_MODE == 0 516 | // If SUBSURFACE_CHANNEL_MODE is 0, we can't support full-resolution lighting, so we 517 | // ignore View.bCheckerboardSubsurfaceProfileRendering 518 | const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0; 519 | #else 520 | const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0; 521 | BaseColor = View.bSubsurfacePostprocessEnabled ? float3(1, 1, 1) : BaseColor; 522 | #endif 523 | if (bCheckerboardRequired) 524 | { 525 | // because we adjust the BaseColor here, we need StoredBaseColor 526 | 527 | // we apply the base color later in SubsurfaceRecombinePS() 528 | BaseColor = bChecker; 529 | // in SubsurfaceRecombinePS() does not multiply with Specular so we do it here 530 | SpecularColor *= !bChecker; 531 | Specular *= !bChecker; 532 | } 533 | } 534 | 535 | /** Populates FGBufferData */ 536 | // @param bChecker High frequency Checkerboard pattern computed with one of the CheckerFrom.. functions, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 537 | FGBufferData DecodeGBufferData( 538 | float4 InGBufferA, 539 | float4 InGBufferB, 540 | float4 InGBufferC, 541 | float4 InGBufferD, 542 | float4 InGBufferE, 543 | float4 InGBufferF, 544 | float4 InGBufferVelocity, 545 | float CustomNativeDepth, 546 | uint CustomStencil, 547 | float SceneDepth, 548 | bool bGetNormalizedNormal, 549 | bool bChecker) 550 | { 551 | FGBufferData GBuffer; 552 | 553 | GBuffer.WorldNormal = DecodeNormal( InGBufferA.xyz ); 554 | if(bGetNormalizedNormal) 555 | { 556 | GBuffer.WorldNormal = normalize(GBuffer.WorldNormal); 557 | } 558 | 559 | GBuffer.PerObjectGBufferData = InGBufferA.a; 560 | GBuffer.Metallic = InGBufferB.r; 561 | GBuffer.Specular = InGBufferB.g; 562 | GBuffer.Roughness = InGBufferB.b; 563 | // Note: must match GetShadingModelId standalone function logic 564 | // Also Note: SimpleElementPixelShader directly sets SV_Target2 ( GBufferB ) to indicate unlit. 565 | // An update there will be required if this layout changes. 566 | GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a); 567 | GBuffer.SelectiveOutputMask = DecodeSelectiveOutputMask(InGBufferB.a); 568 | 569 | GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb); 570 | 571 | #if ALLOW_STATIC_LIGHTING 572 | GBuffer.GBufferAO = 1; 573 | GBuffer.IndirectIrradiance = DecodeIndirectIrradiance(InGBufferC.a); 574 | #else 575 | GBuffer.GBufferAO = InGBufferC.a; 576 | GBuffer.IndirectIrradiance = 1; 577 | #endif 578 | 579 | GBuffer.CustomData = HasCustomGBufferData(GBuffer.ShadingModelID) ? InGBufferD : 0; 580 | 581 | GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? InGBufferE : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 : 1); 582 | GBuffer.CustomDepth = ConvertFromDeviceZ(CustomNativeDepth); 583 | GBuffer.CustomStencil = CustomStencil; 584 | GBuffer.Depth = SceneDepth; 585 | 586 | GBuffer.StoredBaseColor = GBuffer.BaseColor; 587 | GBuffer.StoredMetallic = GBuffer.Metallic; 588 | GBuffer.StoredSpecular = GBuffer.Specular; 589 | 590 | FLATTEN 591 | if( GBuffer.ShadingModelID == SHADINGMODELID_EYE ) 592 | { 593 | GBuffer.Metallic = 0.0; 594 | #if IRIS_NORMAL 595 | GBuffer.Specular = 0.25; 596 | #endif 597 | } 598 | 599 | // derived from BaseColor, Metalness, Specular 600 | { 601 | GBuffer.SpecularColor = ComputeF0(GBuffer.Specular, GBuffer.BaseColor, GBuffer.Metallic); 602 | 603 | if (UseSubsurfaceProfile(GBuffer.ShadingModelID)) 604 | { 605 | AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(GBuffer.BaseColor, GBuffer.SpecularColor, GBuffer.Specular, bChecker); 606 | } 607 | 608 | GBuffer.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic; 609 | 610 | #if USE_DEVELOPMENT_SHADERS 611 | { 612 | // this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help) 613 | GBuffer.DiffuseColor = GBuffer.DiffuseColor * View.DiffuseOverrideParameter.www + View.DiffuseOverrideParameter.xyz; 614 | GBuffer.SpecularColor = GBuffer.SpecularColor * View.SpecularOverrideParameter.w + View.SpecularOverrideParameter.xyz; 615 | } 616 | #endif //USE_DEVELOPMENT_SHADERS 617 | } 618 | 619 | { 620 | bool bHasAnisoProp = HasAnisotropy(GBuffer.SelectiveOutputMask); 621 | 622 | GBuffer.WorldTangent = bHasAnisoProp ? DecodeNormal(InGBufferF.rgb) : 0; 623 | GBuffer.Anisotropy = bHasAnisoProp ? InGBufferF.a * 2.0f - 1.0f : 0; 624 | 625 | if (bGetNormalizedNormal && bHasAnisoProp) 626 | { 627 | GBuffer.WorldTangent = normalize(GBuffer.WorldTangent); 628 | } 629 | } 630 | 631 | GBuffer.Velocity = !(GBuffer.SelectiveOutputMask & SKIP_VELOCITY_MASK) ? InGBufferVelocity : 0; 632 | 633 | return GBuffer; 634 | } 635 | 636 | float3 ExtractSubsurfaceColor(FGBufferData BufferData) 637 | { 638 | return Square(BufferData.CustomData.rgb); 639 | } 640 | 641 | uint ExtractSubsurfaceProfileInt(FGBufferData BufferData) 642 | { 643 | // can be optimized 644 | return uint(BufferData.CustomData.r * 255.0f + 0.5f); 645 | } 646 | 647 | #if SHADING_PATH_DEFERRED 648 | 649 | #if FEATURE_LEVEL >= FEATURE_LEVEL_SM5 650 | // @param PixelPos relative to left top of the rendertarget (not viewport) 651 | FGBufferData GetGBufferDataUint(uint2 PixelPos, bool bGetNormalizedNormal = true) 652 | { 653 | float4 GBufferA = SceneTexturesStruct.GBufferATexture.Load(int3(PixelPos, 0)); 654 | float4 GBufferB = SceneTexturesStruct.GBufferBTexture.Load(int3(PixelPos, 0)); 655 | float4 GBufferC = SceneTexturesStruct.GBufferCTexture.Load(int3(PixelPos, 0)); 656 | float4 GBufferD = SceneTexturesStruct.GBufferDTexture.Load(int3(PixelPos, 0)); 657 | float CustomNativeDepth = SceneTexturesStruct.CustomDepthTexture.Load(int3(PixelPos, 0)).r; 658 | uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(PixelPos, 0)) STENCIL_COMPONENT_SWIZZLE; 659 | 660 | #if ALLOW_STATIC_LIGHTING 661 | float4 GBufferE = SceneTexturesStruct.GBufferETexture.Load(int3(PixelPos, 0)); 662 | #else 663 | float4 GBufferE = 1; 664 | #endif 665 | 666 | float4 GBufferF = SceneTexturesStruct.GBufferFTexture.Load(int3(PixelPos, 0)); 667 | 668 | #if WRITES_VELOCITY_TO_GBUFFER 669 | float4 GBufferVelocity = SceneTexturesStruct.GBufferVelocityTexture.Load(int3(PixelPos, 0)); 670 | #else 671 | float4 GBufferVelocity = 0; 672 | #endif 673 | 674 | float SceneDepth = CalcSceneDepth(PixelPos); 675 | 676 | return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromPixelPos(PixelPos)); 677 | } 678 | 679 | // @param PixelPos relative to left top of the rendertarget (not viewport) 680 | FScreenSpaceData GetScreenSpaceDataUint(uint2 PixelPos, bool bGetNormalizedNormal = true) 681 | { 682 | FScreenSpaceData Out; 683 | 684 | Out.GBuffer = GetGBufferDataUint(PixelPos, bGetNormalizedNormal); 685 | 686 | float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct_ScreenSpaceAOTextureSampler, (PixelPos + 0.5f) * View.BufferSizeAndInvSize.zw, 0); 687 | Out.AmbientOcclusion = ScreenSpaceAO.r; 688 | 689 | return Out; 690 | } 691 | #endif 692 | 693 | // @param UV - UV space in the GBuffer textures (BufferSize resolution) 694 | FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true) 695 | { 696 | float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV, 0); 697 | float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0); 698 | float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0); 699 | float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct_GBufferDTextureSampler, UV, 0); 700 | float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct_CustomDepthTextureSampler, UV, 0).r; 701 | 702 | int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy); 703 | uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE; 704 | 705 | #if ALLOW_STATIC_LIGHTING 706 | float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct_GBufferETextureSampler, UV, 0); 707 | #else 708 | float4 GBufferE = 1; 709 | #endif 710 | 711 | float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct_GBufferFTextureSampler, UV, 0); 712 | 713 | #if WRITES_VELOCITY_TO_GBUFFER 714 | float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct_GBufferVelocityTextureSampler, UV, 0); 715 | #else 716 | float4 GBufferVelocity = 0; 717 | #endif 718 | 719 | float SceneDepth = CalcSceneDepth(UV); 720 | 721 | return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV)); 722 | } 723 | 724 | // Minimal path for just the lighting model, used to branch around unlit pixels (skybox) 725 | uint GetShadingModelId(float2 UV) 726 | { 727 | return DecodeShadingModelId(Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0).a); 728 | } 729 | 730 | // @param UV - UV space in the GBuffer textures (BufferSize resolution) 731 | FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true) 732 | { 733 | FScreenSpaceData Out; 734 | 735 | Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal); 736 | float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct_ScreenSpaceAOTextureSampler, UV, 0); 737 | 738 | Out.AmbientOcclusion = ScreenSpaceAO.r; 739 | 740 | return Out; 741 | } 742 | 743 | #endif 744 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/DeferredShadingCommon.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | DeferredShadingCommon.usf: Common definitions for deferred shading. 5 | =============================================================================*/ 6 | 7 | #pragma once 8 | 9 | #include "ShadingCommon.ush" 10 | #include "LightAccumulator.ush" 11 | #include "SceneTexturesCommon.ush" 12 | 13 | // TODO: for CustomGBufferResolvePS() MSAA_SAMPLE_COUNT is defined by C++ code as 2 or 4 14 | // bot not for any other shaders! 15 | #ifndef MSAA_SAMPLE_COUNT 16 | #define MSAA_SAMPLE_COUNT 2 17 | #endif 18 | 19 | float3 RGBToYCoCg( float3 RGB ) 20 | { 21 | float Y = dot( RGB, float3( 1, 2, 1 ) ) * 0.25; 22 | float Co = dot( RGB, float3( 2, 0, -2 ) ) * 0.25 + ( 0.5 * 256.0 / 255.0 ); 23 | float Cg = dot( RGB, float3( -1, 2, -1 ) ) * 0.25 + ( 0.5 * 256.0 / 255.0 ); 24 | 25 | float3 YCoCg = float3( Y, Co, Cg ); 26 | return YCoCg; 27 | } 28 | 29 | float3 YCoCgToRGB( float3 YCoCg ) 30 | { 31 | float Y = YCoCg.x; 32 | float Co = YCoCg.y - ( 0.5 * 256.0 / 255.0 ); 33 | float Cg = YCoCg.z - ( 0.5 * 256.0 / 255.0 ); 34 | 35 | float R = Y + Co - Cg; 36 | float G = Y + Cg; 37 | float B = Y - Co - Cg; 38 | 39 | float3 RGB = float3( R, G, B ); 40 | return RGB; 41 | } 42 | 43 | // Octahedron Normal Vectors 44 | // [Cigolle 2014, "A Survey of Efficient Representations for Independent Unit Vectors"] 45 | // Mean Max 46 | // oct 8:8 0.33709 0.94424 47 | // snorm 8:8:8 0.17015 0.38588 48 | // oct 10:10 0.08380 0.23467 49 | // snorm 10:10:10 0.04228 0.09598 50 | // oct 12:12 0.02091 0.05874 51 | 52 | float2 UnitVectorToOctahedron( float3 N ) 53 | { 54 | N.xy /= dot( 1, abs(N) ); 55 | if( N.z <= 0 ) 56 | { 57 | N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? float2(1,1) : float2(-1,-1) ); 58 | } 59 | return N.xy; 60 | } 61 | 62 | float3 OctahedronToUnitVector( float2 Oct ) 63 | { 64 | float3 N = float3( Oct, 1 - dot( 1, abs(Oct) ) ); 65 | if( N.z < 0 ) 66 | { 67 | N.xy = ( 1 - abs(N.yx) ) * ( N.xy >= 0 ? float2(1,1) : float2(-1,-1) ); 68 | } 69 | return normalize(N); 70 | } 71 | 72 | float2 UnitVectorToHemiOctahedron( float3 N ) 73 | { 74 | N.xy /= dot( 1, abs(N) ); 75 | return float2( N.x + N.y, N.x - N.y ); 76 | } 77 | 78 | float3 HemiOctahedronToUnitVector( float2 Oct ) 79 | { 80 | Oct = float2( Oct.x + Oct.y, Oct.x - Oct.y ) * 0.5; 81 | float3 N = float3( Oct, 1 - dot( 1, abs(Oct) ) ); 82 | return normalize(N); 83 | } 84 | 85 | float3 Pack1212To888( float2 x ) 86 | { 87 | // Pack 12:12 to 8:8:8 88 | #if 0 89 | uint2 x1212 = (uint2)( x * 4095.0 ); 90 | uint2 High = x1212 >> 8; 91 | uint2 Low = x1212 & 255; 92 | uint3 x888 = uint3( Low, High.x | (High.y << 4) ); 93 | return x888 / 255.0; 94 | #else 95 | float2 x1212 = floor( x * 4095 ); 96 | float2 High = floor( x1212 / 256 ); // x1212 >> 8 97 | float2 Low = x1212 - High * 256; // x1212 & 255 98 | float3 x888 = float3( Low, High.x + High.y * 16 ); 99 | return saturate( x888 / 255 ); 100 | #endif 101 | } 102 | 103 | float2 Pack888To1212( float3 x ) 104 | { 105 | // Pack 8:8:8 to 12:12 106 | #if 0 107 | uint3 x888 = (uint3)( x * 255.0 ); 108 | uint High = x888.z >> 4; 109 | uint Low = x888.z & 15; 110 | uint2 x1212 = x888.xy | uint2( Low << 8, High << 8 ); 111 | return x1212 / 4095.0; 112 | #else 113 | float3 x888 = floor( x * 255 ); 114 | float High = floor( x888.z / 16 ); // x888.z >> 4 115 | float Low = x888.z - High * 16; // x888.z & 15 116 | float2 x1212 = x888.xy + float2( Low, High ) * 256; 117 | return saturate( x1212 / 4095 ); 118 | #endif 119 | } 120 | 121 | float3 EncodeNormal( float3 N ) 122 | { 123 | return N * 0.5 + 0.5; 124 | //return Pack1212To888( UnitVectorToOctahedron( N ) * 0.5 + 0.5 ); 125 | } 126 | 127 | float3 DecodeNormal( float3 N ) 128 | { 129 | return N * 2 - 1; 130 | //return OctahedronToUnitVector( Pack888To1212( N ) * 2 - 1 ); 131 | } 132 | 133 | void EncodeNormal( inout float3 N, out uint Face ) 134 | { 135 | #if 1 136 | uint Axis = 2; 137 | if( abs(N.x) >= abs(N.y) && abs(N.x) >= abs(N.z) ) 138 | { 139 | Axis = 0; 140 | } 141 | else if( abs(N.y) > abs(N.z) ) 142 | { 143 | Axis = 1; 144 | } 145 | Face = Axis * 2; 146 | #else 147 | // TODO GCN 148 | Face = v_cubeid_f32( N ); 149 | uint Axis = Face >> 1; 150 | #endif 151 | 152 | N = Axis == 0 ? N.yzx : N; 153 | N = Axis == 1 ? N.xzy : N; 154 | 155 | float MaxAbs = 1.0 / sqrt(2.0); 156 | 157 | Face += N.z > 0 ? 0 : 1; 158 | N.xy *= N.z > 0 ? 1 : -1; 159 | N.xy = N.xy * (0.5 / MaxAbs) + 0.5; 160 | } 161 | 162 | void DecodeNormal( inout float3 N, in uint Face ) 163 | { 164 | uint Axis = Face >> 1; 165 | 166 | float MaxAbs = 1.0 / sqrt(2.0); 167 | 168 | N.xy = N.xy * (2 * MaxAbs) - (1 * MaxAbs); 169 | N.z = sqrt( 1 - dot( N.xy, N.xy ) ); 170 | 171 | N = Axis == 0 ? N.zxy : N; 172 | N = Axis == 1 ? N.xzy : N; 173 | N *= (Face & 1) ? -1 : 1; 174 | } 175 | 176 | float3 EncodeBaseColor(float3 BaseColor) 177 | { 178 | // we use sRGB on the render target to give more precision to the darks 179 | return BaseColor; 180 | } 181 | 182 | float3 DecodeBaseColor(float3 BaseColor) 183 | { 184 | // we use sRGB on the render target to give more precision to the darks 185 | return BaseColor; 186 | } 187 | 188 | float3 EncodeSubsurfaceColor(float3 SubsurfaceColor) 189 | { 190 | return sqrt(saturate(SubsurfaceColor)); 191 | } 192 | 193 | // @param SubsurfaceProfile 0..1, SubsurfaceProfileId = int(x * 255) 194 | float3 EncodeSubsurfaceProfile(float SubsurfaceProfile) 195 | { 196 | return float3(SubsurfaceProfile, 0, 0); 197 | } 198 | 199 | // Derive density from a heuristic using opacity, tweaked for useful falloff ranges and to give a linear depth falloff with opacity 200 | float SubsurfaceDensityFromOpacity(float Opacity) 201 | { 202 | return (-0.05f * log(1.0f - min(Opacity, 0.999f))); 203 | } 204 | 205 | float EncodeIndirectIrradiance(float IndirectIrradiance) 206 | { 207 | float L = IndirectIrradiance; 208 | #if USE_PREEXPOSURE 209 | L *= View.PreExposure; // Apply pre-exposure as a mean to prevent compression overflow. 210 | #endif 211 | const float LogBlackPoint = 0.00390625; // exp2(-8); 212 | return log2( L + LogBlackPoint ) / 16 + 0.5; 213 | } 214 | 215 | float DecodeIndirectIrradiance(float IndirectIrradiance) 216 | { 217 | #if USE_PREEXPOSURE 218 | const float OneOverPreExposure = View.OneOverPreExposure; 219 | #else 220 | const float OneOverPreExposure = 1.f; 221 | #endif 222 | 223 | // LogL -> L 224 | float LogL = IndirectIrradiance; 225 | const float LogBlackPoint = 0.00390625; // exp2(-8); 226 | return OneOverPreExposure * (exp2( LogL * 16 - 8 ) - LogBlackPoint); // 1 exp2, 1 smad, 1 ssub 227 | } 228 | 229 | float4 EncodeWorldTangentAndAnisotropy(float3 WorldTangent, float Anisotropy) 230 | { 231 | return float4( 232 | EncodeNormal(WorldTangent), 233 | Anisotropy * 0.5f + 0.5f 234 | ); 235 | } 236 | 237 | float ComputeAngleFromRoughness( float Roughness, const float Threshold = 0.04f ) 238 | { 239 | #if 1 240 | float Angle = 3 * Square( Roughness ); 241 | #else 242 | const float LogThreshold = log2( Threshold ); 243 | float Power = 0.5 / pow( Roughness, 4 ) - 0.5; 244 | float Angle = acos( exp2( LogThreshold / Power ) ); 245 | #endif 246 | return Angle; 247 | } 248 | 249 | float ComputeRoughnessFromAngle( float Angle, const float Threshold = 0.04f ) 250 | { 251 | #if 1 252 | float Roughness = sqrt( 0.33333 * Angle ); 253 | #else 254 | const float LogThreshold = log2( Threshold ); 255 | float Power = LogThreshold / log2( cos( Angle ) ); 256 | float Roughness = sqrt( sqrt( 2 / (Power * 4 + 2) ) ); 257 | #endif 258 | return Roughness; 259 | } 260 | 261 | float AddAngleToRoughness( float Angle, float Roughness ) 262 | { 263 | return saturate( sqrt( Square( Roughness ) + 0.33333 * Angle ) ); 264 | } 265 | 266 | // @param Scalar clamped in 0..1 range 267 | // @param Mask 0..1 268 | // @return 8bit in range 0..1 269 | float Encode71(float Scalar, uint Mask) 270 | { 271 | return 272 | 127.0f / 255.0f * saturate(Scalar) + 273 | 128.0f / 255.0f * Mask; 274 | } 275 | 276 | // 8bit reinterpretation as 7bit,1bit 277 | // @param Scalar 0..1 278 | // @param Mask 0..1 279 | // @return 7bit in 0.1 280 | float Decode71(float Scalar, out uint Mask) 281 | { 282 | Mask = (uint)(Scalar > 0.5f); 283 | 284 | return (Scalar - 0.5f * Mask) * 2.0f; 285 | } 286 | 287 | float EncodeShadingModelIdAndSelectiveOutputMask(uint ShadingModelId, uint SelectiveOutputMask) 288 | { 289 | uint Value = (ShadingModelId & SHADINGMODELID_MASK) | SelectiveOutputMask; 290 | return (float)Value / (float)0xFF; 291 | } 292 | 293 | uint DecodeShadingModelId(float InPackedChannel) 294 | { 295 | return ((uint)round(InPackedChannel * (float)0xFF)) & SHADINGMODELID_MASK; 296 | } 297 | 298 | uint DecodeSelectiveOutputMask(float InPackedChannel) 299 | { 300 | return ((uint)round(InPackedChannel * (float)0xFF)) & ~SHADINGMODELID_MASK; 301 | } 302 | 303 | bool IsSubsurfaceModel(int ShadingModel) 304 | { 305 | return ShadingModel == SHADINGMODELID_SUBSURFACE 306 | || ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN 307 | || ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE 308 | || ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE 309 | || ShadingModel == SHADINGMODELID_HAIR 310 | || ShadingModel == SHADINGMODELID_EYE; 311 | } 312 | 313 | bool UseSubsurfaceProfile(int ShadingModel) 314 | { 315 | return ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE || ShadingModel == SHADINGMODELID_EYE; 316 | } 317 | 318 | bool HasCustomGBufferData(int ShadingModelID) 319 | { 320 | return ShadingModelID == SHADINGMODELID_SUBSURFACE 321 | || ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN 322 | || ShadingModelID == SHADINGMODELID_CLEAR_COAT 323 | || ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE 324 | || ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE 325 | || ShadingModelID == SHADINGMODELID_HAIR 326 | || ShadingModelID == SHADINGMODELID_CLOTH 327 | || ShadingModelID == SHADINGMODELID_EYE 328 | || ShadingModelID == SHADINGMODELID_STYLIZED_SHADOW; 329 | } 330 | 331 | bool HasAnisotropy(int SelectiveOutputMask) 332 | { 333 | return (SelectiveOutputMask & HAS_ANISOTROPY_MASK) != 0; 334 | } 335 | 336 | // all values that are output by the forward rendering pass 337 | struct FGBufferData 338 | { 339 | // normalized 340 | float3 WorldNormal; 341 | // normalized, only valid if HAS_ANISOTROPY_MASK in SelectiveOutputMask 342 | float3 WorldTangent; 343 | // 0..1 (derived from BaseColor, Metalness, Specular) 344 | float3 DiffuseColor; 345 | // 0..1 (derived from BaseColor, Metalness, Specular) 346 | float3 SpecularColor; 347 | // 0..1, white for SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE (apply BaseColor after scattering is more correct and less blurry) 348 | float3 BaseColor; 349 | // 0..1 350 | float Metallic; 351 | // 0..1 352 | float Specular; 353 | // 0..1 354 | float4 CustomData; 355 | // Indirect irradiance luma 356 | float IndirectIrradiance; 357 | // Static shadow factors for channels assigned by Lightmass 358 | // Lights using static shadowing will pick up the appropriate channel in their deferred pass 359 | float4 PrecomputedShadowFactors; 360 | // 0..1 361 | float Roughness; 362 | // -1..1, only valid if only valid if HAS_ANISOTROPY_MASK in SelectiveOutputMask 363 | float Anisotropy; 364 | // 0..1 ambient occlusion e.g.SSAO, wet surface mask, skylight mask, ... 365 | float GBufferAO; 366 | // 0..255 367 | uint ShadingModelID; 368 | // 0..255 369 | uint SelectiveOutputMask; 370 | // 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract 371 | float PerObjectGBufferData; 372 | // in world units 373 | float CustomDepth; 374 | // Custom depth stencil value 375 | uint CustomStencil; 376 | // in unreal units (linear), can be used to reconstruct world position, 377 | // only valid when decoding the GBuffer as the value gets reconstructed from the Z buffer 378 | float Depth; 379 | // Velocity for motion blur (only used when WRITES_VELOCITY_TO_GBUFFER is enabled) 380 | float4 Velocity; 381 | 382 | // 0..1, only needed by SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE which apply BaseColor later 383 | float3 StoredBaseColor; 384 | // 0..1, only needed by SHADINGMODELID_SUBSURFACE_PROFILE and SHADINGMODELID_EYE which apply Specular later 385 | float StoredSpecular; 386 | // 0..1, only needed by SHADINGMODELID_EYE which encodes Iris Distance inside Metallic 387 | float StoredMetallic; 388 | }; 389 | 390 | bool CastContactShadow(FGBufferData GBufferData) 391 | { 392 | uint PackedAlpha = (uint)(GBufferData.PerObjectGBufferData * 3.999f); 393 | bool bCastContactShadowBit = PackedAlpha & 1; 394 | // Exclude eye materials from ever casting contact shadows 395 | bool bShadingModelCastContactShadows = (GBufferData.ShadingModelID != SHADINGMODELID_EYE); 396 | return bCastContactShadowBit && bShadingModelCastContactShadows; 397 | } 398 | 399 | bool HasDynamicIndirectShadowCasterRepresentation(FGBufferData GBufferData) 400 | { 401 | uint PackedAlpha = (uint)(GBufferData.PerObjectGBufferData * 3.999f); 402 | return (PackedAlpha & 2) != 0; 403 | } 404 | 405 | struct FScreenSpaceData 406 | { 407 | // GBuffer (material attributes from forward rendering pass) 408 | FGBufferData GBuffer; 409 | // 0..1, only valid in some passes, 1 if off 410 | float AmbientOcclusion; 411 | }; 412 | 413 | /** Sets up the Gbuffer for an unlit material. */ 414 | void SetGBufferForUnlit(out float4 OutGBufferB) 415 | { 416 | OutGBufferB = 0; 417 | OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(SHADINGMODELID_UNLIT, 0); 418 | } 419 | 420 | /** Populates OutGBufferA, B and C */ 421 | void EncodeGBuffer( 422 | FGBufferData GBuffer, 423 | out float4 OutGBufferA, 424 | out float4 OutGBufferB, 425 | out float4 OutGBufferC, 426 | out float4 OutGBufferD, 427 | out float4 OutGBufferE, 428 | out float4 OutGBufferVelocity, 429 | float QuantizationBias = 0 // -0.5 to 0.5 random float. Used to bias quantization. 430 | ) 431 | { 432 | if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT) 433 | { 434 | OutGBufferA = 0; 435 | SetGBufferForUnlit(OutGBufferB); 436 | OutGBufferC = 0; 437 | OutGBufferD = 0; 438 | OutGBufferE = 0; 439 | } 440 | else 441 | { 442 | #if MOBILE_DEFERRED_SHADING 443 | OutGBufferA.rg = UnitVectorToOctahedron( normalize(GBuffer.WorldNormal) ) * 0.5f + 0.5f; 444 | OutGBufferA.b = GBuffer.PrecomputedShadowFactors.x; 445 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 446 | #elif 1 447 | OutGBufferA.rgb = EncodeNormal( GBuffer.WorldNormal ); 448 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 449 | #else 450 | float3 Normal = GBuffer.WorldNormal; 451 | uint NormalFace = 0; 452 | EncodeNormal( Normal, NormalFace ); 453 | 454 | OutGBufferA.rg = Normal.xy; 455 | OutGBufferA.b = 0; 456 | OutGBufferA.a = GBuffer.PerObjectGBufferData; 457 | #endif 458 | 459 | OutGBufferB.r = GBuffer.Metallic; 460 | OutGBufferB.g = GBuffer.Specular; 461 | OutGBufferB.b = GBuffer.Roughness; 462 | OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask); 463 | 464 | OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor ); 465 | 466 | #if ALLOW_STATIC_LIGHTING 467 | // No space for AO. Multiply IndirectIrradiance by AO instead of storing. 468 | OutGBufferC.a = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0); 469 | #else 470 | OutGBufferC.a = GBuffer.GBufferAO; 471 | #endif 472 | 473 | OutGBufferD = GBuffer.CustomData; 474 | OutGBufferE = GBuffer.PrecomputedShadowFactors; 475 | } 476 | 477 | #if WRITES_VELOCITY_TO_GBUFFER 478 | OutGBufferVelocity = GBuffer.Velocity; 479 | #else 480 | OutGBufferVelocity = 0; 481 | #endif 482 | } 483 | 484 | // High frequency Checkerboard pattern 485 | // @param PixelPos relative to left top of the rendertarget (not viewport) 486 | // @return true/false, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 487 | bool CheckerFromPixelPos(uint2 PixelPos) 488 | { 489 | // todo: Index is float and by staying float we can optimize this 490 | // We alternate the pattern to get 2x supersampling on the lower res data to get more near to full res 491 | uint TemporalAASampleIndex = View.TemporalAAParams.x; 492 | 493 | #if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 494 | return (PixelPos.x + PixelPos.y + TemporalAASampleIndex) % 2; 495 | #else 496 | return (uint)(fmod(PixelPos.x + PixelPos.y + TemporalAASampleIndex, 2)) != 0; 497 | #endif 498 | } 499 | 500 | // High frequency Checkerboard pattern 501 | // @param UVSceneColor at pixel center 502 | // @return true/false, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 503 | bool CheckerFromSceneColorUV(float2 UVSceneColor) 504 | { 505 | // relative to left top of the rendertarget (not viewport) 506 | uint2 PixelPos = uint2(UVSceneColor * View.BufferSizeAndInvSize.xy); 507 | 508 | return CheckerFromPixelPos(PixelPos); 509 | } 510 | 511 | // SubsurfaceProfile does deferred lighting with a checker board pixel pattern 512 | // we separate the view from the non view dependent lighting and later recombine the two color constributions in a postprocess 513 | // We have the option to apply the BaseColor/Specular in the base pass or do it later in the postprocess (has implications to texture detail, fresnel and performance) 514 | void AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(inout float3 BaseColor, inout float3 SpecularColor, inout float Specular, bool bChecker) 515 | { 516 | #if SUBSURFACE_CHANNEL_MODE == 0 517 | // If SUBSURFACE_CHANNEL_MODE is 0, we can't support full-resolution lighting, so we 518 | // ignore View.bCheckerboardSubsurfaceProfileRendering 519 | const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0; 520 | #else 521 | const bool bCheckerboardRequired = View.bSubsurfacePostprocessEnabled > 0 && View.bCheckerboardSubsurfaceProfileRendering > 0; 522 | BaseColor = View.bSubsurfacePostprocessEnabled ? float3(1, 1, 1) : BaseColor; 523 | #endif 524 | if (bCheckerboardRequired) 525 | { 526 | // because we adjust the BaseColor here, we need StoredBaseColor 527 | 528 | // we apply the base color later in SubsurfaceRecombinePS() 529 | BaseColor = bChecker; 530 | // in SubsurfaceRecombinePS() does not multiply with Specular so we do it here 531 | SpecularColor *= !bChecker; 532 | Specular *= !bChecker; 533 | } 534 | } 535 | 536 | /** Populates FGBufferData */ 537 | // @param bChecker High frequency Checkerboard pattern computed with one of the CheckerFrom.. functions, todo: profile if float 0/1 would be better (need to make sure it's 100% the same) 538 | FGBufferData DecodeGBufferData( 539 | float4 InGBufferA, 540 | float4 InGBufferB, 541 | float4 InGBufferC, 542 | float4 InGBufferD, 543 | float4 InGBufferE, 544 | float4 InGBufferF, 545 | float4 InGBufferVelocity, 546 | float CustomNativeDepth, 547 | uint CustomStencil, 548 | float SceneDepth, 549 | bool bGetNormalizedNormal, 550 | bool bChecker) 551 | { 552 | FGBufferData GBuffer; 553 | 554 | GBuffer.WorldNormal = DecodeNormal( InGBufferA.xyz ); 555 | if(bGetNormalizedNormal) 556 | { 557 | GBuffer.WorldNormal = normalize(GBuffer.WorldNormal); 558 | } 559 | 560 | GBuffer.PerObjectGBufferData = InGBufferA.a; 561 | GBuffer.Metallic = InGBufferB.r; 562 | GBuffer.Specular = InGBufferB.g; 563 | GBuffer.Roughness = InGBufferB.b; 564 | // Note: must match GetShadingModelId standalone function logic 565 | // Also Note: SimpleElementPixelShader directly sets SV_Target2 ( GBufferB ) to indicate unlit. 566 | // An update there will be required if this layout changes. 567 | GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a); 568 | GBuffer.SelectiveOutputMask = DecodeSelectiveOutputMask(InGBufferB.a); 569 | 570 | GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb); 571 | 572 | #if ALLOW_STATIC_LIGHTING 573 | GBuffer.GBufferAO = 1; 574 | GBuffer.IndirectIrradiance = DecodeIndirectIrradiance(InGBufferC.a); 575 | #else 576 | GBuffer.GBufferAO = InGBufferC.a; 577 | GBuffer.IndirectIrradiance = 1; 578 | #endif 579 | 580 | GBuffer.CustomData = HasCustomGBufferData(GBuffer.ShadingModelID) ? InGBufferD : 0; 581 | 582 | GBuffer.PrecomputedShadowFactors = !(GBuffer.SelectiveOutputMask & SKIP_PRECSHADOW_MASK) ? InGBufferE : ((GBuffer.SelectiveOutputMask & ZERO_PRECSHADOW_MASK) ? 0 : 1); 583 | GBuffer.CustomDepth = ConvertFromDeviceZ(CustomNativeDepth); 584 | GBuffer.CustomStencil = CustomStencil; 585 | GBuffer.Depth = SceneDepth; 586 | 587 | GBuffer.StoredBaseColor = GBuffer.BaseColor; 588 | GBuffer.StoredMetallic = GBuffer.Metallic; 589 | GBuffer.StoredSpecular = GBuffer.Specular; 590 | 591 | FLATTEN 592 | if( GBuffer.ShadingModelID == SHADINGMODELID_EYE ) 593 | { 594 | GBuffer.Metallic = 0.0; 595 | #if IRIS_NORMAL 596 | GBuffer.Specular = 0.25; 597 | #endif 598 | } 599 | 600 | // derived from BaseColor, Metalness, Specular 601 | { 602 | GBuffer.SpecularColor = ComputeF0(GBuffer.Specular, GBuffer.BaseColor, GBuffer.Metallic); 603 | 604 | if (UseSubsurfaceProfile(GBuffer.ShadingModelID)) 605 | { 606 | AdjustBaseColorAndSpecularColorForSubsurfaceProfileLighting(GBuffer.BaseColor, GBuffer.SpecularColor, GBuffer.Specular, bChecker); 607 | } 608 | 609 | GBuffer.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic; 610 | 611 | #if USE_DEVELOPMENT_SHADERS 612 | { 613 | // this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help) 614 | GBuffer.DiffuseColor = GBuffer.DiffuseColor * View.DiffuseOverrideParameter.www + View.DiffuseOverrideParameter.xyz; 615 | GBuffer.SpecularColor = GBuffer.SpecularColor * View.SpecularOverrideParameter.w + View.SpecularOverrideParameter.xyz; 616 | } 617 | #endif //USE_DEVELOPMENT_SHADERS 618 | } 619 | 620 | { 621 | bool bHasAnisoProp = HasAnisotropy(GBuffer.SelectiveOutputMask); 622 | 623 | GBuffer.WorldTangent = bHasAnisoProp ? DecodeNormal(InGBufferF.rgb) : 0; 624 | GBuffer.Anisotropy = bHasAnisoProp ? InGBufferF.a * 2.0f - 1.0f : 0; 625 | 626 | if (bGetNormalizedNormal && bHasAnisoProp) 627 | { 628 | GBuffer.WorldTangent = normalize(GBuffer.WorldTangent); 629 | } 630 | } 631 | 632 | GBuffer.Velocity = !(GBuffer.SelectiveOutputMask & SKIP_VELOCITY_MASK) ? InGBufferVelocity : 0; 633 | 634 | return GBuffer; 635 | } 636 | 637 | float3 ExtractSubsurfaceColor(FGBufferData BufferData) 638 | { 639 | return Square(BufferData.CustomData.rgb); 640 | } 641 | 642 | uint ExtractSubsurfaceProfileInt(FGBufferData BufferData) 643 | { 644 | // can be optimized 645 | return uint(BufferData.CustomData.r * 255.0f + 0.5f); 646 | } 647 | 648 | #if SHADING_PATH_DEFERRED 649 | 650 | #if FEATURE_LEVEL >= FEATURE_LEVEL_SM5 651 | // @param PixelPos relative to left top of the rendertarget (not viewport) 652 | FGBufferData GetGBufferDataUint(uint2 PixelPos, bool bGetNormalizedNormal = true) 653 | { 654 | float4 GBufferA = SceneTexturesStruct.GBufferATexture.Load(int3(PixelPos, 0)); 655 | float4 GBufferB = SceneTexturesStruct.GBufferBTexture.Load(int3(PixelPos, 0)); 656 | float4 GBufferC = SceneTexturesStruct.GBufferCTexture.Load(int3(PixelPos, 0)); 657 | float4 GBufferD = SceneTexturesStruct.GBufferDTexture.Load(int3(PixelPos, 0)); 658 | float CustomNativeDepth = SceneTexturesStruct.CustomDepthTexture.Load(int3(PixelPos, 0)).r; 659 | uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(PixelPos, 0)) STENCIL_COMPONENT_SWIZZLE; 660 | 661 | #if ALLOW_STATIC_LIGHTING 662 | float4 GBufferE = SceneTexturesStruct.GBufferETexture.Load(int3(PixelPos, 0)); 663 | #else 664 | float4 GBufferE = 1; 665 | #endif 666 | 667 | float4 GBufferF = SceneTexturesStruct.GBufferFTexture.Load(int3(PixelPos, 0)); 668 | 669 | #if WRITES_VELOCITY_TO_GBUFFER 670 | float4 GBufferVelocity = SceneTexturesStruct.GBufferVelocityTexture.Load(int3(PixelPos, 0)); 671 | #else 672 | float4 GBufferVelocity = 0; 673 | #endif 674 | 675 | float SceneDepth = CalcSceneDepth(PixelPos); 676 | 677 | return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromPixelPos(PixelPos)); 678 | } 679 | 680 | // @param PixelPos relative to left top of the rendertarget (not viewport) 681 | FScreenSpaceData GetScreenSpaceDataUint(uint2 PixelPos, bool bGetNormalizedNormal = true) 682 | { 683 | FScreenSpaceData Out; 684 | 685 | Out.GBuffer = GetGBufferDataUint(PixelPos, bGetNormalizedNormal); 686 | 687 | float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct_ScreenSpaceAOTextureSampler, (PixelPos + 0.5f) * View.BufferSizeAndInvSize.zw, 0); 688 | Out.AmbientOcclusion = ScreenSpaceAO.r; 689 | 690 | return Out; 691 | } 692 | #endif 693 | 694 | // @param UV - UV space in the GBuffer textures (BufferSize resolution) 695 | FGBufferData GetGBufferData(float2 UV, bool bGetNormalizedNormal = true) 696 | { 697 | float4 GBufferA = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV, 0); 698 | float4 GBufferB = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0); 699 | float4 GBufferC = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0); 700 | float4 GBufferD = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct_GBufferDTextureSampler, UV, 0); 701 | float CustomNativeDepth = Texture2DSampleLevel(SceneTexturesStruct.CustomDepthTexture, SceneTexturesStruct_CustomDepthTextureSampler, UV, 0).r; 702 | 703 | int2 IntUV = (int2)trunc(UV * View.BufferSizeAndInvSize.xy); 704 | uint CustomStencil = SceneTexturesStruct.CustomStencilTexture.Load(int3(IntUV, 0)) STENCIL_COMPONENT_SWIZZLE; 705 | 706 | #if ALLOW_STATIC_LIGHTING 707 | float4 GBufferE = Texture2DSampleLevel(SceneTexturesStruct.GBufferETexture, SceneTexturesStruct_GBufferETextureSampler, UV, 0); 708 | #else 709 | float4 GBufferE = 1; 710 | #endif 711 | 712 | float4 GBufferF = Texture2DSampleLevel(SceneTexturesStruct.GBufferFTexture, SceneTexturesStruct_GBufferFTextureSampler, UV, 0); 713 | 714 | #if WRITES_VELOCITY_TO_GBUFFER 715 | float4 GBufferVelocity = Texture2DSampleLevel(SceneTexturesStruct.GBufferVelocityTexture, SceneTexturesStruct_GBufferVelocityTextureSampler, UV, 0); 716 | #else 717 | float4 GBufferVelocity = 0; 718 | #endif 719 | 720 | float SceneDepth = CalcSceneDepth(UV); 721 | 722 | return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV)); 723 | } 724 | 725 | // Minimal path for just the lighting model, used to branch around unlit pixels (skybox) 726 | uint GetShadingModelId(float2 UV) 727 | { 728 | return DecodeShadingModelId(Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0).a); 729 | } 730 | 731 | // @param UV - UV space in the GBuffer textures (BufferSize resolution) 732 | FScreenSpaceData GetScreenSpaceData(float2 UV, bool bGetNormalizedNormal = true) 733 | { 734 | FScreenSpaceData Out; 735 | 736 | Out.GBuffer = GetGBufferData(UV, bGetNormalizedNormal); 737 | float4 ScreenSpaceAO = Texture2DSampleLevel(SceneTexturesStruct.ScreenSpaceAOTexture, SceneTexturesStruct_ScreenSpaceAOTextureSampler, UV, 0); 738 | 739 | Out.AmbientOcclusion = ScreenSpaceAO.r; 740 | 741 | return Out; 742 | } 743 | 744 | #endif 745 | -------------------------------------------------------------------------------- /ShadersOverride/Default/Private/ShadingModels.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "DeferredShadingCommon.ush" 6 | #include "BRDF.ush" 7 | #include "FastMath.ush" 8 | #include "CapsuleLight.ush" 9 | #include "RectLight.ush" 10 | #include "TransmissionCommon.ush" 11 | #include "HairBsdf.ush" 12 | 13 | #if 0 14 | void StandardShadingShared( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 V, half3 N ) 15 | { 16 | float NoV = saturate( abs( dot(N, V) ) + 1e-5 ); 17 | 18 | // Diffuse_Lambert 19 | Shared.DiffuseMul = DiffuseColor * (1.0 / PI); 20 | 21 | // D_GGX, Vis_SmithJointApprox 22 | float m = Roughness * Roughness; 23 | Shared.m2 = m * m; 24 | Shared.SpecularMul = (0.5 / PI) * Shared.m2; 25 | Shared.VisMad = float2( 2 * NoV * ( 1 - m ) + m, NoV * m ); 26 | 27 | // F_Schlick 28 | Shared.SpecularMul *= saturate( 50.0 * SpecularColor.g ); 29 | } 30 | 31 | void StandardShadingPerLight( Shared, float3 L, float3 V, half3 N ) 32 | { 33 | float3 H = normalize(V + L); // 3 add, 2 mad, 4 mul, 1 rsqrt 34 | float NoL = saturate( dot(N, L) ); // 2 mad, 1 mul 35 | float NoH = saturate( dot(N, H) ); // 2 mad, 1 mul 36 | float VoH = saturate( dot(V, H) ); // 2 mad, 1 mul 37 | 38 | // D_GGX, Vis_SmithJointApprox 39 | float d = ( NoH * Shared.m2 - NoH ) * NoH + 1; // 2 mad 40 | float v = NoL * Shared.VisMad.x + Shared.VisMad.y; // 1 mad 41 | float D_Vis = Shared.SpecularMul * rcp( d * d * v ); // 3 mul, 1 rcp 42 | 43 | // F_Schlick 44 | float Fc = pow( 1 - VoH, 5 ); // 1 sub, 3 mul 45 | float3 F = Fc + (1 - Fc) * SpecularColor; // 1 sub, 3 mad 46 | 47 | return Shared.DiffuseMul + D_Vis * F; // 3 mad 48 | } 49 | #endif 50 | struct FAreaLight 51 | { 52 | float SphereSinAlpha; 53 | float SphereSinAlphaSoft; 54 | float LineCosSubtended; 55 | 56 | float3 FalloffColor; 57 | 58 | FRect Rect; 59 | FRectTexture Texture; 60 | bool bIsRect; 61 | }; 62 | 63 | struct FDirectLighting 64 | { 65 | float3 Diffuse; 66 | float3 Specular; 67 | float3 Transmission; 68 | }; 69 | 70 | struct FShadowTerms 71 | { 72 | float SurfaceShadow; 73 | float TransmissionShadow; 74 | float TransmissionThickness; 75 | FHairTransmittanceData HairTransmittance; 76 | }; 77 | FDirectLighting HairBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow) 78 | { 79 | const float3 BsdfValue = HairShading(GBuffer, L, V, N, Shadow.TransmissionShadow, Shadow.HairTransmittance, 1, 0, uint2(0, 0)); 80 | 81 | FDirectLighting Lighting; 82 | Lighting.Diffuse = 0; 83 | Lighting.Specular = 0; 84 | Lighting.Transmission = AreaLight.FalloffColor * Falloff * BsdfValue; 85 | return Lighting; 86 | } 87 | 88 | bool IsAreaLight(FAreaLight AreaLight) 89 | { 90 | return ( 91 | AreaLight.bIsRect || 92 | AreaLight.SphereSinAlpha > 0.0f || 93 | AreaLight.SphereSinAlphaSoft > 0.0f || 94 | AreaLight.LineCosSubtended < 1.0f 95 | ) ? true : false; 96 | } 97 | 98 | float New_a2( float a2, float SinAlpha, float VoH ) 99 | { 100 | return a2 + 0.25 * SinAlpha * (3.0 * sqrtFast(a2) + SinAlpha) / ( VoH + 0.001 ); 101 | //return a2 + 0.25 * SinAlpha * ( saturate(12 * a2 + 0.125) + SinAlpha ) / ( VoH + 0.001 ); 102 | //return a2 + 0.25 * SinAlpha * ( a2 * 2 + 1 + SinAlpha ) / ( VoH + 0.001 ); 103 | } 104 | 105 | float EnergyNormalization( inout float a2, float VoH, FAreaLight AreaLight ) 106 | { 107 | if( AreaLight.SphereSinAlphaSoft > 0 ) 108 | { 109 | // Modify Roughness 110 | a2 = saturate( a2 + Pow2( AreaLight.SphereSinAlphaSoft ) / ( VoH * 3.6 + 0.4 ) ); 111 | } 112 | 113 | float Sphere_a2 = a2; 114 | float Energy = 1; 115 | if( AreaLight.SphereSinAlpha > 0 ) 116 | { 117 | Sphere_a2 = New_a2( a2, AreaLight.SphereSinAlpha, VoH ); 118 | Energy = a2 / Sphere_a2; 119 | } 120 | 121 | if( AreaLight.LineCosSubtended < 1 ) 122 | { 123 | #if 1 124 | float LineCosTwoAlpha = AreaLight.LineCosSubtended; 125 | float LineTanAlpha = sqrt( ( 1.0001 - LineCosTwoAlpha ) / ( 1 + LineCosTwoAlpha ) ); 126 | float Line_a2 = New_a2( Sphere_a2, LineTanAlpha, VoH ); 127 | Energy *= sqrt( Sphere_a2 / Line_a2 ); 128 | #else 129 | float LineCosTwoAlpha = AreaLight.LineCosSubtended; 130 | float LineSinAlpha = sqrt( 0.5 - 0.5 * LineCosTwoAlpha ); 131 | float Line_a2 = New_a2( Sphere_a2, LineSinAlpha, VoH ); 132 | Energy *= Sphere_a2 / Line_a2; 133 | #endif 134 | } 135 | 136 | return Energy; 137 | } 138 | 139 | float3 SpecularGGX(float Roughness, float Anisotropy, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight) 140 | { 141 | float Alpha = Roughness * Roughness; 142 | float a2 = Alpha * Alpha; 143 | 144 | FAreaLight Punctual = AreaLight; 145 | Punctual.SphereSinAlpha = 0; 146 | Punctual.SphereSinAlphaSoft = 0; 147 | Punctual.LineCosSubtended = 1; 148 | Punctual.Rect = (FRect)0; 149 | Punctual.bIsRect = false; 150 | 151 | float Energy = EnergyNormalization(a2, Context.VoH, Punctual); 152 | 153 | float ax = 0; 154 | float ay = 0; 155 | GetAnisotropicRoughness(Alpha, Anisotropy, ax, ay); 156 | 157 | // Generalized microfacet specular 158 | float3 D = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH) * Energy; 159 | float3 Vis = Vis_SmithJointAniso(ax, ay, Context.NoV, NoL, Context.XoV, Context.XoL, Context.YoV, Context.YoL); 160 | float3 F = F_Schlick( SpecularColor, Context.VoH ); 161 | 162 | return (D * Vis) * F; 163 | } 164 | 165 | float3 SpecularGGX( float Roughness, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight ) 166 | { 167 | float a2 = Pow4( Roughness ); 168 | float Energy = EnergyNormalization( a2, Context.VoH, AreaLight ); 169 | 170 | // Generalized microfacet specular 171 | float D = D_GGX( a2, Context.NoH ) * Energy; 172 | float Vis = Vis_SmithJointApprox( a2, Context.NoV, NoL ); 173 | float3 F = F_Schlick( SpecularColor, Context.VoH ); 174 | 175 | return (D * Vis) * F; 176 | } 177 | 178 | float3 DualSpecularGGX(float AverageRoughness, float Lobe0Roughness, float Lobe1Roughness, float LobeMix, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight) 179 | { 180 | float AverageAlpha2 = Pow4(AverageRoughness); 181 | float Lobe0Alpha2 = Pow4(Lobe0Roughness); 182 | float Lobe1Alpha2 = Pow4(Lobe1Roughness); 183 | 184 | float Lobe0Energy = EnergyNormalization(Lobe0Alpha2, Context.VoH, AreaLight); 185 | float Lobe1Energy = EnergyNormalization(Lobe1Alpha2, Context.VoH, AreaLight); 186 | 187 | // Generalized microfacet specular 188 | float D = lerp(D_GGX(Lobe0Alpha2, Context.NoH) * Lobe0Energy, D_GGX(Lobe1Alpha2, Context.NoH) * Lobe1Energy, LobeMix); 189 | float Vis = Vis_SmithJointApprox(AverageAlpha2, Context.NoV, NoL); // Average visibility well approximates using two separate ones (one per lobe). 190 | float3 F = F_Schlick(SpecularColor, Context.VoH); 191 | 192 | return (D * Vis) * F; 193 | } 194 | 195 | FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 196 | { 197 | BxDFContext Context; 198 | 199 | #if SUPPORTS_ANISOTROPIC_MATERIALS 200 | bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask); 201 | #else 202 | bool bHasAnisotropy = false; 203 | #endif 204 | 205 | BRANCH 206 | if (bHasAnisotropy) 207 | { 208 | half3 X = GBuffer.WorldTangent; 209 | half3 Y = normalize(cross(N, X)); 210 | Init(Context, N, X, Y, V, L); 211 | } 212 | else 213 | { 214 | Init(Context, N, V, L); 215 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true); 216 | } 217 | 218 | Context.NoV = saturate(abs( Context.NoV ) + 1e-5); 219 | 220 | FDirectLighting Lighting; 221 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor ); 222 | 223 | BRANCH 224 | if (bHasAnisotropy) 225 | { 226 | //Lighting.Specular = GBuffer.WorldTangent * .5f + .5f; 227 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight); 228 | } 229 | else 230 | { 231 | if( AreaLight.bIsRect ) 232 | { 233 | Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 234 | } 235 | else 236 | { 237 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight); 238 | } 239 | } 240 | 241 | Lighting.Transmission = 0; 242 | return Lighting; 243 | } 244 | 245 | float3 SimpleShading( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 L, float3 V, half3 N ) 246 | { 247 | float3 H = normalize(V + L); 248 | float NoH = saturate( dot(N, H) ); 249 | 250 | // Generalized microfacet specular 251 | float D = D_GGX( Pow4(Roughness), NoH ); 252 | float Vis = Vis_Implicit(); 253 | float3 F = F_None( SpecularColor ); 254 | 255 | return Diffuse_Lambert( DiffuseColor ) + (D * Vis) * F; 256 | } 257 | 258 | float3 CalcThinTransmission(float NoL, float NoV, FGBufferData GBuffer) 259 | { 260 | float3 Transmission = 1.0; 261 | float AbsorptionMix = GBuffer.Metallic; 262 | if (AbsorptionMix > 0.0) 263 | { 264 | // Normalized layer thickness documented for clarity 265 | float NormalizedLayerThickness = 1.0; 266 | float ThinDistance = NormalizedLayerThickness * (rcp(NoV) + rcp(NoL)); 267 | 268 | // Base color represents reflected color viewed at 0 incidence angle, after being absorbed through the substrate. 269 | // Because of this, extinction is normalized by traveling through layer thickness twice 270 | float3 TransmissionColor = Diffuse_Lambert(GBuffer.BaseColor); 271 | float3 ExtinctionCoefficient = -log(TransmissionColor) / (2.0 * NormalizedLayerThickness); 272 | float3 OpticalDepth = ExtinctionCoefficient * max(ThinDistance - 2.0 * NormalizedLayerThickness, 0.0); 273 | Transmission = saturate(exp(-OpticalDepth)); 274 | Transmission = lerp(1.0, Transmission, AbsorptionMix); 275 | } 276 | 277 | return Transmission; 278 | } 279 | 280 | float RefractBlend(float VoH, float Eta) 281 | { 282 | // Refraction blend factor for normal component of VoH 283 | float k = 1.0 - Eta * Eta * (1.0 - VoH * VoH); 284 | return Eta * VoH - sqrt(k); 285 | } 286 | 287 | float RefractBlendClearCoatApprox(float VoH) 288 | { 289 | // Polynomial approximation of refraction blend factor for normal component of VoH with fixed Eta (1/1.5): 290 | return (0.63 - 0.22 * VoH) * VoH - 0.745; 291 | } 292 | 293 | float3 Refract(float3 V, float3 H, float Eta) 294 | { 295 | // Assumes V points away from the point of incidence 296 | float VoH = dot(V, H); 297 | return RefractBlend(VoH, Eta) * H - Eta * V; 298 | } 299 | 300 | BxDFContext RefractClearCoatContext(BxDFContext Context) 301 | { 302 | // Reference: Propagation of refraction through dot-product NoV 303 | // Note: This version of Refract requires V to point away from the point of incidence 304 | // NoV2 = -dot(N, Refract(V, H, Eta)) 305 | // NoV2 = -dot(N, RefractBlend(VoH, Eta) * H - Eta * V) 306 | // NoV2 = -(RefractBlend(VoH, Eta) * NoH - Eta * NoV) 307 | // NoV2 = Eta * NoV - RefractBlend(VoH, Eta) * NoH 308 | // NoV2 = 1.0 / 1.5 * NoV - RefractBlendClearCoatApprox(VoH) * NoH 309 | 310 | BxDFContext RefractedContext = Context; 311 | float Eta = 1.0 / 1.5; 312 | float RefractionBlendFactor = RefractBlendClearCoatApprox(Context.VoH); 313 | float RefractionProjectionTerm = RefractionBlendFactor * Context.NoH; 314 | RefractedContext.NoV = saturate(Eta * Context.NoV - RefractionProjectionTerm); 315 | RefractedContext.NoL = saturate(Eta * Context.NoL - RefractionProjectionTerm); 316 | RefractedContext.VoH = saturate(Eta * Context.VoH - RefractionBlendFactor); 317 | RefractedContext.VoL = 2.0 * RefractedContext.VoH * RefractedContext.VoH - 1.0; 318 | RefractedContext.NoH = Context.NoH; 319 | return RefractedContext; 320 | } 321 | 322 | FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 323 | { 324 | const float ClearCoat = GBuffer.CustomData.x; 325 | const float ClearCoatRoughness = max(GBuffer.CustomData.y, 0.02f); 326 | const float Film = 1 * ClearCoat; 327 | const float MetalSpec = 0.9; 328 | 329 | FDirectLighting Lighting = { 330 | float3(0.0, 0.0, 0.0), 331 | float3(0.0, 0.0, 0.0), 332 | float3(0.0, 0.0, 0.0) 333 | }; 334 | 335 | BxDFContext Context; 336 | half3 Nspec = N; 337 | 338 | if (CLEAR_COAT_BOTTOM_NORMAL) 339 | { 340 | Nspec = GBuffer.WorldNormal; 341 | } 342 | 343 | #if SUPPORTS_ANISOTROPIC_MATERIALS 344 | bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask); 345 | #else 346 | bool bHasAnisotropy = false; 347 | #endif 348 | 349 | half3 X = 0; 350 | half3 Y = 0; 351 | 352 | BRANCH 353 | if (bHasAnisotropy) 354 | { 355 | X = GBuffer.WorldTangent; 356 | Y = normalize(cross(N, X)); 357 | Init(Context, Nspec, X, Y, V, L); 358 | } 359 | else 360 | { 361 | Init(Context, Nspec, V, L); 362 | } 363 | 364 | // Modify SphereSinAlpha, knowing that it was previously manipulated by roughness of the under coat 365 | // Note: the operation is not invertible for GBuffer.Roughness = 1.0, so roughness is clamped to 254.0/255.0 366 | float SphereSinAlpha = AreaLight.SphereSinAlpha; 367 | float RoughnessCompensation = 1 - Pow2(GBuffer.Roughness); 368 | float Alpha = Pow2(ClearCoatRoughness); 369 | RoughnessCompensation = RoughnessCompensation > 0.0 ? (1 - Alpha) / RoughnessCompensation : 0.0; 370 | AreaLight.SphereSinAlpha = saturate(AreaLight.SphereSinAlpha * RoughnessCompensation); 371 | 372 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, CLEAR_COAT_BOTTOM_NORMAL == 0); 373 | Context.NoV = saturate(abs(Context.NoV) + 1e-5); 374 | Context.VoH = AreaLight.bIsRect ? Context.NoV : Context.VoH; 375 | 376 | // Hard-coded Fresnel evaluation with IOR = 1.5 (for polyurethane cited by Disney BRDF) 377 | float F0 = 0.04; 378 | float Fc = Pow5(1 - Context.VoH); 379 | float F = Fc + (1 - Fc) * F0; 380 | 381 | if (AreaLight.bIsRect) 382 | { 383 | Lighting.Specular = ClearCoat * RectGGXApproxLTC(ClearCoatRoughness, F0, Nspec, V, AreaLight.Rect, AreaLight.Texture); 384 | } 385 | else 386 | { 387 | // Generalized microfacet specular 388 | float a2 = Pow2(Alpha); 389 | float ClearCoatEnergy = EnergyNormalization(a2, Context.VoH, AreaLight); 390 | float D = D_GGX(a2, Context.NoH) * ClearCoatEnergy; 391 | float Vis = Vis_SmithJointApprox(a2, Context.NoV, NoL); 392 | 393 | float Fr1 = D * Vis * F; 394 | Lighting.Specular = ClearCoat * AreaLight.FalloffColor * (Falloff * NoL * Fr1); 395 | } 396 | AreaLight.SphereSinAlpha = SphereSinAlpha; 397 | 398 | // Incoming and exiting Fresnel terms are identical to incoming Fresnel term (VoH == HoL) 399 | // float FresnelCoeff = (1.0 - F1) * (1.0 - F2); 400 | float FresnelCoeff = 1.0 - F; 401 | FresnelCoeff *= FresnelCoeff; 402 | 403 | if (CLEAR_COAT_BOTTOM_NORMAL) 404 | { 405 | BxDFContext TempContext; 406 | 407 | BRANCH 408 | if (bHasAnisotropy) 409 | { 410 | Init(TempContext, N, X, Y, V, L); 411 | } 412 | else 413 | { 414 | Init(TempContext, Nspec, V, L); 415 | } 416 | 417 | // If bottom-normal, update normal-based dot products: 418 | float3 H = normalize(V + L); 419 | Context.NoH = saturate(dot(N, H)); 420 | Context.NoV = saturate(dot(N, V)); 421 | Context.NoL = saturate(dot(N, L)); 422 | Context.VoL = saturate(dot(V, L)); 423 | Context.VoH = saturate(dot(V, H)); 424 | 425 | Context.XoV = TempContext.XoV; 426 | Context.XoL = TempContext.XoL; 427 | Context.XoH = TempContext.XoH; 428 | Context.YoV = TempContext.YoV; 429 | Context.YoL = TempContext.YoL; 430 | Context.YoH = TempContext.YoH; 431 | 432 | if (!bHasAnisotropy) 433 | { 434 | bool bNewtonIteration = true; 435 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, bNewtonIteration); 436 | } 437 | 438 | Context.NoV = saturate(abs(Context.NoV) + 1e-5); 439 | } 440 | 441 | // Propagate refraction through dot-products rather than the original vectors: 442 | // Reference: 443 | // float Eta = 1.0 / 1.5; 444 | // float3 H = normalize(V + L); 445 | // float3 V2 = Refract(V, H, Eta); 446 | // float3 L2 = reflect(V2, H); 447 | // V2 = -V2; 448 | // BxDFContext BottomContext; 449 | // Init(BottomContext, N, X, Y, V2, L2); 450 | BxDFContext BottomContext = RefractClearCoatContext(Context); 451 | BottomContext.VoH = AreaLight.bIsRect ? BottomContext.NoV : BottomContext.VoH; 452 | 453 | // Absorption 454 | float3 Transmission = CalcThinTransmission(BottomContext.NoL, BottomContext.NoV, GBuffer); 455 | 456 | // Default Lit 457 | float3 DefaultDiffuse = (Falloff * NoL) * AreaLight.FalloffColor * Diffuse_Lambert(GBuffer.DiffuseColor); 458 | float3 RefractedDiffuse = FresnelCoeff * Transmission * DefaultDiffuse; 459 | Lighting.Diffuse = lerp(DefaultDiffuse, RefractedDiffuse, ClearCoat); 460 | 461 | if (!bHasAnisotropy && AreaLight.bIsRect) 462 | { 463 | // Note: V is used instead of V2 because LTC integration is not tuned to handle refraction direction 464 | float3 DefaultSpecular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 465 | float3 RefractedSpecular = FresnelCoeff * Transmission * DefaultSpecular; 466 | Lighting.Specular += lerp(DefaultSpecular, RefractedSpecular, ClearCoat); 467 | } 468 | else 469 | { 470 | float a2 = Pow4(GBuffer.Roughness); 471 | float D2 = 0; 472 | float Vis2 = 0; 473 | 474 | BRANCH 475 | if (bHasAnisotropy) 476 | { 477 | float ax = 0; 478 | float ay = 0; 479 | GetAnisotropicRoughness(Alpha, GBuffer.Anisotropy, ax, ay); 480 | 481 | D2 = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH); 482 | Vis2 = Vis_SmithJointAniso(ax, ay, BottomContext.NoV, BottomContext.NoL, BottomContext.XoV, BottomContext.XoL, BottomContext.YoV, BottomContext.YoL); 483 | } 484 | else 485 | { 486 | D2 = D_GGX(a2, BottomContext.NoH); 487 | // NoL is chosen to provide better parity with DefaultLit when ClearCoat=0 488 | Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, NoL); 489 | } 490 | // When refracting into a non-metallic substrate, the IOR ratio (Eta) between top and bottom interfaces approaches 1.0 and drives Fresnel reflectance to 0 491 | float3 F = F_Schlick(GBuffer.SpecularColor, BottomContext.VoH); 492 | float3 RefractedF = lerp(0.0, F, GBuffer.Metallic); 493 | 494 | float Energy = 0; 495 | 496 | BRANCH 497 | if (bHasAnisotropy) 498 | { 499 | FAreaLight Punctual = AreaLight; 500 | Punctual.SphereSinAlpha = 0; 501 | Punctual.SphereSinAlphaSoft = 0; 502 | Punctual.LineCosSubtended = 1; 503 | Punctual.Rect = (FRect)0; 504 | Punctual.bIsRect = false; 505 | 506 | Energy = EnergyNormalization(a2, Context.VoH, Punctual); 507 | } 508 | else 509 | { 510 | Energy = EnergyNormalization(a2, Context.VoH, AreaLight); 511 | } 512 | 513 | // Note: reusing D, V, and F from refracted context to save computation for when ClearCoat < 1 514 | float3 CommonSpecular = (Energy * Falloff * D2 * Vis2) * AreaLight.FalloffColor; 515 | float3 DefaultSpecular = F * NoL; 516 | float3 RefractedSpecular = FresnelCoeff * Transmission * RefractedF * BottomContext.NoL; 517 | Lighting.Specular += CommonSpecular * lerp(DefaultSpecular, RefractedSpecular, ClearCoat); 518 | } 519 | 520 | return Lighting; 521 | } 522 | 523 | // HG phase function approximated 524 | float ApproximateHG(float cosJ, float g) 525 | { 526 | float g2 = g * g; 527 | float gcos2 = 1.0f - (g * cosJ); 528 | gcos2 *= gcos2; 529 | 530 | const float ISO_PHASE_FUNC_Normalized = 0.5; 531 | 532 | return (ISO_PHASE_FUNC_Normalized * (1.0f - g2) / max( 1e-5, gcos2)); 533 | } 534 | 535 | void GetProfileDualSpecular(FGBufferData GBuffer, out float AverageToRoughness0, out float AverageToRoughness1, out float LobeMix) 536 | { 537 | #if !FORWARD_SHADING 538 | // 0..255, which SubSurface profile to pick 539 | uint SubsurfaceProfileInt = ExtractSubsurfaceProfileInt(GBuffer); 540 | 541 | float4 Data = ActualSSProfilesTexture.Load(int3(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileInt, 0)); 542 | AverageToRoughness0 = Data.x * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 543 | AverageToRoughness1 = Data.y * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 544 | LobeMix = Data.z; 545 | #else 546 | // Disable dual lobe, as subsurface profile doesn't work with forward. 547 | AverageToRoughness0 = 1.f; 548 | AverageToRoughness1 = 1.f; 549 | LobeMix = 0.f; 550 | #endif 551 | } 552 | 553 | FDirectLighting SubsurfaceProfileBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 554 | { 555 | BxDFContext Context; 556 | Init( Context, N, V, L ); 557 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true ); 558 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 559 | 560 | float AverageToRoughness0; 561 | float AverageToRoughness1; 562 | float LobeMix; 563 | GetProfileDualSpecular(GBuffer, AverageToRoughness0, AverageToRoughness1, LobeMix); 564 | 565 | float AverageRoughness = GBuffer.Roughness; 566 | float Lobe0Roughness = max(saturate(AverageRoughness * AverageToRoughness0), 0.02f); 567 | float Lobe1Roughness = saturate(AverageRoughness * AverageToRoughness1); 568 | 569 | // Smoothly lerp to default single GGX lobe as Opacity approaches 0, before reverting to SHADINGMODELID_DEFAULT_LIT. 570 | // See SUBSURFACE_PROFILE_OPACITY_THRESHOLD in ShadingModelsMaterial.ush. 571 | float Opacity = GBuffer.CustomData.a; 572 | Lobe0Roughness = lerp(1.0f, Lobe0Roughness, saturate(Opacity * 10.0f)); 573 | Lobe1Roughness = lerp(1.0f, Lobe1Roughness, saturate(Opacity * 10.0f)); 574 | 575 | FDirectLighting Lighting; 576 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Burley( GBuffer.DiffuseColor, GBuffer.Roughness, Context.NoV, NoL, Context.VoH ); 577 | 578 | if (AreaLight.bIsRect) 579 | { 580 | float3 Lobe0Specular = RectGGXApproxLTC(Lobe0Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 581 | float3 Lobe1Specular = RectGGXApproxLTC(Lobe1Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 582 | Lighting.Specular = lerp(Lobe0Specular, Lobe1Specular, LobeMix); 583 | } 584 | else 585 | { 586 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * DualSpecularGGX(AverageRoughness, Lobe0Roughness, Lobe1Roughness, LobeMix, GBuffer.SpecularColor, Context, NoL, AreaLight); 587 | } 588 | 589 | 590 | #if USE_TRANSMISSION 591 | 592 | FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams( GBuffer ); 593 | 594 | float Thickness = Shadow.TransmissionThickness; 595 | Thickness = DecodeThickness(Thickness); 596 | Thickness *= SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE; 597 | float3 Profile = GetTransmissionProfile(GBuffer, Thickness).rgb; 598 | 599 | float3 RefracV = refract(V, -N, TransmissionParams.OneOverIOR); 600 | float PhaseFunction = ApproximateHG( dot(-L, RefracV), TransmissionParams.ScatteringDistribution ); 601 | Lighting.Transmission = AreaLight.FalloffColor * Profile * (Falloff * PhaseFunction); // TODO: This probably should also include cosine term (NoL) 602 | 603 | #else // USE_TRANSMISSION 604 | 605 | Lighting.Transmission = 0; 606 | 607 | #endif // USE_TRANSMISSION 608 | 609 | return Lighting; 610 | } 611 | 612 | FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 613 | { 614 | const float3 FuzzColor = ExtractSubsurfaceColor(GBuffer); 615 | const float Cloth = saturate(GBuffer.CustomData.a); 616 | 617 | BxDFContext Context; 618 | Init( Context, N, V, L ); 619 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true ); 620 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 621 | 622 | float3 Spec1; 623 | if( AreaLight.bIsRect ) 624 | Spec1 = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture ); 625 | else 626 | Spec1 = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight ); 627 | 628 | // Cloth - Asperity Scattering - Inverse Beckmann Layer 629 | float D2 = D_InvGGX( Pow4( GBuffer.Roughness ), Context.NoH ); 630 | float Vis2 = Vis_Cloth( Context.NoV, NoL ); 631 | float3 F2 = F_Schlick( FuzzColor, Context.VoH ); 632 | float3 Spec2 = AreaLight.FalloffColor * (Falloff * NoL) * (D2 * Vis2) * F2; 633 | 634 | FDirectLighting Lighting; 635 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor ); 636 | Lighting.Specular = lerp( Spec1, Spec2, Cloth ); 637 | Lighting.Transmission = 0; 638 | return Lighting; 639 | } 640 | 641 | FDirectLighting SubsurfaceBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 642 | { 643 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 644 | 645 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 646 | float Opacity = GBuffer.CustomData.a; 647 | 648 | float3 H = normalize(V + L); 649 | 650 | // to get an effect when you see through the material 651 | // hard coded pow constant 652 | float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, .1f, Opacity); 653 | // wrap around lighting, /(PI*2) to be energy consistent (hack do get some view dependnt and light dependent effect) 654 | // Opacity of 0 gives no normal dependent lighting, Opacity of 1 gives strong normal contribution 655 | float NormalContribution = saturate(dot(N, H) * Opacity + 1 - Opacity); 656 | float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2); 657 | 658 | // lerp to never exceed 1 (energy conserving) 659 | Lighting.Transmission = AreaLight.FalloffColor * ( Falloff * lerp(BackScatter, 1, InScatter) ) * SubsurfaceColor; 660 | 661 | return Lighting; 662 | } 663 | 664 | FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 665 | { 666 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 667 | 668 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 669 | 670 | // http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/ 671 | float Wrap = 0.5; 672 | float WrapNoL = saturate( ( -dot(N, L) + Wrap ) / Square( 1 + Wrap ) ); 673 | 674 | // Scatter distribution 675 | float VoL = dot(V, L); 676 | float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) ); 677 | 678 | Lighting.Transmission = AreaLight.FalloffColor * (Falloff * WrapNoL * Scatter) * SubsurfaceColor; 679 | 680 | return Lighting; 681 | } 682 | 683 | FDirectLighting EyeBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 684 | { 685 | #if IRIS_NORMAL 686 | const float2 CausticNormalDelta = float2( GBuffer.StoredMetallic, GBuffer.StoredSpecular ) * 2 - (256.0/255.0); 687 | const float2 IrisNormalDelta = float2( GBuffer.CustomData.y, GBuffer.CustomData.z ) * 2 - (256.0/255.0); 688 | const float IrisMask = 1.0f - GBuffer.CustomData.w; 689 | 690 | const float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal ); 691 | const float3 CausticNormal = OctahedronToUnitVector( WorldNormalOct + CausticNormalDelta ); 692 | const float3 IrisNormal = OctahedronToUnitVector( WorldNormalOct + IrisNormalDelta ); 693 | #else 694 | const float3 IrisNormal = OctahedronToUnitVector( GBuffer.CustomData.yz * 2 - 1 ); 695 | const float IrisDistance = GBuffer.StoredMetallic; 696 | const float IrisMask = 1.0f - GBuffer.CustomData.w; 697 | 698 | // Blend in the negative intersection normal to create some concavity 699 | // Not great as it ties the concavity to the convexity of the cornea surface 700 | // No good justification for that. On the other hand, if we're just looking to 701 | // introduce some concavity, this does the job. 702 | const float3 CausticNormal = normalize(lerp(IrisNormal, -N, IrisMask*IrisDistance)); 703 | #endif 704 | 705 | BxDFContext Context; 706 | Init( Context, N, V, L ); 707 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, false ); 708 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 709 | Context.VoH = AreaLight.bIsRect ? Context.NoV : Context.VoH; 710 | 711 | // F_Schlick 712 | float F0 = GBuffer.Specular * 0.08; 713 | float Fc = Pow5( 1 - Context.VoH ); 714 | float F = Fc + (1 - Fc) * F0; 715 | 716 | FDirectLighting Lighting; 717 | 718 | if( AreaLight.bIsRect ) 719 | { 720 | Lighting.Specular = RectGGXApproxLTC( GBuffer.Roughness, F0, N, V, AreaLight.Rect, AreaLight.Texture ); 721 | } 722 | else 723 | { 724 | float a2 = Pow4( GBuffer.Roughness ); 725 | float Energy = EnergyNormalization( a2, Context.VoH, AreaLight ); 726 | 727 | // Generalized microfacet specular 728 | float D = D_GGX( a2, Context.NoH ) * Energy; 729 | float Vis = Vis_SmithJointApprox( a2, Context.NoV, NoL ); 730 | 731 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * D * Vis * F; 732 | } 733 | 734 | float IrisNoL = saturate( dot( IrisNormal, L ) ); 735 | float Power = lerp( 12, 1, IrisNoL ); 736 | float Caustic = 0.8 + 0.2 * ( Power + 1 ) * pow( saturate( dot( CausticNormal, L ) ), Power ); 737 | float Iris = IrisNoL * Caustic; 738 | float Sclera = NoL; 739 | 740 | Lighting.Diffuse = 0; 741 | Lighting.Transmission = AreaLight.FalloffColor * ( Falloff * lerp( Sclera, Iris, IrisMask ) * (1 - F) ) * Diffuse_Lambert( GBuffer.DiffuseColor ); 742 | return Lighting; 743 | } 744 | 745 | FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 746 | { 747 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 748 | 749 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 750 | float Opacity = GBuffer.CustomData.a; 751 | 752 | float3 PreintegratedBRDF = Texture2DSampleLevel(View.PreIntegratedBRDF, View.PreIntegratedBRDFSampler, float2(saturate(dot(N, L) * .5 + .5), 1 - Opacity), 0).rgb; 753 | Lighting.Transmission = AreaLight.FalloffColor * Falloff * PreintegratedBRDF * SubsurfaceColor; 754 | 755 | return Lighting; 756 | } 757 | 758 | FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 759 | { 760 | switch( GBuffer.ShadingModelID ) 761 | { 762 | case SHADINGMODELID_DEFAULT_LIT: 763 | case SHADINGMODELID_SINGLELAYERWATER: 764 | case SHADINGMODELID_THIN_TRANSLUCENT: 765 | return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 766 | case SHADINGMODELID_SUBSURFACE: 767 | return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 768 | case SHADINGMODELID_PREINTEGRATED_SKIN: 769 | return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 770 | case SHADINGMODELID_CLEAR_COAT: 771 | return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 772 | case SHADINGMODELID_SUBSURFACE_PROFILE: 773 | return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 774 | case SHADINGMODELID_TWOSIDED_FOLIAGE: 775 | return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 776 | case SHADINGMODELID_HAIR: 777 | return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 778 | case SHADINGMODELID_CLOTH: 779 | return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 780 | case SHADINGMODELID_EYE: 781 | return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 782 | default: 783 | return (FDirectLighting)0; 784 | } 785 | } 786 | 787 | FDirectLighting EvaluateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float NoL, FShadowTerms Shadow ) 788 | { 789 | FAreaLight AreaLight; 790 | AreaLight.SphereSinAlpha = 0; 791 | AreaLight.SphereSinAlphaSoft = 0; 792 | AreaLight.LineCosSubtended = 1; 793 | AreaLight.FalloffColor = 1; 794 | AreaLight.Rect = (FRect)0; 795 | AreaLight.bIsRect = false; 796 | AreaLight.Texture = InitRectTexture(LTCAmpTexture); // Dummy 797 | 798 | return IntegrateBxDF( GBuffer, N, V, L, 1, NoL, AreaLight, Shadow ); 799 | } 800 | -------------------------------------------------------------------------------- /ShadersOverride/Override/Private/ShadingModels.ush: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "DeferredShadingCommon.ush" 6 | #include "BRDF.ush" 7 | #include "FastMath.ush" 8 | #include "CapsuleLight.ush" 9 | #include "RectLight.ush" 10 | #include "TransmissionCommon.ush" 11 | #include "HairBsdf.ush" 12 | 13 | #if 0 14 | void StandardShadingShared( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 V, half3 N ) 15 | { 16 | float NoV = saturate( abs( dot(N, V) ) + 1e-5 ); 17 | 18 | // Diffuse_Lambert 19 | Shared.DiffuseMul = DiffuseColor * (1.0 / PI); 20 | 21 | // D_GGX, Vis_SmithJointApprox 22 | float m = Roughness * Roughness; 23 | Shared.m2 = m * m; 24 | Shared.SpecularMul = (0.5 / PI) * Shared.m2; 25 | Shared.VisMad = float2( 2 * NoV * ( 1 - m ) + m, NoV * m ); 26 | 27 | // F_Schlick 28 | Shared.SpecularMul *= saturate( 50.0 * SpecularColor.g ); 29 | } 30 | 31 | void StandardShadingPerLight( Shared, float3 L, float3 V, half3 N ) 32 | { 33 | float3 H = normalize(V + L); // 3 add, 2 mad, 4 mul, 1 rsqrt 34 | float NoL = saturate( dot(N, L) ); // 2 mad, 1 mul 35 | float NoH = saturate( dot(N, H) ); // 2 mad, 1 mul 36 | float VoH = saturate( dot(V, H) ); // 2 mad, 1 mul 37 | 38 | // D_GGX, Vis_SmithJointApprox 39 | float d = ( NoH * Shared.m2 - NoH ) * NoH + 1; // 2 mad 40 | float v = NoL * Shared.VisMad.x + Shared.VisMad.y; // 1 mad 41 | float D_Vis = Shared.SpecularMul * rcp( d * d * v ); // 3 mul, 1 rcp 42 | 43 | // F_Schlick 44 | float Fc = pow( 1 - VoH, 5 ); // 1 sub, 3 mul 45 | float3 F = Fc + (1 - Fc) * SpecularColor; // 1 sub, 3 mad 46 | 47 | return Shared.DiffuseMul + D_Vis * F; // 3 mad 48 | } 49 | #endif 50 | struct FAreaLight 51 | { 52 | float SphereSinAlpha; 53 | float SphereSinAlphaSoft; 54 | float LineCosSubtended; 55 | 56 | float3 FalloffColor; 57 | 58 | FRect Rect; 59 | FRectTexture Texture; 60 | bool bIsRect; 61 | }; 62 | 63 | struct FDirectLighting 64 | { 65 | float3 Diffuse; 66 | float3 Specular; 67 | float3 Transmission; 68 | }; 69 | 70 | struct FShadowTerms 71 | { 72 | float SurfaceShadow; 73 | float TransmissionShadow; 74 | float TransmissionThickness; 75 | FHairTransmittanceData HairTransmittance; 76 | }; 77 | FDirectLighting HairBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow) 78 | { 79 | const float3 BsdfValue = HairShading(GBuffer, L, V, N, Shadow.TransmissionShadow, Shadow.HairTransmittance, 1, 0, uint2(0, 0)); 80 | 81 | FDirectLighting Lighting; 82 | Lighting.Diffuse = 0; 83 | Lighting.Specular = 0; 84 | Lighting.Transmission = AreaLight.FalloffColor * Falloff * BsdfValue; 85 | return Lighting; 86 | } 87 | 88 | bool IsAreaLight(FAreaLight AreaLight) 89 | { 90 | return ( 91 | AreaLight.bIsRect || 92 | AreaLight.SphereSinAlpha > 0.0f || 93 | AreaLight.SphereSinAlphaSoft > 0.0f || 94 | AreaLight.LineCosSubtended < 1.0f 95 | ) ? true : false; 96 | } 97 | 98 | float New_a2( float a2, float SinAlpha, float VoH ) 99 | { 100 | return a2 + 0.25 * SinAlpha * (3.0 * sqrtFast(a2) + SinAlpha) / ( VoH + 0.001 ); 101 | //return a2 + 0.25 * SinAlpha * ( saturate(12 * a2 + 0.125) + SinAlpha ) / ( VoH + 0.001 ); 102 | //return a2 + 0.25 * SinAlpha * ( a2 * 2 + 1 + SinAlpha ) / ( VoH + 0.001 ); 103 | } 104 | 105 | float EnergyNormalization( inout float a2, float VoH, FAreaLight AreaLight ) 106 | { 107 | if( AreaLight.SphereSinAlphaSoft > 0 ) 108 | { 109 | // Modify Roughness 110 | a2 = saturate( a2 + Pow2( AreaLight.SphereSinAlphaSoft ) / ( VoH * 3.6 + 0.4 ) ); 111 | } 112 | 113 | float Sphere_a2 = a2; 114 | float Energy = 1; 115 | if( AreaLight.SphereSinAlpha > 0 ) 116 | { 117 | Sphere_a2 = New_a2( a2, AreaLight.SphereSinAlpha, VoH ); 118 | Energy = a2 / Sphere_a2; 119 | } 120 | 121 | if( AreaLight.LineCosSubtended < 1 ) 122 | { 123 | #if 1 124 | float LineCosTwoAlpha = AreaLight.LineCosSubtended; 125 | float LineTanAlpha = sqrt( ( 1.0001 - LineCosTwoAlpha ) / ( 1 + LineCosTwoAlpha ) ); 126 | float Line_a2 = New_a2( Sphere_a2, LineTanAlpha, VoH ); 127 | Energy *= sqrt( Sphere_a2 / Line_a2 ); 128 | #else 129 | float LineCosTwoAlpha = AreaLight.LineCosSubtended; 130 | float LineSinAlpha = sqrt( 0.5 - 0.5 * LineCosTwoAlpha ); 131 | float Line_a2 = New_a2( Sphere_a2, LineSinAlpha, VoH ); 132 | Energy *= Sphere_a2 / Line_a2; 133 | #endif 134 | } 135 | 136 | return Energy; 137 | } 138 | 139 | float3 SpecularGGX(float Roughness, float Anisotropy, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight) 140 | { 141 | float Alpha = Roughness * Roughness; 142 | float a2 = Alpha * Alpha; 143 | 144 | FAreaLight Punctual = AreaLight; 145 | Punctual.SphereSinAlpha = 0; 146 | Punctual.SphereSinAlphaSoft = 0; 147 | Punctual.LineCosSubtended = 1; 148 | Punctual.Rect = (FRect)0; 149 | Punctual.bIsRect = false; 150 | 151 | float Energy = EnergyNormalization(a2, Context.VoH, Punctual); 152 | 153 | float ax = 0; 154 | float ay = 0; 155 | GetAnisotropicRoughness(Alpha, Anisotropy, ax, ay); 156 | 157 | // Generalized microfacet specular 158 | float3 D = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH) * Energy; 159 | float3 Vis = Vis_SmithJointAniso(ax, ay, Context.NoV, NoL, Context.XoV, Context.XoL, Context.YoV, Context.YoL); 160 | float3 F = F_Schlick( SpecularColor, Context.VoH ); 161 | 162 | return (D * Vis) * F; 163 | } 164 | 165 | float3 SpecularGGX( float Roughness, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight ) 166 | { 167 | float a2 = Pow4( Roughness ); 168 | float Energy = EnergyNormalization( a2, Context.VoH, AreaLight ); 169 | 170 | // Generalized microfacet specular 171 | float D = D_GGX( a2, Context.NoH ) * Energy; 172 | float Vis = Vis_SmithJointApprox( a2, Context.NoV, NoL ); 173 | float3 F = F_Schlick( SpecularColor, Context.VoH ); 174 | 175 | return (D * Vis) * F; 176 | } 177 | 178 | float3 DualSpecularGGX(float AverageRoughness, float Lobe0Roughness, float Lobe1Roughness, float LobeMix, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight) 179 | { 180 | float AverageAlpha2 = Pow4(AverageRoughness); 181 | float Lobe0Alpha2 = Pow4(Lobe0Roughness); 182 | float Lobe1Alpha2 = Pow4(Lobe1Roughness); 183 | 184 | float Lobe0Energy = EnergyNormalization(Lobe0Alpha2, Context.VoH, AreaLight); 185 | float Lobe1Energy = EnergyNormalization(Lobe1Alpha2, Context.VoH, AreaLight); 186 | 187 | // Generalized microfacet specular 188 | float D = lerp(D_GGX(Lobe0Alpha2, Context.NoH) * Lobe0Energy, D_GGX(Lobe1Alpha2, Context.NoH) * Lobe1Energy, LobeMix); 189 | float Vis = Vis_SmithJointApprox(AverageAlpha2, Context.NoV, NoL); // Average visibility well approximates using two separate ones (one per lobe). 190 | float3 F = F_Schlick(SpecularColor, Context.VoH); 191 | 192 | return (D * Vis) * F; 193 | } 194 | 195 | FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 196 | { 197 | BxDFContext Context; 198 | 199 | #if SUPPORTS_ANISOTROPIC_MATERIALS 200 | bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask); 201 | #else 202 | bool bHasAnisotropy = false; 203 | #endif 204 | 205 | BRANCH 206 | if (bHasAnisotropy) 207 | { 208 | half3 X = GBuffer.WorldTangent; 209 | half3 Y = normalize(cross(N, X)); 210 | Init(Context, N, X, Y, V, L); 211 | } 212 | else 213 | { 214 | Init(Context, N, V, L); 215 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true); 216 | } 217 | 218 | Context.NoV = saturate(abs( Context.NoV ) + 1e-5); 219 | 220 | FDirectLighting Lighting; 221 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor ); 222 | 223 | BRANCH 224 | if (bHasAnisotropy) 225 | { 226 | //Lighting.Specular = GBuffer.WorldTangent * .5f + .5f; 227 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight); 228 | } 229 | else 230 | { 231 | if( AreaLight.bIsRect ) 232 | { 233 | Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 234 | } 235 | else 236 | { 237 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight); 238 | } 239 | } 240 | 241 | Lighting.Transmission = 0; 242 | return Lighting; 243 | } 244 | 245 | float3 SimpleShading( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 L, float3 V, half3 N ) 246 | { 247 | float3 H = normalize(V + L); 248 | float NoH = saturate( dot(N, H) ); 249 | 250 | // Generalized microfacet specular 251 | float D = D_GGX( Pow4(Roughness), NoH ); 252 | float Vis = Vis_Implicit(); 253 | float3 F = F_None( SpecularColor ); 254 | 255 | return Diffuse_Lambert( DiffuseColor ) + (D * Vis) * F; 256 | } 257 | 258 | float3 CalcThinTransmission(float NoL, float NoV, FGBufferData GBuffer) 259 | { 260 | float3 Transmission = 1.0; 261 | float AbsorptionMix = GBuffer.Metallic; 262 | if (AbsorptionMix > 0.0) 263 | { 264 | // Normalized layer thickness documented for clarity 265 | float NormalizedLayerThickness = 1.0; 266 | float ThinDistance = NormalizedLayerThickness * (rcp(NoV) + rcp(NoL)); 267 | 268 | // Base color represents reflected color viewed at 0 incidence angle, after being absorbed through the substrate. 269 | // Because of this, extinction is normalized by traveling through layer thickness twice 270 | float3 TransmissionColor = Diffuse_Lambert(GBuffer.BaseColor); 271 | float3 ExtinctionCoefficient = -log(TransmissionColor) / (2.0 * NormalizedLayerThickness); 272 | float3 OpticalDepth = ExtinctionCoefficient * max(ThinDistance - 2.0 * NormalizedLayerThickness, 0.0); 273 | Transmission = saturate(exp(-OpticalDepth)); 274 | Transmission = lerp(1.0, Transmission, AbsorptionMix); 275 | } 276 | 277 | return Transmission; 278 | } 279 | 280 | float RefractBlend(float VoH, float Eta) 281 | { 282 | // Refraction blend factor for normal component of VoH 283 | float k = 1.0 - Eta * Eta * (1.0 - VoH * VoH); 284 | return Eta * VoH - sqrt(k); 285 | } 286 | 287 | float RefractBlendClearCoatApprox(float VoH) 288 | { 289 | // Polynomial approximation of refraction blend factor for normal component of VoH with fixed Eta (1/1.5): 290 | return (0.63 - 0.22 * VoH) * VoH - 0.745; 291 | } 292 | 293 | float3 Refract(float3 V, float3 H, float Eta) 294 | { 295 | // Assumes V points away from the point of incidence 296 | float VoH = dot(V, H); 297 | return RefractBlend(VoH, Eta) * H - Eta * V; 298 | } 299 | 300 | BxDFContext RefractClearCoatContext(BxDFContext Context) 301 | { 302 | // Reference: Propagation of refraction through dot-product NoV 303 | // Note: This version of Refract requires V to point away from the point of incidence 304 | // NoV2 = -dot(N, Refract(V, H, Eta)) 305 | // NoV2 = -dot(N, RefractBlend(VoH, Eta) * H - Eta * V) 306 | // NoV2 = -(RefractBlend(VoH, Eta) * NoH - Eta * NoV) 307 | // NoV2 = Eta * NoV - RefractBlend(VoH, Eta) * NoH 308 | // NoV2 = 1.0 / 1.5 * NoV - RefractBlendClearCoatApprox(VoH) * NoH 309 | 310 | BxDFContext RefractedContext = Context; 311 | float Eta = 1.0 / 1.5; 312 | float RefractionBlendFactor = RefractBlendClearCoatApprox(Context.VoH); 313 | float RefractionProjectionTerm = RefractionBlendFactor * Context.NoH; 314 | RefractedContext.NoV = saturate(Eta * Context.NoV - RefractionProjectionTerm); 315 | RefractedContext.NoL = saturate(Eta * Context.NoL - RefractionProjectionTerm); 316 | RefractedContext.VoH = saturate(Eta * Context.VoH - RefractionBlendFactor); 317 | RefractedContext.VoL = 2.0 * RefractedContext.VoH * RefractedContext.VoH - 1.0; 318 | RefractedContext.NoH = Context.NoH; 319 | return RefractedContext; 320 | } 321 | 322 | FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 323 | { 324 | const float ClearCoat = GBuffer.CustomData.x; 325 | const float ClearCoatRoughness = max(GBuffer.CustomData.y, 0.02f); 326 | const float Film = 1 * ClearCoat; 327 | const float MetalSpec = 0.9; 328 | 329 | FDirectLighting Lighting = { 330 | float3(0.0, 0.0, 0.0), 331 | float3(0.0, 0.0, 0.0), 332 | float3(0.0, 0.0, 0.0) 333 | }; 334 | 335 | BxDFContext Context; 336 | half3 Nspec = N; 337 | 338 | if (CLEAR_COAT_BOTTOM_NORMAL) 339 | { 340 | Nspec = GBuffer.WorldNormal; 341 | } 342 | 343 | #if SUPPORTS_ANISOTROPIC_MATERIALS 344 | bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask); 345 | #else 346 | bool bHasAnisotropy = false; 347 | #endif 348 | 349 | half3 X = 0; 350 | half3 Y = 0; 351 | 352 | BRANCH 353 | if (bHasAnisotropy) 354 | { 355 | X = GBuffer.WorldTangent; 356 | Y = normalize(cross(N, X)); 357 | Init(Context, Nspec, X, Y, V, L); 358 | } 359 | else 360 | { 361 | Init(Context, Nspec, V, L); 362 | } 363 | 364 | // Modify SphereSinAlpha, knowing that it was previously manipulated by roughness of the under coat 365 | // Note: the operation is not invertible for GBuffer.Roughness = 1.0, so roughness is clamped to 254.0/255.0 366 | float SphereSinAlpha = AreaLight.SphereSinAlpha; 367 | float RoughnessCompensation = 1 - Pow2(GBuffer.Roughness); 368 | float Alpha = Pow2(ClearCoatRoughness); 369 | RoughnessCompensation = RoughnessCompensation > 0.0 ? (1 - Alpha) / RoughnessCompensation : 0.0; 370 | AreaLight.SphereSinAlpha = saturate(AreaLight.SphereSinAlpha * RoughnessCompensation); 371 | 372 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, CLEAR_COAT_BOTTOM_NORMAL == 0); 373 | Context.NoV = saturate(abs(Context.NoV) + 1e-5); 374 | Context.VoH = AreaLight.bIsRect ? Context.NoV : Context.VoH; 375 | 376 | // Hard-coded Fresnel evaluation with IOR = 1.5 (for polyurethane cited by Disney BRDF) 377 | float F0 = 0.04; 378 | float Fc = Pow5(1 - Context.VoH); 379 | float F = Fc + (1 - Fc) * F0; 380 | 381 | if (AreaLight.bIsRect) 382 | { 383 | Lighting.Specular = ClearCoat * RectGGXApproxLTC(ClearCoatRoughness, F0, Nspec, V, AreaLight.Rect, AreaLight.Texture); 384 | } 385 | else 386 | { 387 | // Generalized microfacet specular 388 | float a2 = Pow2(Alpha); 389 | float ClearCoatEnergy = EnergyNormalization(a2, Context.VoH, AreaLight); 390 | float D = D_GGX(a2, Context.NoH) * ClearCoatEnergy; 391 | float Vis = Vis_SmithJointApprox(a2, Context.NoV, NoL); 392 | 393 | float Fr1 = D * Vis * F; 394 | Lighting.Specular = ClearCoat * AreaLight.FalloffColor * (Falloff * NoL * Fr1); 395 | } 396 | AreaLight.SphereSinAlpha = SphereSinAlpha; 397 | 398 | // Incoming and exiting Fresnel terms are identical to incoming Fresnel term (VoH == HoL) 399 | // float FresnelCoeff = (1.0 - F1) * (1.0 - F2); 400 | float FresnelCoeff = 1.0 - F; 401 | FresnelCoeff *= FresnelCoeff; 402 | 403 | if (CLEAR_COAT_BOTTOM_NORMAL) 404 | { 405 | BxDFContext TempContext; 406 | 407 | BRANCH 408 | if (bHasAnisotropy) 409 | { 410 | Init(TempContext, N, X, Y, V, L); 411 | } 412 | else 413 | { 414 | Init(TempContext, Nspec, V, L); 415 | } 416 | 417 | // If bottom-normal, update normal-based dot products: 418 | float3 H = normalize(V + L); 419 | Context.NoH = saturate(dot(N, H)); 420 | Context.NoV = saturate(dot(N, V)); 421 | Context.NoL = saturate(dot(N, L)); 422 | Context.VoL = saturate(dot(V, L)); 423 | Context.VoH = saturate(dot(V, H)); 424 | 425 | Context.XoV = TempContext.XoV; 426 | Context.XoL = TempContext.XoL; 427 | Context.XoH = TempContext.XoH; 428 | Context.YoV = TempContext.YoV; 429 | Context.YoL = TempContext.YoL; 430 | Context.YoH = TempContext.YoH; 431 | 432 | if (!bHasAnisotropy) 433 | { 434 | bool bNewtonIteration = true; 435 | SphereMaxNoH(Context, AreaLight.SphereSinAlpha, bNewtonIteration); 436 | } 437 | 438 | Context.NoV = saturate(abs(Context.NoV) + 1e-5); 439 | } 440 | 441 | // Propagate refraction through dot-products rather than the original vectors: 442 | // Reference: 443 | // float Eta = 1.0 / 1.5; 444 | // float3 H = normalize(V + L); 445 | // float3 V2 = Refract(V, H, Eta); 446 | // float3 L2 = reflect(V2, H); 447 | // V2 = -V2; 448 | // BxDFContext BottomContext; 449 | // Init(BottomContext, N, X, Y, V2, L2); 450 | BxDFContext BottomContext = RefractClearCoatContext(Context); 451 | BottomContext.VoH = AreaLight.bIsRect ? BottomContext.NoV : BottomContext.VoH; 452 | 453 | // Absorption 454 | float3 Transmission = CalcThinTransmission(BottomContext.NoL, BottomContext.NoV, GBuffer); 455 | 456 | // Default Lit 457 | float3 DefaultDiffuse = (Falloff * NoL) * AreaLight.FalloffColor * Diffuse_Lambert(GBuffer.DiffuseColor); 458 | float3 RefractedDiffuse = FresnelCoeff * Transmission * DefaultDiffuse; 459 | Lighting.Diffuse = lerp(DefaultDiffuse, RefractedDiffuse, ClearCoat); 460 | 461 | if (!bHasAnisotropy && AreaLight.bIsRect) 462 | { 463 | // Note: V is used instead of V2 because LTC integration is not tuned to handle refraction direction 464 | float3 DefaultSpecular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 465 | float3 RefractedSpecular = FresnelCoeff * Transmission * DefaultSpecular; 466 | Lighting.Specular += lerp(DefaultSpecular, RefractedSpecular, ClearCoat); 467 | } 468 | else 469 | { 470 | float a2 = Pow4(GBuffer.Roughness); 471 | float D2 = 0; 472 | float Vis2 = 0; 473 | 474 | BRANCH 475 | if (bHasAnisotropy) 476 | { 477 | float ax = 0; 478 | float ay = 0; 479 | GetAnisotropicRoughness(Alpha, GBuffer.Anisotropy, ax, ay); 480 | 481 | D2 = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH); 482 | Vis2 = Vis_SmithJointAniso(ax, ay, BottomContext.NoV, BottomContext.NoL, BottomContext.XoV, BottomContext.XoL, BottomContext.YoV, BottomContext.YoL); 483 | } 484 | else 485 | { 486 | D2 = D_GGX(a2, BottomContext.NoH); 487 | // NoL is chosen to provide better parity with DefaultLit when ClearCoat=0 488 | Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, NoL); 489 | } 490 | // When refracting into a non-metallic substrate, the IOR ratio (Eta) between top and bottom interfaces approaches 1.0 and drives Fresnel reflectance to 0 491 | float3 F = F_Schlick(GBuffer.SpecularColor, BottomContext.VoH); 492 | float3 RefractedF = lerp(0.0, F, GBuffer.Metallic); 493 | 494 | float Energy = 0; 495 | 496 | BRANCH 497 | if (bHasAnisotropy) 498 | { 499 | FAreaLight Punctual = AreaLight; 500 | Punctual.SphereSinAlpha = 0; 501 | Punctual.SphereSinAlphaSoft = 0; 502 | Punctual.LineCosSubtended = 1; 503 | Punctual.Rect = (FRect)0; 504 | Punctual.bIsRect = false; 505 | 506 | Energy = EnergyNormalization(a2, Context.VoH, Punctual); 507 | } 508 | else 509 | { 510 | Energy = EnergyNormalization(a2, Context.VoH, AreaLight); 511 | } 512 | 513 | // Note: reusing D, V, and F from refracted context to save computation for when ClearCoat < 1 514 | float3 CommonSpecular = (Energy * Falloff * D2 * Vis2) * AreaLight.FalloffColor; 515 | float3 DefaultSpecular = F * NoL; 516 | float3 RefractedSpecular = FresnelCoeff * Transmission * RefractedF * BottomContext.NoL; 517 | Lighting.Specular += CommonSpecular * lerp(DefaultSpecular, RefractedSpecular, ClearCoat); 518 | } 519 | 520 | return Lighting; 521 | } 522 | 523 | // HG phase function approximated 524 | float ApproximateHG(float cosJ, float g) 525 | { 526 | float g2 = g * g; 527 | float gcos2 = 1.0f - (g * cosJ); 528 | gcos2 *= gcos2; 529 | 530 | const float ISO_PHASE_FUNC_Normalized = 0.5; 531 | 532 | return (ISO_PHASE_FUNC_Normalized * (1.0f - g2) / max( 1e-5, gcos2)); 533 | } 534 | 535 | void GetProfileDualSpecular(FGBufferData GBuffer, out float AverageToRoughness0, out float AverageToRoughness1, out float LobeMix) 536 | { 537 | #if !FORWARD_SHADING 538 | // 0..255, which SubSurface profile to pick 539 | uint SubsurfaceProfileInt = ExtractSubsurfaceProfileInt(GBuffer); 540 | 541 | float4 Data = ActualSSProfilesTexture.Load(int3(SSSS_DUAL_SPECULAR_OFFSET, SubsurfaceProfileInt, 0)); 542 | AverageToRoughness0 = Data.x * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 543 | AverageToRoughness1 = Data.y * SSSS_MAX_DUAL_SPECULAR_ROUGHNESS; 544 | LobeMix = Data.z; 545 | #else 546 | // Disable dual lobe, as subsurface profile doesn't work with forward. 547 | AverageToRoughness0 = 1.f; 548 | AverageToRoughness1 = 1.f; 549 | LobeMix = 0.f; 550 | #endif 551 | } 552 | 553 | FDirectLighting SubsurfaceProfileBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 554 | { 555 | BxDFContext Context; 556 | Init( Context, N, V, L ); 557 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true ); 558 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 559 | 560 | float AverageToRoughness0; 561 | float AverageToRoughness1; 562 | float LobeMix; 563 | GetProfileDualSpecular(GBuffer, AverageToRoughness0, AverageToRoughness1, LobeMix); 564 | 565 | float AverageRoughness = GBuffer.Roughness; 566 | float Lobe0Roughness = max(saturate(AverageRoughness * AverageToRoughness0), 0.02f); 567 | float Lobe1Roughness = saturate(AverageRoughness * AverageToRoughness1); 568 | 569 | // Smoothly lerp to default single GGX lobe as Opacity approaches 0, before reverting to SHADINGMODELID_DEFAULT_LIT. 570 | // See SUBSURFACE_PROFILE_OPACITY_THRESHOLD in ShadingModelsMaterial.ush. 571 | float Opacity = GBuffer.CustomData.a; 572 | Lobe0Roughness = lerp(1.0f, Lobe0Roughness, saturate(Opacity * 10.0f)); 573 | Lobe1Roughness = lerp(1.0f, Lobe1Roughness, saturate(Opacity * 10.0f)); 574 | 575 | FDirectLighting Lighting; 576 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Burley( GBuffer.DiffuseColor, GBuffer.Roughness, Context.NoV, NoL, Context.VoH ); 577 | 578 | if (AreaLight.bIsRect) 579 | { 580 | float3 Lobe0Specular = RectGGXApproxLTC(Lobe0Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 581 | float3 Lobe1Specular = RectGGXApproxLTC(Lobe1Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture); 582 | Lighting.Specular = lerp(Lobe0Specular, Lobe1Specular, LobeMix); 583 | } 584 | else 585 | { 586 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * DualSpecularGGX(AverageRoughness, Lobe0Roughness, Lobe1Roughness, LobeMix, GBuffer.SpecularColor, Context, NoL, AreaLight); 587 | } 588 | 589 | 590 | #if USE_TRANSMISSION 591 | 592 | FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams( GBuffer ); 593 | 594 | float Thickness = Shadow.TransmissionThickness; 595 | Thickness = DecodeThickness(Thickness); 596 | Thickness *= SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE; 597 | float3 Profile = GetTransmissionProfile(GBuffer, Thickness).rgb; 598 | 599 | float3 RefracV = refract(V, -N, TransmissionParams.OneOverIOR); 600 | float PhaseFunction = ApproximateHG( dot(-L, RefracV), TransmissionParams.ScatteringDistribution ); 601 | Lighting.Transmission = AreaLight.FalloffColor * Profile * (Falloff * PhaseFunction); // TODO: This probably should also include cosine term (NoL) 602 | 603 | #else // USE_TRANSMISSION 604 | 605 | Lighting.Transmission = 0; 606 | 607 | #endif // USE_TRANSMISSION 608 | 609 | return Lighting; 610 | } 611 | 612 | FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 613 | { 614 | const float3 FuzzColor = ExtractSubsurfaceColor(GBuffer); 615 | const float Cloth = saturate(GBuffer.CustomData.a); 616 | 617 | BxDFContext Context; 618 | Init( Context, N, V, L ); 619 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true ); 620 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 621 | 622 | float3 Spec1; 623 | if( AreaLight.bIsRect ) 624 | Spec1 = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture ); 625 | else 626 | Spec1 = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight ); 627 | 628 | // Cloth - Asperity Scattering - Inverse Beckmann Layer 629 | float D2 = D_InvGGX( Pow4( GBuffer.Roughness ), Context.NoH ); 630 | float Vis2 = Vis_Cloth( Context.NoV, NoL ); 631 | float3 F2 = F_Schlick( FuzzColor, Context.VoH ); 632 | float3 Spec2 = AreaLight.FalloffColor * (Falloff * NoL) * (D2 * Vis2) * F2; 633 | 634 | FDirectLighting Lighting; 635 | Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor ); 636 | Lighting.Specular = lerp( Spec1, Spec2, Cloth ); 637 | Lighting.Transmission = 0; 638 | return Lighting; 639 | } 640 | 641 | FDirectLighting SubsurfaceBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 642 | { 643 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 644 | 645 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 646 | float Opacity = GBuffer.CustomData.a; 647 | 648 | float3 H = normalize(V + L); 649 | 650 | // to get an effect when you see through the material 651 | // hard coded pow constant 652 | float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, .1f, Opacity); 653 | // wrap around lighting, /(PI*2) to be energy consistent (hack do get some view dependnt and light dependent effect) 654 | // Opacity of 0 gives no normal dependent lighting, Opacity of 1 gives strong normal contribution 655 | float NormalContribution = saturate(dot(N, H) * Opacity + 1 - Opacity); 656 | float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2); 657 | 658 | // lerp to never exceed 1 (energy conserving) 659 | Lighting.Transmission = AreaLight.FalloffColor * ( Falloff * lerp(BackScatter, 1, InScatter) ) * SubsurfaceColor; 660 | 661 | return Lighting; 662 | } 663 | 664 | FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 665 | { 666 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 667 | 668 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 669 | 670 | // http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/ 671 | float Wrap = 0.5; 672 | float WrapNoL = saturate( ( -dot(N, L) + Wrap ) / Square( 1 + Wrap ) ); 673 | 674 | // Scatter distribution 675 | float VoL = dot(V, L); 676 | float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) ); 677 | 678 | Lighting.Transmission = AreaLight.FalloffColor * (Falloff * WrapNoL * Scatter) * SubsurfaceColor; 679 | 680 | return Lighting; 681 | } 682 | 683 | FDirectLighting EyeBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 684 | { 685 | #if IRIS_NORMAL 686 | const float2 CausticNormalDelta = float2( GBuffer.StoredMetallic, GBuffer.StoredSpecular ) * 2 - (256.0/255.0); 687 | const float2 IrisNormalDelta = float2( GBuffer.CustomData.y, GBuffer.CustomData.z ) * 2 - (256.0/255.0); 688 | const float IrisMask = 1.0f - GBuffer.CustomData.w; 689 | 690 | const float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal ); 691 | const float3 CausticNormal = OctahedronToUnitVector( WorldNormalOct + CausticNormalDelta ); 692 | const float3 IrisNormal = OctahedronToUnitVector( WorldNormalOct + IrisNormalDelta ); 693 | #else 694 | const float3 IrisNormal = OctahedronToUnitVector( GBuffer.CustomData.yz * 2 - 1 ); 695 | const float IrisDistance = GBuffer.StoredMetallic; 696 | const float IrisMask = 1.0f - GBuffer.CustomData.w; 697 | 698 | // Blend in the negative intersection normal to create some concavity 699 | // Not great as it ties the concavity to the convexity of the cornea surface 700 | // No good justification for that. On the other hand, if we're just looking to 701 | // introduce some concavity, this does the job. 702 | const float3 CausticNormal = normalize(lerp(IrisNormal, -N, IrisMask*IrisDistance)); 703 | #endif 704 | 705 | BxDFContext Context; 706 | Init( Context, N, V, L ); 707 | SphereMaxNoH( Context, AreaLight.SphereSinAlpha, false ); 708 | Context.NoV = saturate( abs( Context.NoV ) + 1e-5 ); 709 | Context.VoH = AreaLight.bIsRect ? Context.NoV : Context.VoH; 710 | 711 | // F_Schlick 712 | float F0 = GBuffer.Specular * 0.08; 713 | float Fc = Pow5( 1 - Context.VoH ); 714 | float F = Fc + (1 - Fc) * F0; 715 | 716 | FDirectLighting Lighting; 717 | 718 | if( AreaLight.bIsRect ) 719 | { 720 | Lighting.Specular = RectGGXApproxLTC( GBuffer.Roughness, F0, N, V, AreaLight.Rect, AreaLight.Texture ); 721 | } 722 | else 723 | { 724 | float a2 = Pow4( GBuffer.Roughness ); 725 | float Energy = EnergyNormalization( a2, Context.VoH, AreaLight ); 726 | 727 | // Generalized microfacet specular 728 | float D = D_GGX( a2, Context.NoH ) * Energy; 729 | float Vis = Vis_SmithJointApprox( a2, Context.NoV, NoL ); 730 | 731 | Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * D * Vis * F; 732 | } 733 | 734 | float IrisNoL = saturate( dot( IrisNormal, L ) ); 735 | float Power = lerp( 12, 1, IrisNoL ); 736 | float Caustic = 0.8 + 0.2 * ( Power + 1 ) * pow( saturate( dot( CausticNormal, L ) ), Power ); 737 | float Iris = IrisNoL * Caustic; 738 | float Sclera = NoL; 739 | 740 | Lighting.Diffuse = 0; 741 | Lighting.Transmission = AreaLight.FalloffColor * ( Falloff * lerp( Sclera, Iris, IrisMask ) * (1 - F) ) * Diffuse_Lambert( GBuffer.DiffuseColor ); 742 | return Lighting; 743 | } 744 | 745 | FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 746 | { 747 | FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 748 | 749 | float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); 750 | float Opacity = GBuffer.CustomData.a; 751 | 752 | float3 PreintegratedBRDF = Texture2DSampleLevel(View.PreIntegratedBRDF, View.PreIntegratedBRDFSampler, float2(saturate(dot(N, L) * .5 + .5), 1 - Opacity), 0).rgb; 753 | Lighting.Transmission = AreaLight.FalloffColor * Falloff * PreintegratedBRDF * SubsurfaceColor; 754 | 755 | return Lighting; 756 | } 757 | 758 | #include "Toon/ToonShaderModels.usf" 759 | 760 | FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow ) 761 | { 762 | switch( GBuffer.ShadingModelID ) 763 | { 764 | case SHADINGMODELID_DEFAULT_LIT: 765 | case SHADINGMODELID_SINGLELAYERWATER: 766 | case SHADINGMODELID_THIN_TRANSLUCENT: 767 | return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 768 | case SHADINGMODELID_STYLIZED_SHADOW: 769 | return StylizedShadowBxDF(GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow); 770 | case SHADINGMODELID_SUBSURFACE: 771 | return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 772 | case SHADINGMODELID_PREINTEGRATED_SKIN: 773 | return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 774 | case SHADINGMODELID_CLEAR_COAT: 775 | return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 776 | case SHADINGMODELID_SUBSURFACE_PROFILE: 777 | return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 778 | case SHADINGMODELID_TWOSIDED_FOLIAGE: 779 | return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 780 | case SHADINGMODELID_HAIR: 781 | return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 782 | case SHADINGMODELID_CLOTH: 783 | return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 784 | case SHADINGMODELID_EYE: 785 | return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow ); 786 | default: 787 | return (FDirectLighting)0; 788 | } 789 | } 790 | 791 | FDirectLighting EvaluateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float NoL, FShadowTerms Shadow ) 792 | { 793 | FAreaLight AreaLight; 794 | AreaLight.SphereSinAlpha = 0; 795 | AreaLight.SphereSinAlphaSoft = 0; 796 | AreaLight.LineCosSubtended = 1; 797 | AreaLight.FalloffColor = 1; 798 | AreaLight.Rect = (FRect)0; 799 | AreaLight.bIsRect = false; 800 | AreaLight.Texture = InitRectTexture(LTCAmpTexture); // Dummy 801 | 802 | return IntegrateBxDF( GBuffer, N, V, L, 1, NoL, AreaLight, Shadow ); 803 | } 804 | --------------------------------------------------------------------------------