From a0355c9b897281d0bfea48bec9d490724ecabd96 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Sun, 12 Apr 2026 17:54:45 -0500 Subject: Add NIF previewer plugin with Linux-specific fixes Vendored from github.com/Parapets/mo2-preview_nif and adapted for Linux: - BSA texture resolution via MO2 virtual tree with case-insensitive fallback walk (Linux FS is case-sensitive; NIFs reference textures with arbitrary case against files on disk) - Process-wide cached BSA candidate list, keyed by profile path, priority-sorted, filtering disabled mods - Path backslash normalization before loose-file lookup - Polygon offset on SLSF1_Decal-flagged shapes to break z-ties with coincident base meshes (road decals, moss overlays) - Opaque framebuffer via glColorMask alpha lock so alpha-blended shapes can't bleed the dialog background through the preview area - QMouseEvent::globalPosition().toPoint() for Qt6 compatibility Preview dialog behavior (organizercore.cpp): - Switch previewFile and previewFileWithAlternatives from stack-allocated exec() to heap + ApplicationModal + show() + WA_DeleteOnClose. - Parent passed as nullptr so preview lifetime is decoupled from the stack-allocated ModInfoDialog that spawns it. Nested exec() inside the enclosing dialog's exec() was softlocking on close. Co-Authored-By: Claude Opus 4.6 (1M context) --- libs/preview_nif/data/shaders/sk_effectshader.frag | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 libs/preview_nif/data/shaders/sk_effectshader.frag (limited to 'libs/preview_nif/data/shaders/sk_effectshader.frag') diff --git a/libs/preview_nif/data/shaders/sk_effectshader.frag b/libs/preview_nif/data/shaders/sk_effectshader.frag new file mode 100644 index 0000000..3a22388 --- /dev/null +++ b/libs/preview_nif/data/shaders/sk_effectshader.frag @@ -0,0 +1,101 @@ +#version 120 + +uniform sampler2D BaseMap; +uniform sampler2D GreyscaleMap; + +uniform bool doubleSided; + +uniform bool hasSourceTexture; +uniform bool hasGreyscaleMap; +uniform bool greyscaleAlpha; +uniform bool greyscaleColor; + +uniform bool useFalloff; +uniform bool vertexColors; +uniform bool vertexAlpha; + +uniform bool hasWeaponBlood; + +uniform vec4 glowColor; +uniform float glowMult; + +uniform vec2 uvScale; +uniform vec2 uvOffset; + +uniform vec4 falloffParams; +uniform float falloffDepth; + +varying vec2 TexCoord; +varying vec3 LightDir; +varying vec3 ViewDir; + +varying vec4 C; + +varying vec3 N; +varying vec3 v; + +vec4 colorLookup( float x, float y ) { + + return texture2D( GreyscaleMap, vec2( clamp(x, 0.0, 1.0), clamp(y, 0.0, 1.0)) ); +} + +void main( void ) +{ + vec4 baseMap = texture2D( BaseMap, TexCoord.st * uvScale + uvOffset ); + + vec4 color; + + vec3 normal = N; + + // Reconstructed normal + //normal = normalize(cross(dFdy(v.xyz), dFdx(v.xyz))); + + //if ( !doubleSided && !gl_FrontFacing ) { return; } + + vec3 E = normalize(ViewDir); + + float tmp2 = falloffDepth; // Unused right now + + // Falloff + float falloff = 1.0; + if ( useFalloff ) { + float startO = min(falloffParams.z, 1.0); + float stopO = max(falloffParams.w, 0.0); + + // TODO: When X and Y are both 0.0 or both 1.0 the effect is reversed. + falloff = smoothstep( falloffParams.y, falloffParams.x, abs(E.b)); + + falloff = mix( max(falloffParams.w, 0.0), min(falloffParams.z, 1.0), falloff ); + } + + float alphaMult = glowColor.a * glowColor.a; + + color.rgb = baseMap.rgb; + color.a = baseMap.a; + + if ( hasWeaponBlood ) { + color.rgb = vec3( 1.0, 0.0, 0.0 ) * baseMap.r; + color.a = baseMap.a * baseMap.g; + } + + color.rgb *= C.rgb * glowColor.rgb; + color.a *= C.a * falloff * alphaMult; + + if ( greyscaleColor ) { + // Only Red emissive channel is used + float emRGB = glowColor.r; + + vec4 luG = colorLookup( baseMap.g, C.g * falloff * emRGB ); + + color.rgb = luG.rgb; + } + + if ( greyscaleAlpha ) { + vec4 luA = colorLookup( baseMap.a, C.a * falloff * alphaMult ); + + color.a = luA.a; + } + + gl_FragColor.rgb = color.rgb * glowMult; + gl_FragColor.a = color.a; +} -- cgit v1.3.1