From 0dc9909516a42e41e8e069d5809bd4440d294ecf Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sat, 4 Jul 2026 22:54:10 -0700 Subject: [PATCH 1/4] feat: vanilla fresnel (upstream #2332) Squash of upstream PR community-shaders/skyrim-community-shaders#2332 (jiayev, head 506488a14) applied to origin/dev. Pure upstream content; the only local edits are trivial conflict resolutions in the feature registration lists (Feature.cpp include, FeatureBuffer.cpp pack order, Globals.cpp includes) against fork-only features. Adds the Vanilla Fresnel core feature: environmental reflections for vanilla and complex materials, optional GGX specular conversion and dynamic cubemap conversion. New always-on VANILLA_FRESNEL shader define invalidates the shader disk cache (full cold recompile). Co-Authored-By: Jiaye Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0183vd8arYzymdQi94eqpctT --- .../Shaders/Features/GrassLighting.ini | 2 +- .../Shaders/GrassLighting/GrassLighting.hlsli | 21 ++- features/Vanilla Fresnel/CORE | 0 .../Shaders/Features/VanillaFresnel.ini | 2 + package/Shaders/Common/LightingEval.hlsli | 30 +++++ package/Shaders/Common/Permutation.hlsli | 1 + package/Shaders/Common/SharedData.hlsli | 17 +++ package/Shaders/Lighting.hlsl | 95 ++++++++++++- package/Shaders/RunGrass.hlsl | 37 ++++- src/Feature.cpp | 2 + src/FeatureBuffer.cpp | 4 +- src/Features/VanillaFresnel.cpp | 127 ++++++++++++++++++ src/Features/VanillaFresnel.h | 48 +++++++ src/Globals.cpp | 2 + src/Globals.h | 2 + src/State.h | 3 +- 16 files changed, 374 insertions(+), 19 deletions(-) create mode 100644 features/Vanilla Fresnel/CORE create mode 100644 features/Vanilla Fresnel/Shaders/Features/VanillaFresnel.ini create mode 100644 src/Features/VanillaFresnel.cpp create mode 100644 src/Features/VanillaFresnel.h diff --git a/features/Grass Lighting/Shaders/Features/GrassLighting.ini b/features/Grass Lighting/Shaders/Features/GrassLighting.ini index 140421b6e6..5e77fe691c 100644 --- a/features/Grass Lighting/Shaders/Features/GrassLighting.ini +++ b/features/Grass Lighting/Shaders/Features/GrassLighting.ini @@ -1,2 +1,2 @@ [Info] -Version = 2-0-3 +Version = 2-1-0 diff --git a/features/Grass Lighting/Shaders/GrassLighting/GrassLighting.hlsli b/features/Grass Lighting/Shaders/GrassLighting/GrassLighting.hlsli index f85808a56d..cbcd8b71de 100644 --- a/features/Grass Lighting/Shaders/GrassLighting/GrassLighting.hlsli +++ b/features/Grass Lighting/Shaders/GrassLighting/GrassLighting.hlsli @@ -1,10 +1,27 @@ +#include "Common/BRDF.hlsli" +#include "Common/Color.hlsli" + namespace GrassLighting { - float3 GetLightSpecularInput(float3 L, float3 V, float3 N, float3 lightColor, float shininess) + float3 GetLightSpecularInput(float3 L, float3 V, float3 N, float3 lightColor, float roughness, float3 F0) { float3 H = normalize(V + L); - float HdotN = saturate(dot(H, N)); +#if defined(VANILLA_FRESNEL) + if (SharedData::vanillaFresnelSettings.Enable && SharedData::vanillaFresnelSettings.EnableGGXOnGrass) { + float NdotL = saturate(dot(N, L)); + float NdotV = saturate(dot(N, V)); + float NdotH = saturate(dot(N, H)); + float VdotH = saturate(dot(V, H)); + float D = BRDF::D_GGX(roughness, NdotH); + float G = BRDF::Vis_SmithJointApprox(roughness, NdotL, NdotV); + float3 F = BRDF::F_Schlick(F0, VdotH); + float3 specular = D * G * F; + return specular * lightColor * NdotL * Color::PBRLightingCompensation; + } +#endif + float shininess = (1.0 - roughness) * 100.f; + float HdotN = saturate(dot(H, N)); float lightColorMultiplier = exp2(shininess * log2(HdotN)); return lightColor * lightColorMultiplier.xxx; } diff --git a/features/Vanilla Fresnel/CORE b/features/Vanilla Fresnel/CORE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/features/Vanilla Fresnel/Shaders/Features/VanillaFresnel.ini b/features/Vanilla Fresnel/Shaders/Features/VanillaFresnel.ini new file mode 100644 index 0000000000..19f01444dc --- /dev/null +++ b/features/Vanilla Fresnel/Shaders/Features/VanillaFresnel.ini @@ -0,0 +1,2 @@ +[Info] +Version = 1-0-0 \ No newline at end of file diff --git a/package/Shaders/Common/LightingEval.hlsli b/package/Shaders/Common/LightingEval.hlsli index 21748a03ec..2cf7a857cf 100644 --- a/package/Shaders/Common/LightingEval.hlsli +++ b/package/Shaders/Common/LightingEval.hlsli @@ -95,6 +95,25 @@ float3 VanillaSpecular(DirectContext context, float shininess, float2 uv, float2 return lightColorMultiplier; } +float3 MicrofacetSpecular(DirectContext context, float3 F0, float roughness) +{ + const float3 N = context.worldNormal; + const float3 V = context.viewDir; + const float3 L = context.lightDir; + const float3 H = context.halfVector; + + float NdotL = clamp(dot(N, L), EPSILON_DOT_CLAMP, 1); + float NdotV = saturate(abs(dot(N, V)) + EPSILON_DOT_CLAMP); + float NdotH = saturate(dot(N, H)); + float VdotH = saturate(dot(V, H)); + + float D = BRDF::D_GGX(roughness, NdotH); + float G = BRDF::Vis_SmithJointApprox(roughness, NdotV, NdotL); + float3 F = BRDF::F_Schlick(F0, VdotH); + + return D * G * F * NdotL; +} + void EvaluateLighting(DirectContext context, MaterialProperties material, float3x3 tbnTr, float2 uv, float2 uv_ddx, float2 uv_ddy, out DirectLightingOutput lightingOutput) { lightingOutput = (DirectLightingOutput)0; @@ -145,6 +164,17 @@ void EvaluateLighting(DirectContext context, MaterialProperties material, float3 # if defined(BACK_LIGHTING) lightingOutput.diffuse += softLightColor * saturate(-NdotL) * material.backLightColor * Color::VanillaNormalization(); # endif + +# if defined(VANILLA_FRESNEL) + if (SharedData::vanillaFresnelSettings.Enable && SharedData::vanillaFresnelSettings.EnableGGX) { + lightingOutput.specular = diffuseLightColor * MicrofacetSpecular(context, material.F0, material.Roughness) * Color::PBRLightingCompensation * Color::PBRLightingScale; + + float2 specularBRDF = BRDF::EnvBRDF(material.Roughness, saturate(dot(context.worldNormal, context.viewDir))); + lightingOutput.specular *= 1 + material.F0 * (1 / (specularBRDF.x + specularBRDF.y) - 1); + return; + } +# endif + lightingOutput.specular = VanillaSpecular(context, material.Shininess, uv, uv_ddx, uv_ddy) * material.SpecularColor * material.Glossiness * diffuseLightColor * Color::VanillaNormalization(); #endif } diff --git a/package/Shaders/Common/Permutation.hlsli b/package/Shaders/Common/Permutation.hlsli index d3e2005526..41054c5a86 100644 --- a/package/Shaders/Common/Permutation.hlsli +++ b/package/Shaders/Common/Permutation.hlsli @@ -75,6 +75,7 @@ namespace Permutation static const uint GrassSphereNormal = (1 << 3); static const uint IsSun = (1 << 4); static const uint SuppressExternalEmittance = (1 << 5); + static const uint IsEye = (1 << 6); } namespace ExtraFeatureFlags diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 1fe669386a..f1895d3cae 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -334,6 +334,22 @@ namespace SharedData float4 wetParams; }; + struct VanillaFresnelSettings + { + uint Enable; + uint EnableGGX; + uint EnableGGXOnGrass; + uint EnableDynamicCubemapsConversion; + uint EnableEyeSpecialHandling; + float RoughnessMultiplier; + float SpecularRoughnessBlend; + float BaseF0Multiplier; + float MinF0; + float CubemapToF0Multiplier; + float ComplexMaterialF0Multiplier; + float pad; + }; + cbuffer FeatureData : register(b6) { GrassLightingSettings grassLightingSettings; @@ -355,6 +371,7 @@ namespace SharedData ExponentialHeightFogSettings exponentialHeightFogSettings; TruePBRSettings truePBRSettings; SkinData skinData; + VanillaFresnelSettings vanillaFresnelSettings; }; Texture2D DepthTexture : register(t17); diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index f074361c98..fef025b8f1 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1943,6 +1943,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) shininess = lerp(shininess, shininess * complexMaterialColor.y, complexMaterial); if (complexMaterial) { complexSpecular = lerp(1.0, baseColor.xyz, complexMaterialColor.z); +# if defined(VANILLA_FRESNEL) + complexSpecular = saturate(complexSpecular * (SharedData::vanillaFresnelSettings.Enable ? SharedData::vanillaFresnelSettings.ComplexMaterialF0Multiplier : 1.0)); +# endif baseColor.xyz = lerp(baseColor.xyz, 0.0, complexMaterialColor.z); } # endif // defined (EMAT) && defined(ENVMAP) @@ -2342,7 +2345,27 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(BACK_LIGHTING) material.backLightColor = backLightColor.xyz; # endif -# endif // TRUE_PBR + +# if defined(VANILLA_FRESNEL) + const bool enableVanillaFresnel = SharedData::vanillaFresnelSettings.Enable; + const bool isEyeMaterial = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::IsEye) != 0; + material.F0 = enableVanillaFresnel ? SharedData::vanillaFresnelSettings.MinF0 : 0.0; +# if defined(SPECULAR) + if (enableVanillaFresnel && !isEyeMaterial) { + material.F0 = saturate(glossiness * SpecularColor.xyz / Math::PI); + float roughnessFromSpecular = (1.0 - glossiness) * (1.0 - glossiness); + float roughnessFromShininess = ShininessToRoughness(material.Shininess); + material.Roughness = lerp(roughnessFromShininess, roughnessFromSpecular, SharedData::vanillaFresnelSettings.SpecularRoughnessBlend * (1.0 - glossiness)); + } +# endif + if (enableVanillaFresnel && isEyeMaterial && SharedData::vanillaFresnelSettings.EnableEyeSpecialHandling) { + material.F0 = 0.027; + material.Roughness = 0.1; + } + material.F0 = max((enableVanillaFresnel ? SharedData::vanillaFresnelSettings.MinF0 : 0.0), material.F0 * SharedData::vanillaFresnelSettings.BaseF0Multiplier); + const float3 baseF0 = material.F0; +# endif // VANILLA_FRESNEL +# endif // TRUE_PBR # if defined(SKIN) && defined(CS_SKIN) const float ExtraRoughness = BRDF::F_Schlick(0.04, saturate(dot(worldNormal.xyz, viewDirection))) * SharedData::skinData.fuzzParams.w; @@ -2411,10 +2434,16 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) uint2 envSize; TexEnvSampler.GetDimensions(envSize.x, envSize.y); -# if defined(EMAT) +# if defined(EMAT) && !defined(VANILLA_FRESNEL) if (envSize.x == 1 && envSize.y == 1 || complexMaterial) { +# elif defined(VANILLA_FRESNEL) + bool vfStartDynamicCubemapTest = (enableVanillaFresnel && SharedData::vanillaFresnelSettings.EnableDynamicCubemapsConversion); +# if defined(EMAT) + vfStartDynamicCubemapTest = complexMaterial || vfStartDynamicCubemapTest; +# endif + if (vfStartDynamicCubemapTest || (envSize.x == 1 && envSize.y == 1)) { # else - if (envSize.x == 1 && envSize.y == 1) { + if (envSize.x == 1 && envSize.y == 1) { # endif dynamicCubemap = true; @@ -2434,15 +2463,26 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } # endif +# if defined(VANILLA_FRESNEL) + dynamicCubemap = dynamicCubemap || (SharedData::vanillaFresnelSettings.EnableDynamicCubemapsConversion && enableVanillaFresnel); + const float originRoughness = material.Roughness; +# endif + if (dynamicCubemap) { float4 envColorBase = TexEnvSampler.SampleLevel(SampEnvSampler, float3(1.0, 0.0, 0.0), 15); + bool usingDynamicCubemap = envColorBase.a < 1.0; - if (envColorBase.a < 1.0) { + if (usingDynamicCubemap) { material.F0 = Color::SkyrimGammaToLinear(envColorBase.rgb); material.Roughness = envColorBase.a; } else { +# if defined(VANILLA_FRESNEL) + material.F0 = saturate(Color::SkyrimGammaToLinear(envColorBase.rgb) * Math::PI * SharedData::vanillaFresnelSettings.CubemapToF0Multiplier); + material.Roughness = material.Roughness; +# else material.F0 = 1.0; material.Roughness = 1.0 / 7.0; +# endif } # if defined(CREATOR) @@ -2457,10 +2497,42 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) material.Roughness = lerp(material.Roughness, complexMaterialRoughness, complexMaterial); material.F0 = lerp(material.F0, complexSpecular, complexMaterial); # endif + + envMask = saturate(envMask); +# if defined(VANILLA_FRESNEL) + if (enableVanillaFresnel) { +# if defined(SPECULAR) + material.F0 = max(lerp(baseF0, material.F0, envMask), SharedData::vanillaFresnelSettings.MinF0); +# else + material.F0 = max(lerp(0, material.F0, envMask), SharedData::vanillaFresnelSettings.MinF0); +# endif +# if defined(EMAT) + if (!complexMaterial) { +# endif + material.Roughness = lerp(originRoughness, material.Roughness, envMask); + if (!isEyeMaterial && !usingDynamicCubemap) { + material.F0 = saturate(material.F0 + envMask * material.BaseColor); + material.BaseColor = lerp(material.BaseColor, 0, envMask); + } +# if defined(EMAT) + } +# endif + } +# endif } } # endif +# if defined(VANILLA_FRESNEL) && !defined(TRUE_PBR) + if (enableVanillaFresnel) { + if (isEyeMaterial && SharedData::vanillaFresnelSettings.EnableEyeSpecialHandling) { + material.F0 = 0.027; + material.Roughness = 0.1; + } + material.Roughness = clamp(material.Roughness * SharedData::vanillaFresnelSettings.RoughnessMultiplier, 0.04, 1.0); + } +# endif + if (!dynamicCubemap) { float3 envColorBase = Color::SkyrimGammaToLinear(TexEnvSampler.Sample(SampEnvSampler, envSamplingPoint).xyz); envColor = envColorBase.xyz * envMask; @@ -3226,7 +3298,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # endif # if defined(ENVMAP) || defined(MULTI_LAYER_PARALLAX) || defined(EYE) - indirectLobeWeights.specular *= envMask; +# if defined(VANILLA_FRESNEL) + if (!enableVanillaFresnel) +# endif + indirectLobeWeights.specular *= envMask; # endif # if defined(SPECULAR) && !defined(TRUE_PBR) @@ -3291,12 +3366,18 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(DYNAMIC_CUBEMAPS) if (!dynamicCubemap) # endif - specularColor += envColor * Color::IrradianceToLinear(diffuseColor); +# if defined(VANILLA_FRESNEL) + if (!(enableVanillaFresnel && SharedData::vanillaFresnelSettings.EnableDynamicCubemapsConversion)) +# endif + specularColor += envColor * Color::IrradianceToLinear(diffuseColor); indirectLobeWeights.diffuse += envColor; # endif # if defined(EMAT_ENVMAP) - specularColor *= complexSpecular; +# if defined(VANILLA_FRESNEL) + if (!(enableVanillaFresnel && SharedData::vanillaFresnelSettings.EnableGGX)) +# endif + specularColor *= complexSpecular; # endif // defined (EMAT) && defined(ENVMAP) # if defined(LOD_LAND_BLEND) && defined(TRUE_PBR) diff --git a/package/Shaders/RunGrass.hlsl b/package/Shaders/RunGrass.hlsl index df0b98558a..9fbb76b088 100644 --- a/package/Shaders/RunGrass.hlsl +++ b/package/Shaders/RunGrass.hlsl @@ -354,12 +354,10 @@ struct PS_OUTPUT float4 NormalGlossiness: SV_Target2; float4 Albedo: SV_Target3; float4 Specular: SV_Target4; -# if defined(TRUE_PBR) float4 Reflectance: SV_Target5; -# endif // TRUE_PBR float4 Masks: SV_Target6; float4 Masks2: SV_Target7; -# endif // RENDER_DEPTH +# endif // RENDER_DEPTH }; #else struct PS_OUTPUT @@ -558,6 +556,14 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) baseColor.xyz *= SharedData::grassLightingSettings.BasicGrassBrightness; # endif // !TRUE_PBR +# if defined(VANILLA_FRESNEL) + const bool enableVanillaFresnel = SharedData::vanillaFresnelSettings.Enable; + float3 F0 = enableVanillaFresnel ? max(SharedData::vanillaFresnelSettings.MinF0, saturate(specColor.w * SharedData::grassLightingSettings.SpecularStrength * SharedData::vanillaFresnelSettings.BaseF0Multiplier / Math::PI)) : 0.0; +# else + float3 F0 = 0.0; +# endif + float roughness = saturate(1.0 - SharedData::grassLightingSettings.Glossiness * 0.01); + # if defined(TRUE_PBR) float4 rawRMAOS = TexRMAOSSampler.SampleBias(SampRMAOSSampler, input.TexCoord.xy, SharedData::MipBias) * float4(PBRParams1.x, 1, 1, PBRParams1.y); @@ -657,7 +663,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 subsurfaceColor = dirLightColor * dirDetailedShadow * (GetSoftLightMultiplier(dirLightAngle, softLightRolloff)) * Color::VanillaNormalization(); if (complex) - lightsSpecularColor += dirDetailedShadow * GrassLighting::GetLightSpecularInput(SharedData::DirLightDirection.xyz, viewDirection, normal, dirLightColor, SharedData::grassLightingSettings.Glossiness) * Color::VanillaNormalization(); + lightsSpecularColor += dirDetailedShadow * GrassLighting::GetLightSpecularInput(SharedData::DirLightDirection.xyz, viewDirection, normal, dirLightColor, roughness, F0) * Color::VanillaNormalization(); # endif # if defined(LIGHT_LIMIT_FIX) @@ -733,7 +739,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) lightsDiffuseColor += lightDiffuseColor * Color::VanillaNormalization(); if (complex) - lightsSpecularColor += GrassLighting::GetLightSpecularInput(normalizedLightDirection, viewDirection, normal, lightColor, SharedData::grassLightingSettings.Glossiness) * Color::VanillaNormalization(); + lightsSpecularColor += GrassLighting::GetLightSpecularInput(normalizedLightDirection, viewDirection, normal, lightColor, roughness, F0) * Color::VanillaNormalization(); # endif } } @@ -780,7 +786,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif specularColor += lightsSpecularColor; - specularColor *= specColor.w * SharedData::grassLightingSettings.SpecularStrength; +# if defined(VANILLA_FRESNEL) + if (!(SharedData::vanillaFresnelSettings.Enable && SharedData::vanillaFresnelSettings.EnableGGXOnGrass)) +# endif + specularColor *= specColor.w * SharedData::grassLightingSettings.SpecularStrength; # endif # if defined(LIGHT_LIMIT_FIX) && defined(LLFDEBUG) @@ -805,8 +814,22 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) psout.NormalGlossiness = float4(GBuffer::EncodeNormal(normalVS), 1 - pbrSurfaceProperties.Roughness, 1); psout.Reflectance = float4(indirectSpecularLobeWeight, 1); # else + + float3 reflectance = 0; +# if defined(DYNAMIC_CUBEMAPS) && (defined(VANILLA_FRESNEL) || defined(TRUE_PBR)) +# if defined(VANILLA_FRESNEL) + if (SharedData::vanillaFresnelSettings.Enable) { +# endif + float2 specularBDRF = BRDF::EnvBRDF(roughness, saturate(dot(viewDirection, normal))); + reflectance = F0 * specularBDRF.x + specularBDRF.y; +# if defined(VANILLA_FRESNEL) + } +# endif +# endif + + psout.Reflectance = float4(reflectance, 1); psout.Albedo = float4(albedo, 1); - psout.NormalGlossiness = float4(GBuffer::EncodeNormal(normalVS), specColor.w, 1); + psout.NormalGlossiness = float4(GBuffer::EncodeNormal(normalVS), 1.0 - roughness, 1); # endif psout.Specular = float4(specularColor, 1); diff --git a/src/Feature.cpp b/src/Feature.cpp index 7f2c43c41d..612364c9e1 100644 --- a/src/Feature.cpp +++ b/src/Feature.cpp @@ -36,6 +36,7 @@ #include "Features/UnifiedWater.h" #include "Features/Upscaling.h" #include "Features/VR.h" +#include "Features/VanillaFresnel.h" #include "Features/VolumetricLighting.h" #include "Features/VolumetricShadows.h" #include "Features/WaterEffects.h" @@ -238,6 +239,7 @@ const std::vector& Feature::GetFeatureList() &globals::features::skySync, &globals::features::terrainBlending, &globals::features::terrainHelper, + &globals::features::vanillaFresnel, &globals::features::volumetricLighting, &globals::features::lodBlending, &globals::features::inverseSquareLighting, diff --git a/src/FeatureBuffer.cpp b/src/FeatureBuffer.cpp index fe4839f5b9..1559ea6256 100644 --- a/src/FeatureBuffer.cpp +++ b/src/FeatureBuffer.cpp @@ -19,6 +19,7 @@ #include "Features/TerrainBlending.h" #include "Features/TerrainShadows.h" #include "Features/TerrainVariation.h" +#include "Features/VanillaFresnel.h" #include "Features/WetnessEffects.h" #include "TruePBR.h" @@ -62,5 +63,6 @@ std::pair GetFeatureBufferData(bool a_inWorld) globals::features::terrainBlending.settings, globals::features::exponentialHeightFog.settings, globals::features::truePBR.settings, - globals::features::skin.GetCommonBufferData()); + globals::features::skin.GetCommonBufferData(), + globals::features::vanillaFresnel.settings); } diff --git a/src/Features/VanillaFresnel.cpp b/src/Features/VanillaFresnel.cpp new file mode 100644 index 0000000000..0a1cb3dda7 --- /dev/null +++ b/src/Features/VanillaFresnel.cpp @@ -0,0 +1,127 @@ +#include "VanillaFresnel.h" + +#include "Globals.h" +#include "ShaderCache.h" +#include "State.h" + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + VanillaFresnel::Settings, + Enable, + EnableGGX, + EnableGGXOnGrass, + EnableDynamicCubemapsConversion, + EnableEyeSpecialHandling, + RoughnessMultiplier, + SpecularRoughnessBlend, + BaseF0Multiplier, + MinF0, + CubemapToF0Multiplier, + ComplexMaterialF0Multiplier) + +namespace +{ + constexpr auto IsEyeDescriptor = static_cast(State::ExtraShaderDescriptors::IsEye); + + bool ContainsEye(std::string_view a_name) + { + constexpr std::string_view eye = "eye"; + return std::search( + a_name.begin(), a_name.end(), + eye.begin(), eye.end(), + [](char a_lhs, char a_rhs) { + return static_cast(std::tolower(static_cast(a_lhs))) == a_rhs; + }) != a_name.end(); + } + + bool IsEyePass(const RE::BSLightingShader* a_shader, const RE::BSRenderPass* a_pass) + { + if (a_shader) { + const auto technique = static_cast(0x3F & (a_shader->currentRawTechnique >> 24)); + if (technique == SIE::ShaderCache::LightingShaderTechniques::Eye) { + return true; + } + } + + if (!a_pass || !a_pass->shaderProperty) { + return false; + } + + const auto* lightingProperty = a_pass->shaderProperty->GetRTTI() == globals::rtti::BSLightingShaderPropertyRTTI.get() ? + static_cast(a_pass->shaderProperty) : + nullptr; + if (!lightingProperty || !lightingProperty->material) { + return false; + } + + const auto feature = lightingProperty->material->GetFeature(); + if (feature == RE::BSShaderMaterial::Feature::kEye) { + return true; + } + + return feature == RE::BSShaderMaterial::Feature::kEnvironmentMap && + a_pass->geometry && + ContainsEye(a_pass->geometry->name.c_str()); + } + + void UpdateEyePermutation(const RE::BSLightingShader* a_shader, const RE::BSRenderPass* a_pass) + { + auto& descriptor = globals::state->permutationData.ExtraShaderDescriptor; + descriptor &= ~IsEyeDescriptor; + + if (IsEyePass(a_shader, a_pass)) { + descriptor |= IsEyeDescriptor; + } + } + + struct BSLightingShader_SetupGeometry + { + static void thunk(RE::BSLightingShader* a_shader, RE::BSRenderPass* a_pass, uint32_t a_renderFlags) + { + UpdateEyePermutation(a_shader, a_pass); + func(a_shader, a_pass, a_renderFlags); + } + static inline REL::Relocation func; + }; +} + +void VanillaFresnel::PostPostLoad() +{ + stl::write_vfunc<0x6, BSLightingShader_SetupGeometry>(RE::VTABLE_BSLightingShader[0]); + logger::info("[VanillaFresnel] Installed hooks - BSLightingShader_SetupGeometry"); +} + +void VanillaFresnel::RestoreDefaultSettings() +{ + settings = {}; +} + +void VanillaFresnel::LoadSettings(json& o_json) +{ + settings = o_json; +} + +void VanillaFresnel::SaveSettings(json& o_json) +{ + o_json = settings; +} + +void VanillaFresnel::DrawSettings() +{ + ImGui::Checkbox("Enable Vanilla Fresnel", reinterpret_cast(&settings.Enable)); + ImGui::Checkbox("Enable Phong to GGX", reinterpret_cast(&settings.EnableGGX)); + ImGui::Checkbox("Enable Phong to GGX on Grass", reinterpret_cast(&settings.EnableGGXOnGrass)); + if (!settings.EnableGGX) { + settings.EnableDynamicCubemapsConversion = false; + } + ImGui::BeginDisabled(!settings.EnableGGX); + ImGui::Checkbox("Enable Auto Cubemaps Conversion", reinterpret_cast(&settings.EnableDynamicCubemapsConversion)); + ImGui::EndDisabled(); + ImGui::Checkbox("Enable Eye Special Handling", reinterpret_cast(&settings.EnableEyeSpecialHandling)); + + ImGui::SliderFloat("Roughness Multiplier", &settings.RoughnessMultiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat("Specular Roughness Blend", &settings.SpecularRoughnessBlend, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("Base F0 Multiplier", &settings.BaseF0Multiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat("Min F0", &settings.MinF0, 0.0f, 0.04f, "%.3f"); + ImGui::SliderFloat("Cubemap to F0 Multiplier", &settings.CubemapToF0Multiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat("Complex Material Env F0 Multiplier", &settings.ComplexMaterialF0Multiplier, 0.0f, 10.0f, "%.2f"); +} diff --git a/src/Features/VanillaFresnel.h b/src/Features/VanillaFresnel.h new file mode 100644 index 0000000000..733cd9dcfc --- /dev/null +++ b/src/Features/VanillaFresnel.h @@ -0,0 +1,48 @@ +#pragma once + +struct VanillaFresnel : public Feature +{ + ////////////////////////////////////////////////// Boilerplate + // Metadata + virtual inline std::string GetName() override { return "Vanilla Fresnel"; } + virtual inline std::string GetShortName() override { return "VanillaFresnel"; } + virtual inline std::string_view GetCategory() const override { return "Lighting"; } + virtual inline std::pair> GetFeatureSummary() override + { + return { + "Add realistic environmental reflections to vanilla materials.", + { "Add environmental reflections to all materials", + "Supports vanilla and complex materials", + "Optionally turn vanilla phong specular into GGX", + "Optionally turn static cubemaps into dynamic reflections" } + }; + } + + // Functionality + virtual bool inline IsCore() const override { return true; } + virtual inline std::string_view GetShaderDefineName() override { return "VANILLA_FRESNEL"; } + virtual inline bool HasShaderDefine(RE::BSShader::Type) override { return true; }; + virtual void PostPostLoad() override; + + // Settings & UI + virtual void RestoreDefaultSettings() override; + virtual void LoadSettings(json& o_json) override; + virtual void SaveSettings(json& o_json) override; + virtual void DrawSettings() override; + + struct alignas(16) Settings + { + uint Enable = true; + uint EnableGGX = false; + uint EnableGGXOnGrass = false; + uint EnableDynamicCubemapsConversion = false; + uint EnableEyeSpecialHandling = true; + float RoughnessMultiplier = 1.0f; + float SpecularRoughnessBlend = 1.0f; + float BaseF0Multiplier = 0.32f; + float MinF0 = 0.02f; + float CubemapToF0Multiplier = 1.0f; + float ComplexMaterialF0Multiplier = 1.0f; + float pad; + } settings; +}; diff --git a/src/Globals.cpp b/src/Globals.cpp index 850727b412..83db9259db 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -35,6 +35,7 @@ #include "Features/UnifiedWater.h" #include "Features/Upscaling.h" #include "Features/VR.h" +#include "Features/VanillaFresnel.h" #include "Features/VolumetricLighting.h" #include "Features/VolumetricShadows.h" #include "Features/WaterEffects.h" @@ -79,6 +80,7 @@ namespace globals TerrainHelper terrainHelper{}; TerrainShadows terrainShadows{}; UnifiedWater unifiedWater{}; + VanillaFresnel vanillaFresnel{}; VolumetricLighting volumetricLighting{}; VR vr{}; WaterEffects waterEffects{}; diff --git a/src/Globals.h b/src/Globals.h index 3ec03fe4bf..2898737a9f 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -25,6 +25,7 @@ struct TerrainBlending; struct TerrainHelper; struct TerrainShadows; struct UnifiedWater; +struct VanillaFresnel; struct VolumetricLighting; struct VR; struct WaterEffects; @@ -112,6 +113,7 @@ namespace globals extern TerrainHelper terrainHelper; extern TerrainShadows terrainShadows; extern UnifiedWater unifiedWater; + extern VanillaFresnel vanillaFresnel; extern VolumetricLighting volumetricLighting; extern VR vr; extern WaterEffects waterEffects; diff --git a/src/State.h b/src/State.h index 616306eb00..07e2f541c7 100644 --- a/src/State.h +++ b/src/State.h @@ -277,7 +277,8 @@ class State IsBeastRace = 1 << 2, GrassSphereNormal = 1 << 3, IsSun = 1 << 4, - SuppressExternalEmittance = 1 << 5 + SuppressExternalEmittance = 1 << 5, + IsEye = 1 << 6 }; enum class ExtraFeatureDescriptors : uint32_t From 2039b35e3f039b8079e29f3066ee7a8e202a39de Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sat, 4 Jul 2026 22:55:49 -0700 Subject: [PATCH 2/4] feat(vr): enable Vanilla Fresnel in VR Fork adaptation on top of the upstream #2332 squash. All feature shader terms derive from the per-eye viewDirection/context.viewDir, the IsEye descriptor uses the existing permutationData path shared with VR, and the SetupGeometry hook is a vtable write with no address-library dependency, so no VR divergence is required beyond the SupportsVR flag. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0183vd8arYzymdQi94eqpctT --- src/Features/VanillaFresnel.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/VanillaFresnel.h b/src/Features/VanillaFresnel.h index 733cd9dcfc..7fb2c0f367 100644 --- a/src/Features/VanillaFresnel.h +++ b/src/Features/VanillaFresnel.h @@ -22,6 +22,8 @@ struct VanillaFresnel : public Feature virtual bool inline IsCore() const override { return true; } virtual inline std::string_view GetShaderDefineName() override { return "VANILLA_FRESNEL"; } virtual inline bool HasShaderDefine(RE::BSShader::Type) override { return true; }; + // fork: shader math is per-eye and the hook is a shared vtable write, so VR needs no divergence + virtual bool SupportsVR() override { return true; } virtual void PostPostLoad() override; // Settings & UI From 43b7d93a9f92ce7e854d187f67a6f746423a6a45 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 5 Jul 2026 10:36:40 -0700 Subject: [PATCH 3/4] fix(vanilla-fresnel): apply fork i18n/buffer-safety standards Copilot review on #243: the upstream squash's new feature file hardcoded English UI strings and a raw category literal instead of this fork's T()/FeatureCategories conventions (upstream has no i18n system, so it never could have used them), and the packed Settings struct lacked the STATIC_ASSERT_ALIGNAS_16 check every other b6-buffer feature carries. Fixed to match the exact pattern in ExtendedMaterials/DynamicCubemaps. Two other Copilot findings were checked and NOT changed: - GrassLighting.hlsli's Vis_SmithJointApprox(roughness, NdotL, NdotV) looks argument-swapped against the function's (roughness, NdotV, NdotL) signature, but the approximation's Vis_SmithV+Vis_SmithL sum is symmetric in the two angles by construction; verified numerically that output is bit-identical either order. No bug. - DrawSettings() force-disables EnableDynamicCubemapsConversion when EnableGGX is off. This looked like an artificial UI restriction (the shader's indirect-specular path consumes the cubemap-derived F0/Roughness independent of EnableGGX), but jiayev's upstream history has a standalone commit titled "forbid cubemap conversion when not ggx" (498fa7d8a) deliberately adding this exact coupling. Confirmed intentional; left as upstream authored it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WFzp29R2ZAM2Xa93jk8TG7 --- src/Features/VanillaFresnel.cpp | 27 +++++++++++++++------------ src/Features/VanillaFresnel.h | 22 ++++++++++++++-------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Features/VanillaFresnel.cpp b/src/Features/VanillaFresnel.cpp index 0a1cb3dda7..3bd2c98f64 100644 --- a/src/Features/VanillaFresnel.cpp +++ b/src/Features/VanillaFresnel.cpp @@ -1,9 +1,12 @@ #include "VanillaFresnel.h" +#include "../I18n/I18n.h" #include "Globals.h" #include "ShaderCache.h" #include "State.h" +#define I18N_KEY_PREFIX "feature.vanilla_fresnel." + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( VanillaFresnel::Settings, Enable, @@ -107,21 +110,21 @@ void VanillaFresnel::SaveSettings(json& o_json) void VanillaFresnel::DrawSettings() { - ImGui::Checkbox("Enable Vanilla Fresnel", reinterpret_cast(&settings.Enable)); - ImGui::Checkbox("Enable Phong to GGX", reinterpret_cast(&settings.EnableGGX)); - ImGui::Checkbox("Enable Phong to GGX on Grass", reinterpret_cast(&settings.EnableGGXOnGrass)); + ImGui::Checkbox(T(TKEY("enable"), "Enable Vanilla Fresnel"), reinterpret_cast(&settings.Enable)); + ImGui::Checkbox(T(TKEY("enable_ggx"), "Enable Phong to GGX"), reinterpret_cast(&settings.EnableGGX)); + ImGui::Checkbox(T(TKEY("enable_ggx_on_grass"), "Enable Phong to GGX on Grass"), reinterpret_cast(&settings.EnableGGXOnGrass)); if (!settings.EnableGGX) { settings.EnableDynamicCubemapsConversion = false; } ImGui::BeginDisabled(!settings.EnableGGX); - ImGui::Checkbox("Enable Auto Cubemaps Conversion", reinterpret_cast(&settings.EnableDynamicCubemapsConversion)); + ImGui::Checkbox(T(TKEY("enable_dynamic_cubemaps_conversion"), "Enable Auto Cubemaps Conversion"), reinterpret_cast(&settings.EnableDynamicCubemapsConversion)); ImGui::EndDisabled(); - ImGui::Checkbox("Enable Eye Special Handling", reinterpret_cast(&settings.EnableEyeSpecialHandling)); - - ImGui::SliderFloat("Roughness Multiplier", &settings.RoughnessMultiplier, 0.0f, 10.0f, "%.2f"); - ImGui::SliderFloat("Specular Roughness Blend", &settings.SpecularRoughnessBlend, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("Base F0 Multiplier", &settings.BaseF0Multiplier, 0.0f, 10.0f, "%.2f"); - ImGui::SliderFloat("Min F0", &settings.MinF0, 0.0f, 0.04f, "%.3f"); - ImGui::SliderFloat("Cubemap to F0 Multiplier", &settings.CubemapToF0Multiplier, 0.0f, 10.0f, "%.2f"); - ImGui::SliderFloat("Complex Material Env F0 Multiplier", &settings.ComplexMaterialF0Multiplier, 0.0f, 10.0f, "%.2f"); + ImGui::Checkbox(T(TKEY("enable_eye_special_handling"), "Enable Eye Special Handling"), reinterpret_cast(&settings.EnableEyeSpecialHandling)); + + ImGui::SliderFloat(T(TKEY("roughness_multiplier"), "Roughness Multiplier"), &settings.RoughnessMultiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("specular_roughness_blend"), "Specular Roughness Blend"), &settings.SpecularRoughnessBlend, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("base_f0_multiplier"), "Base F0 Multiplier"), &settings.BaseF0Multiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("min_f0"), "Min F0"), &settings.MinF0, 0.0f, 0.04f, "%.3f"); + ImGui::SliderFloat(T(TKEY("cubemap_to_f0_multiplier"), "Cubemap to F0 Multiplier"), &settings.CubemapToF0Multiplier, 0.0f, 10.0f, "%.2f"); + ImGui::SliderFloat(T(TKEY("complex_material_f0_multiplier"), "Complex Material Env F0 Multiplier"), &settings.ComplexMaterialF0Multiplier, 0.0f, 10.0f, "%.2f"); } diff --git a/src/Features/VanillaFresnel.h b/src/Features/VanillaFresnel.h index 7fb2c0f367..4a418da8b6 100644 --- a/src/Features/VanillaFresnel.h +++ b/src/Features/VanillaFresnel.h @@ -1,20 +1,23 @@ #pragma once +#include "Buffer.h" + struct VanillaFresnel : public Feature { ////////////////////////////////////////////////// Boilerplate // Metadata virtual inline std::string GetName() override { return "Vanilla Fresnel"; } + virtual std::string GetDisplayName() override { return T("feature.vanilla_fresnel.name", "Vanilla Fresnel"); } virtual inline std::string GetShortName() override { return "VanillaFresnel"; } - virtual inline std::string_view GetCategory() const override { return "Lighting"; } - virtual inline std::pair> GetFeatureSummary() override + virtual std::string_view GetCategory() const override { return FeatureCategories::kLighting; } + virtual std::pair> GetFeatureSummary() override { return { - "Add realistic environmental reflections to vanilla materials.", - { "Add environmental reflections to all materials", - "Supports vanilla and complex materials", - "Optionally turn vanilla phong specular into GGX", - "Optionally turn static cubemaps into dynamic reflections" } + T("feature.vanilla_fresnel.description", "Add realistic environmental reflections to vanilla materials."), + { T("feature.vanilla_fresnel.key_feature_1", "Add environmental reflections to all materials"), + T("feature.vanilla_fresnel.key_feature_2", "Supports vanilla and complex materials"), + T("feature.vanilla_fresnel.key_feature_3", "Optionally turn vanilla phong specular into GGX"), + T("feature.vanilla_fresnel.key_feature_4", "Optionally turn static cubemaps into dynamic reflections") } }; } @@ -46,5 +49,8 @@ struct VanillaFresnel : public Feature float CubemapToF0Multiplier = 1.0f; float ComplexMaterialF0Multiplier = 1.0f; float pad; - } settings; + }; + STATIC_ASSERT_ALIGNAS_16(Settings); + + Settings settings; }; From a66f70879170737ed5e36512dd8c416f4ba86e1d Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 5 Jul 2026 11:33:41 -0700 Subject: [PATCH 4/4] build(i18n): regenerate en.json for VanillaFresnel T() keys CI check "Verify en.json is in sync with source" failed after the fork-standards commit added T() calls to VanillaFresnel.h/.cpp; en.json is generated (tools/extract-i18n.py --write) and must never be hand-edited. Regenerated, adding only the 17 new feature.vanilla_fresnel.* keys. --- .../CommunityShaders/Translations/en.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index dd62e7b9bc..a4cfc7d781 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -2002,6 +2002,23 @@ "feature.upscaling.vr_intermediates_not_created": "VR intermediates not yet created (enter game world)", "feature.upscaling.vr_perf_upscale_preset": "Upscale preset", "feature.upscaling.vr_perf_upscaling_header": "Upscaling & Foveation", + "feature.vanilla_fresnel.base_f0_multiplier": "Base F0 Multiplier", + "feature.vanilla_fresnel.complex_material_f0_multiplier": "Complex Material Env F0 Multiplier", + "feature.vanilla_fresnel.cubemap_to_f0_multiplier": "Cubemap to F0 Multiplier", + "feature.vanilla_fresnel.description": "Add realistic environmental reflections to vanilla materials.", + "feature.vanilla_fresnel.enable": "Enable Vanilla Fresnel", + "feature.vanilla_fresnel.enable_dynamic_cubemaps_conversion": "Enable Auto Cubemaps Conversion", + "feature.vanilla_fresnel.enable_eye_special_handling": "Enable Eye Special Handling", + "feature.vanilla_fresnel.enable_ggx": "Enable Phong to GGX", + "feature.vanilla_fresnel.enable_ggx_on_grass": "Enable Phong to GGX on Grass", + "feature.vanilla_fresnel.key_feature_1": "Add environmental reflections to all materials", + "feature.vanilla_fresnel.key_feature_2": "Supports vanilla and complex materials", + "feature.vanilla_fresnel.key_feature_3": "Optionally turn vanilla phong specular into GGX", + "feature.vanilla_fresnel.key_feature_4": "Optionally turn static cubemaps into dynamic reflections", + "feature.vanilla_fresnel.min_f0": "Min F0", + "feature.vanilla_fresnel.name": "Vanilla Fresnel", + "feature.vanilla_fresnel.roughness_multiplier": "Roughness Multiplier", + "feature.vanilla_fresnel.specular_roughness_blend": "Specular Roughness Blend", "feature.volumetric_lighting.description": "Volumetric Lighting creates realistic light scattering effects through fog, dust, and atmospheric particles.\nThis adds dramatic god rays and atmospheric depth to both interior and exterior environments.", "feature.volumetric_lighting.enable_exteriors": "Enable Volumetric Lighting in Exteriors", "feature.volumetric_lighting.enable_exteriors_tooltip": "Volumetric god-rays / fog scattering in exterior cells.",