diff --git a/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli b/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli new file mode 100644 index 000000000..cf59f4a7d --- /dev/null +++ b/features/Extended Translucency/Shaders/ExtendedTranslucency/ExtendedTranslucency.hlsli @@ -0,0 +1,46 @@ +// ExtendedTranslucency::MaterialParams +cbuffer ExtendedTranslucencyPerGeometry : register(b7) +{ + uint AnisotropicAlphaFlags; // [0,1,2,3] The MaterialModel + float AnisotropicAlphaReduction; // [0, 1.0] The factor to reduce the transparency to matain the average transparency [0,1] + float AnisotropicAlphaSoftness; // [0, 2.0] The soft remap upper limit [0,2] + float AnisotropicAlphaStrength; // [0, 1.0] The inverse blend weight of the effect +}; + +namespace ExtendedTranslucency +{ + namespace MaterialModel + { + static const uint Disabled = 0; + static const uint RimLight = 1; + static const uint IsotropicFabric = 2; + static const uint AnisotropicFabric = 3; + } + + 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 000000000..000b60a56 --- /dev/null +++ b/features/Extended Translucency/Shaders/Features/ExtendedTranslucency.ini @@ -0,0 +1,2 @@ +[Info] +Version = 1-0-0 diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 404eccaac..0ac8e67ab 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -999,6 +999,14 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Skylighting/Skylighting.hlsli" # endif +# if defined(EXTENDED_TRANSLUCENCY) && !(defined(LOD) || defined(SKIN) || defined(HAIR) || defined(EYE) || defined(TREE_ANIM) || defined(LODOBJECTSHD) || defined(LODOBJECTS)) +# define ANISOTROPIC_ALPHA +# endif + +# if defined(ANISOTROPIC_ALPHA) +# include "ExtendedTranslucency/ExtendedTranslucency.hlsli" +# endif + # define LinearSampler SampColorSampler # include "Common/ShadowSampling.hlsli" @@ -2612,6 +2620,29 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace discard; } # endif // DO_ALPHA_TEST + +# if defined(ANISOTROPIC_ALPHA) + if (AnisotropicAlphaFlags > ExtendedTranslucency::MaterialModel::Disabled) { + if (alpha >= 0.0156862754 && alpha < 1.0) { + float originalAlpha = alpha; + alpha = alpha * (1.0 - AnisotropicAlphaReduction); + if (AnisotropicAlphaFlags == ExtendedTranslucency::MaterialModel::AnisotropicFabric) { +# if defined(SKINNED) || !defined(MODELSPACENORMALS) + alpha = ExtendedTransclucency::GetViewDependentAlphaFabric2D(alpha, viewDirection, tbnTr); +# else + alpha = ExtendedTranslucency::GetViewDependentAlphaFabric1D(alpha, viewDirection, modelNormal.xyz); +# endif + } else if (AnisotropicAlphaFlags == 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 - AnisotropicAlphaSoftness)); + alpha = lerp(alpha, originalAlpha, AnisotropicAlphaStrength); + } + } +# endif // EXTENDED_TRANSLUCENCY + psout.Diffuse.w = alpha; # endif diff --git a/src/Buffer.h b/src/Buffer.h index a3014c284..6229584e3 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -56,6 +56,17 @@ class ConstantBuffer DX::ThrowIfFailed(device->CreateBuffer(&desc, nullptr, resource.put())); } + // Create a constant buffer of FIXED data + template + explicit ConstantBuffer(const T& data) : + desc(ConstantBufferDesc(/*dynamic=*/false)) + { + static_assert(alignof(T) >= 16); + D3D11_SUBRESOURCE_DATA subresource{ .pSysMem = &data, .SysMemPitch = 0, .SysMemSlicePitch = 0 }; + auto device = reinterpret_cast(RE::BSGraphics::Renderer::GetSingleton()->GetRuntimeData().forwarder); + DX::ThrowIfFailed(device->CreateBuffer(&desc, &subresource, resource.put())); + } + ID3D11Buffer* CB() const { return resource.get(); } void Update(void const* src_data, size_t data_size) diff --git a/src/Feature.cpp b/src/Feature.cpp index f1f7ae0a9..8d651a092 100644 --- a/src/Feature.cpp +++ b/src/Feature.cpp @@ -4,6 +4,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/LightLimitFix.h" @@ -126,7 +127,8 @@ const std::vector& Feature::GetFeatureList() ScreenSpaceGI::GetSingleton(), Skylighting::GetSingleton(), TerrainBlending::GetSingleton(), - VolumetricLighting::GetSingleton() + VolumetricLighting::GetSingleton(), + ExtendedTranslucency::GetSingleton() }; static std::vector featuresVR(features); diff --git a/src/Features/ExtendedTranslucency.cpp b/src/Features/ExtendedTranslucency.cpp new file mode 100644 index 000000000..84c45ec3f --- /dev/null +++ b/src/Features/ExtendedTranslucency.cpp @@ -0,0 +1,156 @@ +#include "ExtendedTranslucency.h" + +#include "../State.h" +#include "../Util.h" + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + ExtendedTranslucency::MaterialParams, + AlphaMode, + AlphaReduction, + AlphaSoftness); + +const RE::BSFixedString ExtendedTranslucency::NiExtraDataName = "AnisotropicAlphaMaterial"; + +ExtendedTranslucency* ExtendedTranslucency::GetSingleton() +{ + static ExtendedTranslucency singleton; + return &singleton; +} + +void ExtendedTranslucency::SetupResources() +{ + // Per material model settings for geometries with explicit material model + MaterialParams params{ 0, 0.f, 0.f, 0 }; + for (int material = 0; material < MaterialModel::Max; material++) { + params.AlphaMode = material; + materialCB[material].emplace(params); + } + // Default material model buffer only changes in settings UI + materialDefaultCB.emplace(settings); +} + +void ExtendedTranslucency::BSLightingShader_SetupGeometry(RE::BSRenderPass* pass) +{ + auto* transcluency = ExtendedTranslucency::GetSingleton(); + static const REL::Relocation NiIntegerExtraDataRTTI{ RE::NiIntegerExtraData::Ni_RTTI }; + + // TODO: OPTIMIZATION: Use materialCB[MaterialModel::Disabled] for geometry without NiAlphaProperty or Alpha Blend not enabled + ID3D11DeviceContext* context = State::GetSingleton()->context; + ID3D11Buffer* buffers[1]; + if (auto* data = pass->geometry->GetExtraData(NiExtraDataName)) { + // Mods supporting this feature should adjust their alpha value in texture already + // And the texture should be adjusted based on full strength param + MaterialParams params = transcluency->settings; + if (data->GetRTTI() == NiIntegerExtraDataRTTI.get()) { + params.AlphaMode = std::clamp(static_cast(data)->value, 0, MaterialModel::Max - 1); + } else { + params.AlphaMode = std::to_underlying(ExtendedTranslucency::MaterialModel::Default); + } + + buffers[0] = materialCB[params.AlphaMode]->CB(); + context->PSSetConstantBuffers(materialCBIndex, 1, buffers); + } else { + buffers[0] = materialDefaultCB->CB(); + context->PSSetConstantBuffers(materialCBIndex, 1, buffers); + } +} + +struct ExtendedTranslucency::Hooks +{ + struct BSLightingShader_SetupGeometry + { + static void thunk(RE::BSShader* This, RE::BSRenderPass* Pass, uint32_t RenderFlags) + { + GetSingleton()->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"); + } +}; + +void ExtendedTranslucency::PostPostLoad() +{ + Hooks::Install(); +} + +void ExtendedTranslucency::DrawSettings() +{ + if (ImGui::TreeNodeEx("Anisotropic 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("Anisotropic transluency 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."); + } + + if (changed && materialDefaultCB) { + materialDefaultCB->Update(settings); + logger::info("[ExtendedTranslucency] Installed hooks"); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::TreePop(); + } +} + +void ExtendedTranslucency::LoadSettings(json& o_json) +{ + settings = o_json; + if (materialDefaultCB) { + materialDefaultCB->Update(settings); + } +} + +void ExtendedTranslucency::SaveSettings(json& o_json) +{ + o_json = settings; +} + +void ExtendedTranslucency::RestoreDefaultSettings() +{ + settings = {}; + if (materialDefaultCB) { + materialDefaultCB->Update(settings); + } +} diff --git a/src/Features/ExtendedTranslucency.h b/src/Features/ExtendedTranslucency.h new file mode 100644 index 000000000..a03cd4c3f --- /dev/null +++ b/src/Features/ExtendedTranslucency.h @@ -0,0 +1,51 @@ +#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 SetupResources() override; + 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; }; + + void BSLightingShader_SetupGeometry(RE::BSRenderPass* pass); + + struct Hooks; + + enum MaterialModel : uint32_t + { + Disabled = 0, + 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 + Max = 4, + Default = AnisotropicFabric + }; + + struct alignas(16) MaterialParams + { + uint32_t AlphaMode = std::to_underlying(MaterialModel::Default); + float AlphaReduction = 0.15f; + float AlphaSoftness = 0.f; + float AlphaStrength = 0.f; + }; + + MaterialParams settings; + + static const RE::BSFixedString NiExtraDataName; + static constexpr int materialCBIndex = 7; + + std::optional materialDefaultCB; + std::optional materialCB[MaterialModel::Max]; +};