From 477d38d92851d9093ff7db8e6e158fff1380b3dd Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Sat, 30 Aug 2025 10:43:33 -0400 Subject: [PATCH] Wallpaper: shaders improvements with more parameters and new Stripes shader --- Commons/Settings.qml | 1 + Modules/Background/Background.qml | 56 ++++++--- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 99 +++++++++------ Services/WallpaperService.qml | 6 +- Shaders/frag/wp_disc.frag | 11 +- Shaders/frag/wp_fade.frag | 9 +- Shaders/frag/wp_stripes.frag | 126 ++++++++++++++++++++ Shaders/frag/wp_wipe.frag | 18 ++- Shaders/qsb/wp_disc.frag.qsb | Bin 2398 -> 2532 bytes Shaders/qsb/wp_fade.frag.qsb | Bin 1548 -> 1470 bytes Shaders/qsb/wp_stripes.frag.qsb | Bin 0 -> 5031 bytes Shaders/qsb/wp_wipe.frag.qsb | Bin 2629 -> 2677 bytes 12 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 Shaders/frag/wp_stripes.frag create mode 100644 Shaders/qsb/wp_stripes.frag.qsb diff --git a/Commons/Settings.qml b/Commons/Settings.qml index df60967..59f78e0 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -170,6 +170,7 @@ Singleton { property int randomIntervalSec: 300 // 5 min property int transitionDuration: 1500 // 1500 ms property string transitionType: "random" + property real transitionEdgeSmoothness: 0.05 property list monitors: [] } diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 780e17b..df13fc6 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -21,17 +21,20 @@ Variants { property bool firstWallpaper: true property string transitionType: 'fade' property bool transitioning: false - property real transitionProgress: 0.0 + property real transitionProgress: 0 + property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness readonly property var allTransitions: WallpaperService.allTransitions // Wipe direction: 0=left, 1=right, 2=up, 3=down property real wipeDirection: 0 - property real wipeSmoothness: 0.05 // Disc property real discCenterX: 0.5 property real discCenterY: 0.5 - property real discSmoothness: 0.05 + + // Stripe + property real stripesCount: 16 + property real stripesAngle: 0 // External state management property string servicedWallpaper: WallpaperService.getWallpaper(modelData.name) @@ -58,7 +61,8 @@ Variants { transitionType = 'fade' } - //Logger.log("Background", "Using transition:", transitionType) + Logger.log("Background", "New wallpaper: ", servicedWallpaper, "On:", modelData.name, "Transition:", transitionType) + switch (transitionType) { case "none": setWallpaperImmediate(servicedWallpaper) @@ -84,6 +88,11 @@ Variants { discCenterY = Math.random() setWallpaperWithTransition(servicedWallpaper) break + case "stripes": + stripesCount = Math.round(Math.random() * 24 + 2) + stripesAngle = Math.random() * 360 + setWallpaperWithTransition(servicedWallpaper) + break default: setWallpaperWithTransition(servicedWallpaper) break @@ -134,7 +143,7 @@ Variants { property variant source1: currentWallpaper property variant source2: nextWallpaper - property real fade: transitionProgress + property real progress: root.transitionProgress fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb") } @@ -146,9 +155,10 @@ Variants { property variant source1: currentWallpaper property variant source2: nextWallpaper - property real progress: transitionProgress - property real direction: wipeDirection - property real smoothness: wipeSmoothness + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real direction: root.wipeDirection + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb") } @@ -160,14 +170,32 @@ Variants { property variant source1: currentWallpaper property variant source2: nextWallpaper - property real progress: transitionProgress - property real centerX: discCenterX - property real centerY: discCenterY - property real smoothness: discSmoothness - property real aspectRatio: width / height + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real aspectRatio: root.width / root.height + property real centerX: root.discCenterX + property real centerY: root.discCenterY + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb") } + // Diagonal stripes transition shader + ShaderEffect { + id: stripesShader + anchors.fill: parent + visible: transitionType === 'stripes' + + property variant source1: currentWallpaper + property variant source2: nextWallpaper + property real progress: root.transitionProgress + property real smoothness: root.edgeSmoothness + property real aspectRatio: root.width / root.height + property real stripeCount: root.stripesCount + property real angle: root.stripesAngle + + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb") + } + // Animation for the transition progress NumberAnimation { id: transitionAnimation @@ -176,7 +204,7 @@ Variants { from: 0.0 to: 1.0 duration: Settings.data.wallpaper.transitionDuration ?? 1000 - easing.type: transitionType.startsWith('wipe_') ? Easing.InOutCubic : Easing.OutQuad + easing.type: Easing.InOutCubic //transitionType.startsWith('wipe_') ? Easing.InOutCubic : Easing.OutQuad onFinished: { // Swap images after transition completes currentWallpaper.source = nextWallpaper.source diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 546a4ca..71dd82e 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -88,47 +88,14 @@ ColumnLayout { // Random Wallpaper NToggle { label: "Random Wallpaper" - description: "Automatically select random wallpapers from the folder." + description: "Schedule random wallpaper changes at regular intervals." checked: Settings.data.wallpaper.randomEnabled onToggled: checked => Settings.data.wallpaper.randomEnabled = checked } - // Transition Type - NComboBox { - label: "Transition Type" - description: "Animation type when switching between wallpapers." - model: WallpaperService.transitionsModel - currentKey: Settings.data.wallpaper.transitionType - onSelected: key => Settings.data.wallpaper.transitionType = key - } - - // Transition Duration - ColumnLayout { - NLabel { - label: "Transition Duration" - description: "Duration of transition animations in seconds." - } - - RowLayout { - spacing: Style.marginL * scaling - NSlider { - Layout.fillWidth: true - from: 100 - to: 5000 - stepSize: 100 - value: Settings.data.wallpaper.transitionDuration - onMoved: Settings.data.wallpaper.transitionDuration = value - cutoutColor: Color.mSurface - } - NText { - text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s" - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - } - - // Interval (slider + H:M inputs) + // Interval ColumnLayout { + visible: Settings.data.wallpaper.randomEnabled RowLayout { NLabel { label: "Wallpaper Interval" @@ -224,6 +191,66 @@ ColumnLayout { } } } + + // Transition Type + NComboBox { + label: "Transition Type" + description: "Animation type when switching between wallpapers." + model: WallpaperService.transitionsModel + currentKey: Settings.data.wallpaper.transitionType + onSelected: key => Settings.data.wallpaper.transitionType = key + } + + // Transition Duration + ColumnLayout { + NLabel { + label: "Transition Duration" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 100 + to: 5000 + stepSize: 100 + value: Settings.data.wallpaper.transitionDuration + onMoved: Settings.data.wallpaper.transitionDuration = value + cutoutColor: Color.mSurface + } + NText { + text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + + // Edge Smoothness + ColumnLayout { + NLabel { + label: "Transition Edge Smoothness" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 0.0 + to: 1.0 + value: Settings.data.wallpaper.transitionEdgeSmoothness + onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value + cutoutColor: Color.mSurface + } + NText { + text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + + } // Reusable component for interval preset chips diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 472e317..403153a 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -31,6 +31,10 @@ Singleton { key: "disc" name: "Disc" } + ListElement { + key: "stripes" + name: "Stripes" + } ListElement { key: "wipe_left" name: "Wipe Left" @@ -149,7 +153,7 @@ Singleton { return } - Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) + //Logger.log("Wallpaper", "setWallpaper on", screenName, ": ", path) var wallpaperChanged = false diff --git a/Shaders/frag/wp_disc.frag b/Shaders/frag/wp_disc.frag index 292ce59..4bde549 100644 --- a/Shaders/frag/wp_disc.frag +++ b/Shaders/frag/wp_disc.frag @@ -1,3 +1,4 @@ +// ===== wp_disc.frag ===== #version 450 layout(location = 0) in vec2 qt_TexCoord0; @@ -12,7 +13,7 @@ layout(std140, binding = 0) uniform buf { float progress; // Transition progress (0.0 to 1.0) float centerX; // X coordinate of disc center (0.0 to 1.0) float centerY; // Y coordinate of disc center (0.0 to 1.0) - float smoothness; // Edge smoothness (0.01 to 0.5, default 0.05) + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) float aspectRatio; // Width / Height of the screen } ubuf; @@ -21,6 +22,10 @@ void main() { vec4 color1 = texture(source1, uv); // Current (old) wallpaper vec4 color2 = texture(source2, uv); // Next (new) wallpaper + // Map smoothness from 0.0-1.0 to 0.001-0.5 range + // Using a non-linear mapping for better control + float mappedSmoothness = mix(0.001, 0.5, ubuf.smoothness * ubuf.smoothness); + // Adjust UV coordinates to compensate for aspect ratio // This makes distances circular instead of elliptical vec2 adjustedUV = vec2(uv.x * ubuf.aspectRatio, uv.y); @@ -39,7 +44,7 @@ void main() { // Scale progress to cover the maximum distance // Add extra range for smoothness to ensure complete coverage // Adjust smoothness for aspect ratio to maintain consistent visual appearance - float adjustedSmoothness = ubuf.smoothness * max(1.0, ubuf.aspectRatio); + float adjustedSmoothness = mappedSmoothness * max(1.0, ubuf.aspectRatio); float radius = ubuf.progress * (maxDist + adjustedSmoothness); // Use smoothstep for a smooth edge transition @@ -49,4 +54,4 @@ void main() { fragColor = mix(color2, color1, factor); fragColor *= ubuf.qt_Opacity; -} \ No newline at end of file +} diff --git a/Shaders/frag/wp_fade.frag b/Shaders/frag/wp_fade.frag index 10c3a63..24917a5 100644 --- a/Shaders/frag/wp_fade.frag +++ b/Shaders/frag/wp_fade.frag @@ -7,16 +7,13 @@ layout(binding = 2) uniform sampler2D source2; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; - float fade; + float progress; }; void main() { vec4 color1 = texture(source1, qt_TexCoord0); vec4 color2 = texture(source2, qt_TexCoord0); - // Smooth cross-fade using smoothstep for better visual quality - float smoothFade = smoothstep(0.0, 1.0, fade); - - // Mix the two textures based on fade value - fragColor = mix(color1, color2, smoothFade) * qt_Opacity; + // Mix the two textures based on progress value + fragColor = mix(color1, color2, progress) * qt_Opacity; } \ No newline at end of file diff --git a/Shaders/frag/wp_stripes.frag b/Shaders/frag/wp_stripes.frag new file mode 100644 index 0000000..7d12b30 --- /dev/null +++ b/Shaders/frag/wp_stripes.frag @@ -0,0 +1,126 @@ +// ===== wp_stripes.frag ===== +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source1; // Current wallpaper +layout(binding = 2) uniform sampler2D source2; // Next wallpaper + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float progress; // Transition progress (0.0 to 1.0) + float stripeCount; // Number of stripes (default 12.0) + float angle; // Angle of stripes in degrees (default 30.0) + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) + float aspectRatio; // Width / Height of the screen +} ubuf; + +void main() { + vec2 uv = qt_TexCoord0; + vec4 color1 = texture(source1, uv); // Current (old) wallpaper + vec4 color2 = texture(source2, uv); // Next (new) wallpaper + + // Map smoothness from 0.0-1.0 to 0.001-0.1 range + // Using a non-linear mapping for better control at low values + float mappedSmoothness = mix(0.001, 0.1, ubuf.smoothness * ubuf.smoothness); + + // Use values directly without forcing defaults + float stripes = (ubuf.stripeCount > 0.0) ? ubuf.stripeCount : 12.0; + float angleRad = radians(ubuf.angle); + float edgeSmooth = mappedSmoothness; + + // Create a coordinate system for stripes based on angle + // At 0°: vertical stripes (divide by x) + // At 45°: diagonal stripes + // At 90°: horizontal stripes (divide by y) + + // Transform coordinates based on angle + float cosA = cos(angleRad); + float sinA = sin(angleRad); + + // Project the UV position onto the stripe direction + // This gives us the position along the stripe direction + float stripeCoord = uv.x * cosA + uv.y * sinA; + + // Perpendicular coordinate (for edge movement) + float perpCoord = -uv.x * sinA + uv.y * cosA; + + // Calculate the range of perpCoord based on angle + // This determines how far edges need to travel to fully cover the screen + float minPerp = min(min(0.0 * -sinA + 0.0 * cosA, 1.0 * -sinA + 0.0 * cosA), + min(0.0 * -sinA + 1.0 * cosA, 1.0 * -sinA + 1.0 * cosA)); + float maxPerp = max(max(0.0 * -sinA + 0.0 * cosA, 1.0 * -sinA + 0.0 * cosA), + max(0.0 * -sinA + 1.0 * cosA, 1.0 * -sinA + 1.0 * cosA)); + + // Determine which stripe we're in + float stripePos = stripeCoord * stripes; + int stripeIndex = int(floor(stripePos)); + + // Determine if this is an odd or even stripe + bool isOddStripe = (stripeIndex % 2) == 1; + + // Calculate the progress for this specific stripe with wave delay + // Use absolute stripe position for consistent delay across all stripes + float normalizedStripePos = clamp(stripePos / stripes, 0.0, 1.0); + + // Reduced delay factor and better scaling to match other shaders' timing + float maxDelay = 0.15; // Maximum delay for the last stripe + float stripeDelay = normalizedStripePos * maxDelay; + + // Ensure all stripes complete when progress reaches 1.0 + // without making the overall animation appear faster + float stripeProgress = clamp((ubuf.progress - stripeDelay) / (1.0 - maxDelay), 0.0, 1.0); + + // Apply smooth easing + stripeProgress = smoothstep(0.0, 1.0, stripeProgress); + + // Use the perpendicular coordinate for edge comparison + float yPos = perpCoord; + + // Calculate edge position for this stripe + // Use the actual perpendicular coordinate range for this angle + float perpRange = maxPerp - minPerp; + float margin = edgeSmooth * 2.0; // Simplified margin calculation + float edgePosition; + if (isOddStripe) { + // Odd stripes: edge moves from max to min + edgePosition = maxPerp + margin - stripeProgress * (perpRange + margin * 2.0); + } else { + // Even stripes: edge moves from min to max + edgePosition = minPerp - margin + stripeProgress * (perpRange + margin * 2.0); + } + + // Determine which wallpaper to show based on rotated position + float mask; + if (isOddStripe) { + // Odd stripes reveal new wallpaper from bottom + mask = smoothstep(edgePosition - edgeSmooth, edgePosition + edgeSmooth, yPos); + } else { + // Even stripes reveal new wallpaper from top + mask = 1.0 - smoothstep(edgePosition - edgeSmooth, edgePosition + edgeSmooth, yPos); + } + + // Mix the wallpapers + fragColor = mix(color1, color2, mask); + + // Force exact values at start and end to prevent any bleed-through + if (ubuf.progress <= 0.0) { + fragColor = color1; // Only show old wallpaper at start + } else if (ubuf.progress >= 1.0) { + fragColor = color2; // Only show new wallpaper at end + } else { + // Add manga-style edge shadow only during transition + float edgeDist = abs(yPos - edgePosition); + float shadowStrength = 1.0 - smoothstep(0.0, edgeSmooth * 2.5, edgeDist); + shadowStrength *= 0.2 * (1.0 - abs(stripeProgress - 0.5) * 2.0); + fragColor.rgb *= (1.0 - shadowStrength); + + // Add slight vignette during transition for dramatic effect + float vignette = 1.0 - ubuf.progress * 0.1 * (1.0 - abs(stripeProgress - 0.5) * 2.0); + fragColor.rgb *= vignette; + } + + fragColor *= ubuf.qt_Opacity; +} \ No newline at end of file diff --git a/Shaders/frag/wp_wipe.frag b/Shaders/frag/wp_wipe.frag index f6db80e..10b948a 100644 --- a/Shaders/frag/wp_wipe.frag +++ b/Shaders/frag/wp_wipe.frag @@ -1,3 +1,5 @@ + +// ===== wp_wipe.frag ===== #version 450 layout(location = 0) in vec2 qt_TexCoord0; @@ -11,7 +13,7 @@ layout(std140, binding = 0) uniform buf { float qt_Opacity; float progress; // Transition progress (0.0 to 1.0) float direction; // 0=left, 1=right, 2=up, 3=down - float smoothness; // Edge smoothness (0.01 to 0.5, default 0.05) + float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) } ubuf; void main() { @@ -19,37 +21,41 @@ void main() { vec4 color1 = texture(source1, uv); // Current (old) wallpaper vec4 color2 = texture(source2, uv); // Next (new) wallpaper + // Map smoothness from 0.0-1.0 to 0.001-0.5 range + // Using a non-linear mapping for better control + float mappedSmoothness = mix(0.001, 0.5, ubuf.smoothness * ubuf.smoothness); + float edge = 0.0; float factor = 0.0; // Extend the progress range to account for smoothness // This ensures the transition completes fully at the edges - float extendedProgress = ubuf.progress * (1.0 + 2.0 * ubuf.smoothness) - ubuf.smoothness; + float extendedProgress = ubuf.progress * (1.0 + 2.0 * mappedSmoothness) - mappedSmoothness; // Calculate edge position based on direction // As progress goes from 0 to 1, we reveal source2 (new wallpaper) if (ubuf.direction < 0.5) { // Wipe from right to left (new image enters from right) edge = 1.0 - extendedProgress; - factor = smoothstep(edge - ubuf.smoothness, edge + ubuf.smoothness, uv.x); + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.x); fragColor = mix(color1, color2, factor); } else if (ubuf.direction < 1.5) { // Wipe from left to right (new image enters from left) edge = extendedProgress; - factor = smoothstep(edge - ubuf.smoothness, edge + ubuf.smoothness, uv.x); + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.x); fragColor = mix(color2, color1, factor); } else if (ubuf.direction < 2.5) { // Wipe from bottom to top (new image enters from bottom) edge = 1.0 - extendedProgress; - factor = smoothstep(edge - ubuf.smoothness, edge + ubuf.smoothness, uv.y); + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.y); fragColor = mix(color1, color2, factor); } else { // Wipe from top to bottom (new image enters from top) edge = extendedProgress; - factor = smoothstep(edge - ubuf.smoothness, edge + ubuf.smoothness, uv.y); + factor = smoothstep(edge - mappedSmoothness, edge + mappedSmoothness, uv.y); fragColor = mix(color2, color1, factor); } diff --git a/Shaders/qsb/wp_disc.frag.qsb b/Shaders/qsb/wp_disc.frag.qsb index 485186eb3b9d20a42aabf0e0bd11c919b0f55384..9c66bfb94229f7df7cd1831b23c20033749d497a 100644 GIT binary patch literal 2532 zcmV}sVPyep=d%yIwXJ&sRjYcbQ8`>nN-g9K_z4N<|xpVj4 z*^!B89}%U9Xb%ycq$k9wO3UO>hvvv9p8~2;o`^<>i2jL-5-y?O2-PVdiyBh6DaB*; zuM{1pb&9A?M0+Xf-mhq=327kq@E%#jDIk|RM6@4>>l*qEb#oLV3)CiyY;uXyGZ?+D zyfd#is7(R+)S!SI3MquZ{4jw-WRpW4adIe-ZVUMBoxxWDzdH0hIUY|>g z!-S5LMIp7xA)7eeml1QxhuHEkF?ERrKSUw<6j31EEf7%#Yq%$QRyw?>!DaS**J~W# zwH|B#pGQYdPY8GR(_Z=mxNp*kbZ|e7%kNuvm+t05UdxrHMJm%F8kIOPc9^7z3y|~6CJ=Ucs zkjpynG`hmVA>tdyG4?^wr@=c$Df$pP#GKB6{s`+S9QYjY1iYZnX|#|p zYI73f7YzMnQ#>_N;&&|8MS7qEAM#-yC8SpxHS8j*7= zBVYDu`61T-utA^DXt7?WG+M}?HRu@wf777v81y59{@kE#gLV!23Fuio4+oIrS7}tv z?EuC^P98-*D(o@L{}GMJcpeAl7~~Iwr{v#8{wn!*4Ec9~c?a?*z`H?XGLL?S-1`VR z#9C*-SD48J%oOs)VhC)yjS!s0FD z7*qOJ65_uKzQX(}0aF2A`TlG8UeSD5(|lM3M)>duV@g*Qbx6^_K^;=-Zvi9t2WVVk z)}i|oj0?S=gRk_6P1Y%G9!^3m>) z6)iSvLeC4>@PY9(yKU(4Vb=vu=@2g{u11H@sn&)A6Ka^qkl8f+cnZv>h7s>6H?Lhn-abM!W)`?u&Fi!>p}zXnhL2)knJ8?Cl}1zlgm?iR+? zn*0uH@%L8oJKe4lT4hO8B~9h={`xa*hG&&%m_w$pRy>i=YPFkAP@HF>9|g8kD8B+wET_|(R`AUA8f??Ci(+QWPG`>dgIZpU z`EHGgP-i9)6p4y9*{lJ@Sc%zw(+>)4mT{-UqrfT7XW&ehMVpf*vY3D@YREVgEvwyj zYD?WPv02t~JDGegpD*O&t8}q+`C@6ha6UhM{?g@AzEr$?;nKzFEX(Jnvoc({UYOaG z$>}*(WLE8Q6mqBb;31opoJ_Qt>nIs*S%jatXGx-Sa*?~@LJp~rtfmK8?VEVb4LO^Y zUzTS(nVxG|!Taw|CIFLctPa{(&9VV~t4XqKmNgx( z!8bB8Ov>wQSNCL}l$vmV9+;^yCEY3H^4XXBA6PXv5*j2*J-lp+DL>em1dDwt>XyxY zvC45Ch1_Xpp!RHnY*yJ!Qk<2G4E2p>_0zy=%v;>53Ptq%UJR|lv6&+4G- zC2a-$h{wCOdSEr?LVQoX@DAj{ah= zNArX)1L}(nhsg9oYi*O+5}67Md@BvufJE~ z0A5vfT8}puVOxsJ^%A$Z!>T}4QN(q0DA2?77W?!ThUhIO>J{j8$Sv;L@ifgop^x=9 z3-r*X#XI*OR_6jg46Ap%cEpiJ3EJ2UTfVt6x3F-3J|BlFLB~SG4JDdcHp7-4R+p`y z;c%#sJxc(peT@s|hqNx}?KUrjJ{JcrV=jexexbE91lN%4274zbA5AA{@8syCF3&#d z*@3K`GC8_MCkeMb$V3ss0vTGuVePJJw7bFb5pQA#N)rpY z)o`k=xL1XuuRzV}h+IP5=+&8+kzhL3;7cL~>71M%0Ii(L&g<|f@C*gd^=_WRFTS|O zw@&o>2wr|qhZf#Ibqwf9VVrc4$Hf} z|A+Ka1TRG}@*+UPxPkZ1K##<-a&8elZqYw{(e!|SyRn@ zho<^{{jyNELOwI|zxaJ2(b9kLE5mDUuRXVZ+`l&XMw@9RyjI4~n(<=UFPQXlskJFT u?oOgTjmbiRw@u@X6aQCi literal 2398 zcmV-k38D4?04eu)ob6iccN51Izu2-3!aNe5d6Z7#CXpSX$g0W1LO@~@Ac2}-hv2C0 zcD-833(~IGoi!rFJ?ZJ`*M96@(m$?WdfIbmeJialAg&nb$z`6S7uP`S-&Rm0)BHG{3J!xQOhA|hx{RzZjTc> zO*Tc;C6^qMbVo(ZqX1$X{*%ciUC*I^SsYAz8eG9 z;n~TFxfyonFdd{nfcpwfC%R87G8MHa~$cWVm zno$0{MU%=0{(YJzl}L{83<;{~8@q zF`UjwTur}0lk^H=&GN7Ib1AXMG_NliG_NN=rzl6iz+CuwAGDr7>-#1AVIR&?PQ~g7 z{5?m9RBYeIc-Dt?{K{C@D(H8>V;|my4+r1_)9)Gb1z@KQAB!nGZ}Lfl{s=xY&2m@O zjDEPR$G=6sXnNaN!w|ZTz+a}Llzv@`Kcp!or)v^p{!SB0cNO_?$XI_~-(xBIj6pO0 z%@kcWXkL$MioTqpZ>8vs6up_E?G){St|7l?k+YX*Qq5%+W1M3rkzX2n3iJJjrd0e+ z19J-U$H3F_uOT0`{Oc+CH-LE^@@K&Nl%|xg0{C;#F^&B#fZ^PkN4~vhU>3mtGfg3{ z61fFK_j%|p8geD@Z_||W_XEh5;NP3zY1?iQA44#hnMdX{NYsj}*no;^LAv^t1ZpD!MJVW<2@c#t4>)7Mh47nQ__PzjL+xrr-Ul?<~i8&YG&zG3I|K6-_}ce7@ck>phjqh;HDK6>TNu;2zQ+F7^j++Kz5d?-!~9vARv3NE`wZi( zcLRLwcO8CzYv}$h_!_g75#Qf|ujgSI^SFv}9sA!yrv)E2!PEL3=-)Q(vJ4fr06yWc-vL0WiLS>7tg;(m_Q( zM64EJBLIed^f1n{To)z!hNffm6|H+0b^JEXsPRYOnIE>xV?Ed{L2nE^+l?@;*W^2_ z$z#LT6Y#Y!-=kmt1vPvRyeEcE=KX^X(8tM1CWkZZ0W1xUH@Uyf?Yd#)1-@7;m-F4w zbv#Awc&(kTXto1eR!7*LFRS@{B*WN|Vmof;pX7xQqGQV?@laZ~Y#DmJs^TdP%v|f* zjwc_R+-?}OLN|&`p5yw`4eyPzHceL42?Dv}r)2G@>pJp|Exn+c->c^HvF|m5u){8j zcsp(ytYjjQ-RZX7u(B$mAPyaOvHA=^rJB$0+Tml*Z;4&ksqoA;+}=tMgpD$f1ztmN zs0;H5ibBP^qLzYUY)LplI|vs=O-Q#Vl zttaJkUE0zW7El(7s)>#mJx*_BNblkpy_HP8jLt{WmY$PLGhMgMvEky4p0?wuMxU(ZvKl#>fW!>7F2<^VE?beIid)jW; z9cey^y7I@R;u@m?&ai!H&Q1=B<+@KdHh@N+M#MuGZCeXO+4822_wUFq9IU3 z0x~%zGXO?yQ*F?dapm#Zj@+=&4jg|V&Z;}EZ0>_NsAa@qvl>h($ diff --git a/Shaders/qsb/wp_fade.frag.qsb b/Shaders/qsb/wp_fade.frag.qsb index e06417c6f07afa3020f1dea72d0ef13311f0fd73..89641d8b0148d85f48da59479b767e133b282537 100644 GIT binary patch literal 1470 zcmV;v1wr}%024NNob6a`Z`)K9eww7s=8e6-c5inpjAW}q>U9V;Yc42hjxUzb!<{{SBlpZG!i75)TY5a?VVCpT`|Zh#OUu$0E<_&Lu#_c`a-P5~ST zPy#Ro;0oSA!NDec46up_5hPlu11JE%AADfKK?LW}hJ=fb4({skl>W%j8Ehj#Tc0_2 z02{y)15?!WX@1Y)LO~+H0Kf@GJT}m0vXcuOvW`AncnF}d%S%4aa?2lQ(1%2X4kUcU zh&f>Gpn&Wkkxn5-gai^Ed@KN{@V<#+-!Tri4Y*eP?;bP!-5R+{FN%e^GNp3@$1u;{ zvnc2UPhduWfAwtRSuIwr+TuehKThciq?vM-A#y}|gaJa=WjPWCMOuAqb)e2w;VlQp}+Yo_}y z;TErdlR0j&9^|`-lGamj4)@u8Fi!|#|>+*VOdES zv?JLgKH)sD4Ntmg?DqOyU)pOTjuPql3ymWHM6u(_-5~6U9pAGli%oy98bz{Ir)yEr z5+qBd${X16y+z?g-AFEoWug3mN~CYERd~VqF@|a*7h_L|u_wlKy3Uum#(n9BQFZP5zgV$DYW07Wbd`QK zs`S&SN)P|TRSLpMV-zJSoq@J=JFC>Y?><`ZKUjy3)i8s9aH8n(MK68X6YN)SJ88Fl zspVV}((eSZ@?~Yap6ygd-#b0`+eoI>SR5?U63(!GVoCH}@4Kkoj(t_}hRN&FF_M$X zK^ONyvyJ3|wcTo3{LSos^e9bcB`dX=yP_O6(u!{=SNXzWl*4^sqGLiI#A}c7E*!vX zPvj-E6)RT-Ub@A6tC`ov`;A9t?ev#GJ#DjiOny*SzTX$xmHi5 zn!vH1s2qv0tXL8oubfR+c6`MYy6p+zjP*W84!fVlt)o!0#D zMXHtFU`F??W;1&f9164z;DQEbw;U$g==Hm#1-qGT9AxJ~#FX{%XtVTL7H{_&hfaIS zkv;M>^SlVc+ItN97=?W(MU?CONzcP{nWLYG#;8Q;gwTjgYXSHXnR`s~|04L`O1WRm Yge99kx%qnMxa?PVK3*Sx0@5c_2-dd!CjbBd literal 1548 zcmV+n2J`s<02HBkob6cKZrer>9ZPcTFlp}hq{$|3Q#w%@iE)a+iroZu(*#K1AU4th zfzK$9~(b9VO3&XN`Y z90q_2089YD1$YetJXiw`eOQ775=fy1H2^390Q^A*j5(0t6oin14{aITk>QH|z|cdm z0Xc;7&V#$)0>BdkQLjQh7Cr<1i(4z_aT|v5E(HdN&GGCkM!6={9{ z`30!JhlI!ZT_sJ?hd8A}1+Gy(S82>^q!0SeLPhG$D5u+G-!SGdL-(5R6WSA|h!1U> zd^+ACzRzG%#_AqM%x_SV>!(5WIBamCea5g+k4t1PKt<|)j5J3Zbv;Fzeg%^fcbYKh zUxc#cKW)UHBh7XE`9k~!!knl0Gvs>&lTwS1NUs~jfx3K5{-ZE0?VB|JI@RwAVUX_< zJ;zuaZgW} zzBc)OhY7f^I-^2+&;;#s)Giah&TR2Ci;|etYc;Ev@*tAdX4Ky7v9Oc)qOr>z#iC(Z znMm_Mu#G&l-dKz=*7e04do8@@zDT2fL;55GI#+sr5Q%Nm8~QEYuyz`jmB&$-q+R4< z-d)%5r~xy-+w1VuU1nL5rva}w4ges2~rH%Y|iW531O0vVa$J-g=A zs;rKGyt}T00#R7I<2_FO_OdVhW^J71!-r4sK`-2cn^{$>5qDC^I|vflfTmNkmUHENOz)n#Gox0i64zkTz*d+Yua zjXOyEZ|hlnJL@k}be{`0@-XD7tie1>c{|DkPwkDGmM2ZV^Sb^wNvb9>*Pp{7yg|X) zJnQ+vw@DagT-d?rj0s7}6rgjvpg8p+$l0D%t7T1Zkq=ZNi+XLCc?%qWRV|TK;S0_@ zLV2`sOmv3my?EUb-ufQA?pR(#Tbb}h6sYMH*QWVwbb(TTt-g5r@|CxgCRye^jeEJE za*SbP^K9*lua{O#> zE{eT(GZ+m-LKZj%!oFWag|ef<>R?@sX{ioRd%JpS$m#U^{XVsnS7@o-(yC2mSJ_x5 z{5JO@yw@U*6mP*qIHRjXCrmX^jxj?khC~_)WO^KP&c;%c-OWXw#sz{MbH~7MdHsJ7 ze$wBciQ=G>w>W#y<-+fHnP_#QjYVtd&!TSY4*qdA7cDE#@Ixy0yFBap0cSF5+$Z2* ze|fK$3DIHJHJr&_OTF2Ym9}fM>+548qj;Z2Yc}(Yr*$uk9O!%>!{7qb5tl3oiuuQ8u_#AAhJ!!brHJkc%voFv!fOQGfyA%2q|hwm-nlBf ygWkGEZzZawu%{-aYV;Z|yo%}PANmEa@FN9c(Z;jTkZ&EQf9NR2%i~XD`kmn)s~n5~ diff --git a/Shaders/qsb/wp_stripes.frag.qsb b/Shaders/qsb/wp_stripes.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..14cfb3e9be1ea1c8f60a73d309ab9835601d9570 GIT binary patch literal 5031 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 z zga@q9@g`y?CStLg>y$iyhMY8OhifH;CAtz=Gu4Xir+1rySMv*YYGMIEdLkA(YWZ`H zTQ-l{g*5eT%AVcjd44fTZJtv!DcJFTgoUGON6nlDMO(%!cool2nK?7GXTzFrr+3HU z%%E92+OH$iEs$viDMB%0RjYRKU_DG`&YW>(ykMOQvcebli+%L)FkT?y=#LaB5jsRGqmMKP#*qs*%VR?*CvzEyNAHxMpz zexw_Av22TIQY5wDs=$I5>@;)qD=xK16a|h;?SbRAX-AJepS+6KjwWW!0j{;*+{EcQ zbAW4!h*fRBS{I5BkyyXPDRNv-WS%(gr0rMv^l;lW=(M#;Q2SZ3qdM3KeUn+OS}5 zmTu9#o*Xn&iDZlJ?6xb`oSAE>b71~OB65!vZ>t5NU7W;n&u2B=WODsC2h5aImL49< zq{gzN_QB&w)!;t8Byvc z8|T;PNGdy)%A}KH<71g&u^L7q)MP`fdqHSd;}SZkp(4SZ6Jt`pVfDNCfaR8Hu;sKL zlJ{uzp76_#OQBR2rU5gZNb-r=(6himHLY=zUf_g|=Qh@CWeVyGwJJcqNS8OkKN>wb z?HYrvc(;jUcu$1ya_+ZX<(n*G`5LNQ+;lP)WpIPr7uA0PMrT08zaizRF&K4yQ>2Ne z#6mrXEn0$xPJ_jAC4a4Nm3LdAHJv|~vtth1AKcCt=fovgEyb#}7O)17tRuVkJ5 zjjS{KyvsTrx7BM-uNI2>M9H_xyQo}tYF>RV_QhxCwWplvo!6e7*Pfl%o}Jg8o!6e7 z*Pfl%p3nNVXMO+jT)**D4L_GJJDcow-g7?d_ngY6_Y}S2tZQuhcC&$K_hPewI6K~H z&L73az0TC**8C<@k6ZIgOg(PxGs(uivv3kg=YQ$D2=dROTwFL@E0t`Yr(|19-!3~r zX#4TQNtrD9-m(0Qb(`mlamvhQxQ%>0voyDuRjY8jS1JW|7%xPRY)L5G(Xg3jY<;*j zWMZxvtDe}@;m4cdJZo(N9&4euafmdE&bCn6$Z>TrcbU@ODS0e*s|evXnd7Pv4=Hd5 zMXGx+v_jj=1C>VttfB)#x6_-B=uNfJn{KI>(6J!2LZ=|Qspr^gTQt!|1>)f3-UBz~ zclllre%Dtf(sFwW>mMjwar+Hsyb25L#tB@6czNjYYD+oQg=aNX{C`oM5ytEi`zX zkdOEVC!sD6+GzN8So2*?!3ni!(t|(dh-=M58>+rlp0Q|e;+D;#W0hSmpsd&_yPj_s z%`1m)o^gV}amzzh-wVBPu4)HY#$p>Bw@|4SZFB349a@!q5Ed)W;cW^(aAt~|>7T@r zZLwI5RG6+cV+U2MV4GZOA{N`c+1yL%`CMQc}TG?N`pkEgR#AUM(DxIw)- zbY>knfS)vyNgCaDb%g0@{?v#k`wtyzA?djDR57NfYk}>@D_+5Eh?8m>o`z}hDJ~V7 zj%!X&%b9=Z5IvpUAhcW_)vNeDXHHLx`W*Lbu>J+>qNw;Z3Q5z`@(9Abz_bEPaiFXL zLD9jPYK4LUHnc@SX&pqGPohK%m$}f(aSbPOW7?CG1xambyUm{Y%;m&u_ zJBKn(vglH>f^f?gs+Kf9o%EN!=T&tIzMT&J)~s~;RD4r?URD2JzppMj{k;9wy4wBD zch@`LUGIE%ef}zpr{x{34swY7%V{~vp__j$k8`xN{Ufi)oE=|XkCtL0k$e{An1!k_ z5xs3IGEr2!s!SAh<{tBi7G)L_*~JxRTE()tIMXVY%>^2-MJ(GFX`=U8tK%PpLN4!# x!Uk0~dU=|L^B~%p5d5!7_z%Cd|7(Y^M9aU&r<6q&Ehqn8o-WU?9{}KJR>8^@N67#H literal 0 HcmV?d00001 diff --git a/Shaders/qsb/wp_wipe.frag.qsb b/Shaders/qsb/wp_wipe.frag.qsb index e1977cede70717110957613f25d9f80aba5a19a1..478c27d43bcfa1ee9a77e6e4e95506c9ef77faed 100644 GIT binary patch literal 2677 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