-
Notifications
You must be signed in to change notification settings - Fork 49
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
base: dev
Are you sure you want to change the base?
Changes from all commits
0cd3393
6889943
ad9f06a
aec2ab1
8f44606
1fabbab
009860a
4d59069
1b3d92c
a4216fb
6bf60ad
1611a11
b34b3fd
64fa623
7d51a1d
ee5de21
151d0dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[Info] | ||
Version = 1-0-0 |
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: 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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I end up with a truly constant constant buffers solution: 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
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]; | ||
}; |
There was a problem hiding this comment.
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.