Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Extended Translucency #678

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ExtendedTranslucency::MaterialParams
cbuffer ExtendedTranslucencyPerGeometry : register(b7)
Copy link
Owner

@doodlum doodlum Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should use PermutationCB because that updates anyway, during most calls. You can add flags at any time before the end of the pass. It's more maintainable for the project if constant buffers are not scattered all over the place.

{
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Info]
Version = 1-0-0
31 changes: 31 additions & 0 deletions package/Shaders/Lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/Buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ class ConstantBuffer
DX::ThrowIfFailed(device->CreateBuffer(&desc, nullptr, resource.put()));
}

// Create a constant buffer of FIXED data
template <typename T>
explicit ConstantBuffer(const T& data) :
desc(ConstantBufferDesc<T>(/*dynamic=*/false))
{
static_assert(alignof(T) >= 16);
D3D11_SUBRESOURCE_DATA subresource{ .pSysMem = &data, .SysMemPitch = 0, .SysMemSlicePitch = 0 };
auto device = reinterpret_cast<ID3D11Device*>(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)
Expand Down
4 changes: 3 additions & 1 deletion src/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -126,7 +127,8 @@ const std::vector<Feature*>& Feature::GetFeatureList()
ScreenSpaceGI::GetSingleton(),
Skylighting::GetSingleton(),
TerrainBlending::GetSingleton(),
VolumetricLighting::GetSingleton()
VolumetricLighting::GetSingleton(),
ExtendedTranslucency::GetSingleton()
};

static std::vector<Feature*> featuresVR(features);
Expand Down
156 changes: 156 additions & 0 deletions src/Features/ExtendedTranslucency.cpp
Original file line number Diff line number Diff line change
@@ -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<const RE::NiRTTI*> 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)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how good for perf it to look for ExtraData in each PerGeometry

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be fine compared to the cost of updating constant buffer:
The ExtraData is empty on most geometry, and for others with a small amount of extra data, it's a linear/binary lookup on an array of char*, the compare is pointer based instead of strcmp.

I tried to reuse some shader flags, like VertexAlpha, but found there are lot of random assets using them 😂

// 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()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we typically use netimmerse_cast. doubt it matters.

params.AlphaMode = std::clamp<int>(static_cast<RE::NiIntegerExtraData*>(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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use hook into Unmap in order to inject parameters into vanilla PerGeometry buffer
https://github.com/doodlum/skyrim-community-shaders/blob/dev/src/Hooks.cpp#L187

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I end up with a truly constant constant buffers solution:
We have one cbuffer for each material model, and it never needs runtime update: We just assign them into the shader slot. This should avoid most of the cost of synchronized GPU access ;)

BTW, based on the name of these cbuffer, I thought Skyrim is having one DX constant buffer per-geometry/material based on CRC, or we only have one cbuffer for each shader, and Map it every frame?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of spamming graphics commands, even if it doesn't cause a state switch.

} 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<decltype(thunk)> 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);
}
}
51 changes: 51 additions & 0 deletions src/Features/ExtendedTranslucency.h
Original file line number Diff line number Diff line change
@@ -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<ConstantBuffer> materialDefaultCB;
std::optional<ConstantBuffer> materialCB[MaterialModel::Max];
};
Loading