diff --git a/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli b/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli new file mode 100644 index 0000000000..40a13db510 --- /dev/null +++ b/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli @@ -0,0 +1,50 @@ +namespace ExtendedTranslucency +{ + namespace MaterialModel + { + static const uint Default = 0; // Use ExtendedTranslucencySettings + static const uint RimLight = 1; + static const uint IsotropicFabric = 2; + static const uint AnisotropicFabric = 3; + static const uint Disabled = 4; // Any value >= 4 + } + + bool IsValidMaterial(uint Material) + { + return Material > 0 && Material < MaterialModel::Disabled; + } + + uint GetMaterialModelFromDescriptor(uint Descriptor) + { + // TerrainHelper : 6 + // ExtraFeatureDescriptor : 3 + return (Descriptor >> 6) & 7; + } + + float GetViewDependentAlphaNaive(float alpha, float3 view, float3 normal) + { + return 1.0 - (1.0 - alpha) * dot(view, normal); + } + + float GetViewDependentAlphaFabric1D(float alpha, float3 view, float3 normal) + { + return alpha / min(1.0, (abs(dot(view, normal)) + 0.001)); + } + + float GetViewDependentAlphaFabric2D(float alpha, float3 view, float3x3 tbnTr) + { + float3 t = tbnTr[0]; + float3 b = tbnTr[1]; + float3 n = tbnTr[2]; + float3 v = view; + float a0 = 1 - sqrt(1.0 - alpha); + return a0 * (length(cross(v, t)) + length(cross(v, b))) / (abs(dot(v, n)) + 0.001) - a0 * a0; + } + + float SoftClamp(float alpha, float limit) + { + // soft clamp [alpha,1] and remap the transparency + alpha = min(alpha, limit / (1 + exp(-4 * (alpha - limit * 0.5) / limit))); + return saturate(alpha); + } +} diff --git a/features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini b/features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini new file mode 100644 index 0000000000..000b60a568 --- /dev/null +++ b/features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini @@ -0,0 +1,2 @@ +[Info] +Version = 1-0-0 diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 61bca68556..081236b331 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -183,6 +183,14 @@ namespace SharedData uint3 pad; }; + struct ExtendedTranslucencySettings + { + uint MaterialModel; // [0,1,2,3] The MaterialModel + float Reduction; // [0, 1.0] The factor to reduce the transparency to matain the average transparency [0,1] + float Softness; // [0, 2.0] The soft remap upper limit [0,2] + float Strength; // [0, 1.0] The inverse blend weight of the effect + }; + cbuffer FeatureData : register(b6) { GrassLightingSettings grassLightingSettings; @@ -197,6 +205,7 @@ namespace SharedData HairSpecularSettings hairSpecularSettings; TerrainVariationSettings terrainVariationSettings; IBLSettings iblSettings; + ExtendedTranslucencySettings extendedTranslucencySettings; }; Texture2D DepthTexture : register(t17); diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 6e1029129f..190e7d58c2 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1024,6 +1024,11 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "TerrainVariation/TerrainVariation.hlsli" # endif +# if defined(EXTENDED_TRANSLUCENCY) && !(defined(LOD) || defined(SKIN) || defined(HAIR) || defined(EYE) || defined(TREE_ANIM) || defined(LODOBJECTSHD) || defined(LODOBJECTS) || defined(DEPTH_WRITE_DECALS)) +# include "ExtendedTranslucency/ExtendedTranslucency.hlsli" +# define ANISOTROPIC_ALPHA +# endif + # define LinearSampler SampColorSampler # include "Common/ShadowSampling.hlsli" @@ -3108,6 +3113,47 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) discard; } # endif // DO_ALPHA_TEST + +# if defined(ANISOTROPIC_ALPHA) + // Uniform alpha material settings + uint AlphaMaterialModel = ExtendedTranslucency::GetMaterialModelFromDescriptor(Permutation::ExtraFeatureDescriptor); + float AlphaMaterialReduction = 0.f; + float AlphaMaterialSoftness = 0.f; + float AlphaMaterialStrength = 0.f; + if (AlphaMaterialModel == ExtendedTranslucency::MaterialModel::Default) { + AlphaMaterialModel = SharedData::extendedTranslucencySettings.MaterialModel; + AlphaMaterialReduction = SharedData::extendedTranslucencySettings.Reduction; + AlphaMaterialSoftness = SharedData::extendedTranslucencySettings.Softness; + AlphaMaterialStrength = SharedData::extendedTranslucencySettings.Strength; + } + + [branch] if (ExtendedTranslucency::IsValidMaterial(AlphaMaterialModel)) + { + if (alpha >= 0.0156862754 && alpha < 1.0) { + float originalAlpha = alpha; + alpha = alpha * (1.0 - AlphaMaterialReduction); + [branch] if (AlphaMaterialModel == ExtendedTranslucency::MaterialModel::AnisotropicFabric) + { +# if defined(SKINNED) || !defined(MODELSPACENORMALS) + alpha = ExtendedTranslucency::GetViewDependentAlphaFabric2D(alpha, viewDirection, tbnTr); +# else + alpha = ExtendedTranslucency::GetViewDependentAlphaFabric1D(alpha, viewDirection, modelNormal.xyz); +# endif + } + else if (AlphaMaterialModel == ExtendedTranslucency::MaterialModel::IsotropicFabric) + { + alpha = ExtendedTranslucency::GetViewDependentAlphaFabric1D(alpha, viewDirection, modelNormal.xyz); + } + else + { + alpha = ExtendedTranslucency::GetViewDependentAlphaNaive(alpha, viewDirection, modelNormal.xyz); + } + alpha = saturate(ExtendedTranslucency::SoftClamp(alpha, 2.0f - AlphaMaterialSoftness)); + alpha = lerp(alpha, originalAlpha, AlphaMaterialStrength); + } + } +# endif // ANISOTROPIC_ALPHA + psout.Diffuse.w = alpha; # endif diff --git a/src/Feature.cpp b/src/Feature.cpp index 42ff7df094..6b5980f2ae 100644 --- a/src/Feature.cpp +++ b/src/Feature.cpp @@ -5,6 +5,7 @@ #include "Features/CloudShadows.h" #include "Features/DynamicCubemaps.h" #include "Features/ExtendedMaterials.h" +#include "Features/ExtendedTranslucency.h" #include "Features/GrassCollision.h" #include "Features/GrassLighting.h" #include "Features/HairSpecular.h" @@ -207,7 +208,8 @@ const std::vector& Feature::GetFeatureList() globals::features::hairSpecular, globals::features::interiorSunShadows, globals::features::terrainVariation, - globals::features::ibl + globals::features::ibl, + globals::features::extendedTranslucency }; static std::vector featuresVR = [] { diff --git a/src/FeatureBuffer.cpp b/src/FeatureBuffer.cpp index e8c00e9ec2..359139749a 100644 --- a/src/FeatureBuffer.cpp +++ b/src/FeatureBuffer.cpp @@ -3,6 +3,7 @@ #include "Features/CloudShadows.h" #include "Features/DynamicCubemaps.h" #include "Features/ExtendedMaterials.h" +#include "Features/ExtendedTranslucency.h" #include "Features/GrassLighting.h" #include "Features/HairSpecular.h" #include "Features/IBL.h" @@ -45,5 +46,6 @@ std::pair GetFeatureBufferData(bool a_inWorld) globals::features::lodBlending->settings, globals::features::hairSpecular->settings, globals::features::terrainVariation->settings, - globals::features::ibl->settings); + globals::features::ibl->settings, + globals::features::extendedTranslucency->settings); } \ No newline at end of file diff --git a/src/Features/ExtendedTranslucency.cpp b/src/Features/ExtendedTranslucency.cpp new file mode 100644 index 0000000000..8f3298c2cf --- /dev/null +++ b/src/Features/ExtendedTranslucency.cpp @@ -0,0 +1,133 @@ +#include "ExtendedTranslucency.h" + +#include "../ShaderCache.h" +#include "../State.h" +#include "../Util.h" + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + ExtendedTranslucency::MaterialParams, + AlphaMode, + AlphaReduction, + AlphaSoftness, + AlphaStrength); + +const RE::BSFixedString ExtendedTranslucency::NiExtraDataName_AnisotropicAlphaMaterial = "AnisotropicAlphaMaterial"; + +ExtendedTranslucency* ExtendedTranslucency::GetSingleton() +{ + static ExtendedTranslucency singleton; + return &singleton; +} + +void ExtendedTranslucency::BSLightingShader_SetupGeometry(RE::BSRenderPass* pass) +{ + globals::state->currentExtraFeatureDescriptor &= ~(ExtraFeatureDescriptorMask << ExtraFeatureDescriptorShift); + // TODO: PERFORMANCE: Caching the feature descriptor in map if this get more complex + if (auto* data = pass->geometry->GetExtraData(NiExtraDataName_AnisotropicAlphaMaterial)) { + static const REL::Relocation NiIntegerExtraDataRTTI{ RE::NiIntegerExtraData::Ni_RTTI }; + // netimmerse_cast(data) seems not working here + if (data->GetRTTI() == NiIntegerExtraDataRTTI.get()) { + uint32_t material = static_cast(static_cast(data)->value) & ExtraFeatureDescriptorMask; + if (material == MaterialModel::Disabled) { + // MaterialModel::Disabled (0) is the flag when this extra does not exist + // And it will let the effect use default settings instead of force disable it + // Ensure this is disabled by using the ForceDisabled flag + material = MaterialModel::ForceDisabled; + } + globals::state->currentExtraFeatureDescriptor |= (material << ExtraFeatureDescriptorShift); + + // TODO: Per-material settings from Nif + // Mods supporting this feature should adjust their alpha value in texture already + // And the texture should be adjusted based on full strength param + } + } +} + +struct ExtendedTranslucency::Hooks +{ + struct BSLightingShader_SetupGeometry + { + static void thunk(RE::BSShader* This, RE::BSRenderPass* Pass, uint32_t RenderFlags) + { + ExtendedTranslucency::BSLightingShader_SetupGeometry(Pass); + func(This, Pass, RenderFlags); + } + static inline REL::Relocation func; + }; + + static void Install() + { + stl::write_vfunc<0x6, BSLightingShader_SetupGeometry>(RE::VTABLE_BSLightingShader[0]); + logger::info("[ExtendedTranslucency] Installed hooks - BSLightingShader_SetupGeometry"); + } +}; + +void ExtendedTranslucency::PostPostLoad() +{ + Hooks::Install(); +} + +void ExtendedTranslucency::DrawSettings() +{ + if (ImGui::TreeNodeEx("Translucent Material", ImGuiTreeNodeFlags_DefaultOpen)) { + static const char* AlphaModeNames[4] = { + "Disabled", + "Rim Light", + "Isotropic Fabric", + "Anisotropic Fabric" + }; + + bool changed = false; + if (ImGui::Combo("Default Material Model", (int*)&settings.AlphaMode, AlphaModeNames, 4)) { + changed = true; + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Anisotropic transluency will make the surface more opaque when you view it parallel to the surface.\n" + " - Disabled: No anisotropic transluency\n" + " - Rim Light: Naive rim light effect\n" + " - Isotropic Fabric: Imaginary fabric weaved from threads in one direction, respect normal map.\n" + " - Anisotropic Fabric: Common fabric weaved from tangent and birnormal direction, ignores normal map.\n"); + } + + if (ImGui::SliderFloat("Transparency Increase", &settings.AlphaReduction, 0.f, 1.f)) { + changed = true; + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Transluent material will make the material more opaque on average, which could be different from the intent, reduce the alpha to counter this effect and increase the dynamic range of the output."); + } + + if (ImGui::SliderFloat("Softness", &settings.AlphaSoftness, 0.0f, 1.0f)) { + changed = true; + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Control the softness of the alpha increase, increase the softness reduce the increased amount of alpha."); + } + + if (ImGui::SliderFloat("Blend Weight", &settings.AlphaStrength, 0.0f, 1.0f)) { + changed = true; + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Control the blend weight of the effect applied to the final result."); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::TreePop(); + } +} + +void ExtendedTranslucency::LoadSettings(json& o_json) +{ + settings = o_json; +} + +void ExtendedTranslucency::SaveSettings(json& o_json) +{ + o_json = settings; +} + +void ExtendedTranslucency::RestoreDefaultSettings() +{ + settings = {}; +} diff --git a/src/Features/ExtendedTranslucency.h b/src/Features/ExtendedTranslucency.h new file mode 100644 index 0000000000..db43d3b77a --- /dev/null +++ b/src/Features/ExtendedTranslucency.h @@ -0,0 +1,53 @@ +#pragma once + +#include "../Buffer.h" +#include "../Feature.h" + +struct ExtendedTranslucency final : Feature +{ + static ExtendedTranslucency* GetSingleton(); + + virtual inline std::string GetName() override { return "Extended Translucency"; } + virtual inline std::string GetShortName() override { return "ExtendedTranslucency"; } + virtual inline std::string_view GetShaderDefineName() override { return "EXTENDED_TRANSLUCENCY"; } + virtual bool HasShaderDefine(RE::BSShader::Type shaderType) override { return RE::BSShader::Type::Lighting == shaderType; }; + virtual void PostPostLoad() override; + virtual void DrawSettings() override; + virtual void LoadSettings(json& o_json) override; + virtual void SaveSettings(json& o_json) override; + virtual void RestoreDefaultSettings() override; + virtual bool SupportsVR() override { return true; }; + + // Future proof function for UI refactoring + std::string GetFeatureDescription() { return "Realistic rendering of thin fabric and other translucent materials"; } // Feature description for settings page + std::string GetFeatureModLink() { return "https://www.nexusmods.com/skyrimspecialedition/mods/150755"; } + + static void BSLightingShader_SetupGeometry(RE::BSRenderPass* pass); + + struct Hooks; + + // TODO: Support more material model like glasses or arcylic + enum MaterialModel : uint32_t + { + Disabled = 0, // In ExtraFeatureDescriptor, this value means 'Default' instead of 'Disabled' + RimLight = 1, // Similar effect like rim light + IsotropicFabric = 2, // 1D fabric model, respect normal map + AnisotropicFabric = 3, // 2D fabric model alone tangent and binormal, ignores normal map + ForceDisabled = 7, // In ExtraFeatureDescriptor, value >= 4 means 'Disabled' + }; + + static constexpr uint32_t ExtraFeatureDescriptorShift = 6; + static constexpr uint32_t ExtraFeatureDescriptorMask = 7; + + struct alignas(16) MaterialParams + { + uint32_t AlphaMode = MaterialModel::AnisotropicFabric; + float AlphaReduction = 0.15f; + float AlphaSoftness = 0.f; + float AlphaStrength = 0.f; + }; + + MaterialParams settings; + + static const RE::BSFixedString NiExtraDataName_AnisotropicAlphaMaterial; +}; diff --git a/src/Features/TerrainHelper.cpp b/src/Features/TerrainHelper.cpp index 413540a210..bbba5fa757 100644 --- a/src/Features/TerrainHelper.cpp +++ b/src/Features/TerrainHelper.cpp @@ -165,6 +165,7 @@ void TerrainHelper::BSLightingShader_SetupMaterial(RE::BSLightingShaderMaterialB const auto& stateData = globals::game::graphicsState->GetRuntimeData(); // Populate extended slots + // Please update bits allocation in ExtraFeatureDescriptor/Permutation.hlsli and other feature code if you need to change the constant 6 for (uint32_t textureI = 0; textureI < 6; ++textureI) { if (materialBase.parallax[textureI] != nullptr && materialBase.parallax[textureI] != stateData.defaultTextureNormalMap) { thExtendedRendererState.SetPSTexture(textureI, materialBase.parallax[textureI]->rendererTexture); diff --git a/src/Globals.cpp b/src/Globals.cpp index 9c93ebed4a..e0b135d5bf 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -14,6 +14,7 @@ #include "Features/CloudShadows.h" #include "Features/DynamicCubemaps.h" #include "Features/ExtendedMaterials.h" +#include "Features/ExtendedTranslucency.h" #include "Features/GrassCollision.h" #include "Features/GrassLighting.h" #include "Features/HairSpecular.h" @@ -75,6 +76,7 @@ namespace globals VR* vr = nullptr; WaterEffects* waterEffects = nullptr; WetnessEffects* wetnessEffects = nullptr; + ExtendedTranslucency* extendedTranslucency = nullptr; namespace llf { @@ -158,6 +160,7 @@ namespace globals features::vr = VR::GetSingleton(); features::waterEffects = WaterEffects::GetSingleton(); features::wetnessEffects = WetnessEffects::GetSingleton(); + features::extendedTranslucency = ExtendedTranslucency::GetSingleton(); features::llf::particleLights = ParticleLights::GetSingleton(); } diff --git a/src/Globals.h b/src/Globals.h index 2de8ec65f6..b6a397bd8f 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -24,6 +24,7 @@ struct VolumetricLighting; struct VR; struct WaterEffects; struct WetnessEffects; +struct ExtendedTranslucency; class ParticleLights; @@ -76,6 +77,7 @@ namespace globals extern VR* vr; extern WaterEffects* waterEffects; extern WetnessEffects* wetnessEffects; + extern ExtendedTranslucency* extendedTranslucency; namespace llf { diff --git a/src/State.h b/src/State.h index 9f8755e871..7dd3196947 100644 --- a/src/State.h +++ b/src/State.h @@ -143,7 +143,8 @@ class State THLand2HasDisplacement = 1 << 2, THLand3HasDisplacement = 1 << 3, THLand4HasDisplacement = 1 << 4, - THLand5HasDisplacement = 1 << 5 + THLand5HasDisplacement = 1 << 5, + ETMaterialModel = 0b111 << 6, }; void UpdateSharedData(bool a_inWorld, bool a_prepass);