/*------------------. | :: Description :: | '-------------------/ Lens Distortion PS (version 1.4.1) Copyright: This code © 2022-2023 Jakub Maksymilian Fober License: This work is licensed under the Creative Commons Attribution-NonCommercial- NoDerivs 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/3.0/ Additional permissions under Creative Commons Plus (CC+): § 1. The copyright owner further grants permission for commercial reuse of image recordings based on the work (e.g., Let's Play videos, gameplay streams, and screenshots featuring ReShade filters). Any such use must include credit to the creator and the name of the used shader. Intent §: To facilitate non-corporate, common use of the shader at no cost. Outcome §: That recognition of the work in any derivative images is ensured. § 2. Additionally, permission is granted for the translation of the front-end UI text within this shader. Intent §: To increase accessibility and understanding across different languages. Outcome §: That usability across users from diverse linguistic backgrounds is promoted, allowing them to fully engage with the shader. Contact: If you want additional licensing for your commercial product, please contact me: jakub.m.fober@protonmail.com ██████████▀▀▀▀▀ ▄▄▄▄▄▄▄ ▀▀▀▀▀███████████ ██▀▀▀ █████▀▀▀▀▀▀▀█████ ▀▀▀██ ▀ ███ ███ ▀ ██ ██ ██ ██ ██ ██ ▄ ███ ███ ▄ ██▄▄▄ █████▄▄▄▄▄▄▄█████ ▄▄▄██ ██████████▄▄▄▄▄ ▀▀▀▀▀▀▀ ▄▄▄▄▄███████████ P A N T O M O R P H I C For updates visit GitHub repository at https://github.com/Fubaxiusz/fubax-shaders. About: This shader version is based upon following research article: Temporally-smooth Antialiasing and Lens Distortion with Rasterization Map arXiv:2010.04077 [cs.GR] (2020) https://arxiv.org/abs/2010.04077 and Pantomorphic Perspective for Immersive Imagery arXiv:2102.12682 [cs.GR] (2021) https://arxiv.org/abs/2102.12682 by Fober, J. M. */ /*-------------. | :: Macros :: | '-------------*/ // Alternative anamorphic mode #ifndef PATNOMORPHIC_LENS_MODE #define PATNOMORPHIC_LENS_MODE 0 #endif // Parallax aberration mode #ifndef PARALLAX_ABERRATION #define PARALLAX_ABERRATION 1 #endif // Maximum number of samples for chromatic aberration #define CHROMATIC_ABERRATION_MAX_SAMPLES 64u #if PARALLAX_ABERRATION // Maximum number of samples for parallax aberration #define PARRALLAX_ABERRATION_MAX_SAMPLES 255u #endif /*--------------. | :: Commons :: | '--------------*/ #include "ReShade.fxh" #include "ReShadeUI.fxh" #include "ColorConversion.fxh" #include "LinearGammaWorkflow.fxh" #include "BlueNoiseDither.fxh" /*-----------. | :: Menu :: | '-----------*/ uniform bool ShowGrid < __UNIFORM_INPUT_BOOL1 ui_label = "Display calibration grid"; ui_tooltip = "This can be used in conjunction with Image.fx\n" "to display real-world camera lens image and\n" "match its distortion profile."; > = false; // :: Main distortion :: // #if PATNOMORPHIC_LENS_MODE==0 uniform float4 K < __UNIFORM_DRAG_FLOAT4 ui_min = -0.2; ui_max = 0.2; ui_label = "Radial distortion"; ui_tooltip = "Radial distortion coefficients K1, K2, K3, K4."; ui_category = "Geometrical lens distortions"; > = 0f; uniform float S < __UNIFORM_SLIDER_FLOAT1 ui_min = 1f; ui_max = 2f; ui_step = 0.05; ui_label = "Anamorphic"; ui_tooltip = "Anamorphic squeeze factor S,\n" "affects vertical axis:\n" "\n" "1 spherical lens\n" "1.25 Ultra Panavision 70\n" "1.33 16x9 TV\n" "1.5 Technirama\n" "1.6 digital anamorphic\n" "1.8 4x3 full-frame\n" "2 golden-standard"; ui_category = "Geometrical lens distortions"; > = 1f; #else uniform float4 Ky < __UNIFORM_DRAG_FLOAT4 ui_min = -0.2; ui_max = 0.2; ui_label = "Radial distortion - vertical"; ui_tooltip = "Radial distortion coefficients K1, K2, K3, K4\n" "for vertical distortion."; ui_category = "Geometrical lens distortions"; > = 0f; uniform float4 Kx < __UNIFORM_DRAG_FLOAT4 ui_min = -0.2; ui_max = 0.2; ui_label = "Radial distortion - horizontal"; ui_tooltip = "Radial distortion coefficients K1, K2, K3, K4\n" "for horizontal distortion."; ui_category = "Geometrical lens distortions"; > = 0f; #endif // :: Color :: // uniform bool UseVignette < __UNIFORM_DRAG_FLOAT2 ui_label = "Brightness aberration"; ui_tooltip = "Automatically change image brightness based on projection area."; ui_category = "Color aberrations"; > = true; uniform float T < __UNIFORM_DRAG_FLOAT1 ui_min = -0.2; ui_max = 0.2; ui_label = "Chromatic radius"; ui_tooltip = "Color separation amount using T."; ui_category = "Color aberrations"; > = -0.2; // :: Miss-alignment :: // uniform float2 P < __UNIFORM_DRAG_FLOAT2 ui_min = -0.1; ui_max = 0.1; ui_label = "Decentering"; ui_tooltip = "Correct image sensor alignment to the optical axis, using P1, P2."; ui_category = "Elements misalignment"; > = 0f; uniform float2 Q < __UNIFORM_DRAG_FLOAT2 ui_min = -0.05; ui_max = 0.05; ui_label = "Thin prism"; ui_tooltip = "Correct optical elements offset from the optical axis, using Q1, Q2."; ui_category = "Elements misalignment"; > = 0f; uniform float2 C < __UNIFORM_DRAG_FLOAT2 ui_min = -0.05; ui_max = 0.05; ui_label = "Center"; ui_tooltip = "Offset lens optical center, to correct image cropping, using C1, C2."; ui_category = "Elements misalignment"; > = 0f; #if PARALLAX_ABERRATION // :: Parallax :: // uniform float4 Kp < __UNIFORM_DRAG_FLOAT4 ui_min = -0.2; ui_max = 0f; ui_label = "Radial parallax"; ui_tooltip = "Parallax aberration radial coefficients K1, K2, K3, K4.\n" "Requires depth-buffer access."; ui_category = "Parallax aberration"; > = 0f; #endif // :: Border :: // uniform bool MirrorBorder < __UNIFORM_INPUT_BOOL1 ui_label = "Mirror on border"; ui_tooltip = "Choose between mirrored image or original background on the border."; ui_category = "Border"; ui_category_closed = true; > = true; uniform bool BorderVignette < __UNIFORM_INPUT_BOOL1 ui_label = "Brightness aberration on border"; ui_tooltip = "Apply brightness aberration effect to the border."; ui_category = "Border"; > = true; uniform float4 BorderColor < __UNIFORM_COLOR_FLOAT4 ui_label = "Border color"; ui_tooltip = "Use alpha to change border transparency."; ui_category = "Border"; > = float4(0.027, 0.027, 0.027, 0.96); uniform float BorderCorner < __UNIFORM_SLIDER_FLOAT1 ui_min = 0f; ui_max = 1f; ui_label = "Corner radius"; ui_tooltip = "Value of 0.0 gives sharp corners."; ui_category = "Border"; > = 0.062; uniform uint BorderGContinuity < __UNIFORM_SLIDER_INT1 ui_min = 1u; ui_max = 3u; ui_units = "G"; ui_label = "Corner roundness"; ui_tooltip = "G-surfacing continuity level for the corners:\n" "\n" "G0 sharp\n" "G1 circular\n" "G2 smooth\n" "G3 very smooth"; ui_category = "Border"; > = 3u; // :: Grid :: // uniform float DimGridBackground < __UNIFORM_SLIDER_FLOAT1 ui_min = 0.25; ui_max = 1f; ui_step = 0.1; ui_label = "Dim background"; ui_tooltip = "Adjust background visibility."; ui_category = "Grid"; ui_category_closed = true; ui_text = "Use this in conjunction with Image.fx, to match\n" "lens distortion with a real-world camera profile."; > = 1f; uniform uint GridLook < __UNIFORM_COMBO_INT1 ui_items = "yellow grid\0" "black grid\0" "white grid\0" "red-green grid\0"; ui_label = "Grid look"; ui_tooltip = "Select look of the grid."; ui_category = "Grid"; > = 0u; uniform uint GridSize < __UNIFORM_SLIDER_INT1 ui_min = 1u; ui_max = 32u; ui_label = "Grid size"; ui_tooltip = "Adjust calibration grid size."; ui_category = "Grid"; > = 16u; uniform uint GridWidth < __UNIFORM_SLIDER_INT1 ui_min = 2u; ui_max = 16u; ui_units = " pixels"; ui_label = "Grid bar width"; ui_tooltip = "Adjust calibration grid bar width in pixels."; ui_category = "Grid"; > = 2u; uniform float GridTilt < __UNIFORM_SLIDER_FLOAT1 ui_min = -1f; ui_max = 1f; ui_step = 0.01; ui_units = "°"; ui_label = "Tilt grid"; ui_tooltip = "Adjust calibration grid tilt in degrees."; ui_category = "Grid"; > = 0f; // :: Performance :: // uniform uint ChromaticSamplesLimit < __UNIFORM_SLIDER_INT1 ui_min = 6u; ui_max = CHROMATIC_ABERRATION_MAX_SAMPLES; ui_step = 2u; ui_label = "Chromatic aberration samples limit"; ui_tooltip = "Sample count is generated automatically per pixel, based on visible amount.\n" "This option limits maximum sample (steps) count allowed for color fringing.\n" "Only even numbers are accepted, odd numbers will be clamped."; ui_category = "Performance"; > = 32u; #if PARALLAX_ABERRATION uniform uint ParallaxSamples < __UNIFORM_SLIDER_INT1 ui_min = 2u; ui_max = 64u; ui_label = "Parallax aberration samples"; ui_tooltip = "Amount of samples (steps) for parallax aberration mapping."; ui_category = "Performance"; > = 32u; #endif /*---------------. | :: Textures :: | '---------------*/ // Define screen texture with mirror tiles sampler BackBuffer { Texture = ReShade::BackBufferTex; // Border style AddressU = MIRROR; AddressV = MIRROR; }; /*----------------. | :: Functions :: | '----------------*/ // Get reciprocal screen aspect ratio (1/x) #define BUFFER_RCP_ASPECT_RATIO (BUFFER_HEIGHT*BUFFER_RCP_WIDTH) /* G continuity distance function by Jakub Max Fober. Determined empirically. (G from 0, to 3) G=0 Sharp corners G=1 Round corners G=2 Smooth corners G=3 Luxury corners */ float glength(uint G, float2 pos) { // Sharp corner if (G==0u) return max(abs(pos.x), abs(pos.y)); // G0 // Higher-power length function pos = pow(abs(pos), ++G); // Power of G+1 return pow(pos.x+pos.y, rcp(G)); // Power G+1 root } /* Linear pixel step function for anti-aliasing by Jakub Max Fober. This algorithm is part of scientific paper: · arXiv:2010.04077 [cs.GR] (2020) */ float aastep(float grad) { // Differential vector float2 Del = float2(ddx(grad), ddy(grad)); // Gradient normalization to pixel size, centered at the step edge return saturate(rsqrt(dot(Del, Del))*grad+0.5); // half-pixel offset } /* Chromatic aberration hue color generator by Fober J. M. hue = index/samples; where index ∊ [0, samples-1] and samples is an even number */ float3 Spectrum(float hue) { float3 hueColor; hue *= 4f; // Slope hueColor.rg = hue-float2(1f, 2f); hueColor.rg = saturate(1.5-abs(hueColor.rg)); hueColor.r += saturate(hue-3.5); hueColor.b = 1f-hueColor.r; return hueColor; } // Border mask shader with rounded corners float GetBorderMask(float2 borderCoord) { // Get coordinates for each corner borderCoord = abs(borderCoord); if (BorderGContinuity!=0u && BorderCorner!=0f) // If round corners { // Correct corner aspect ratio if (BUFFER_ASPECT_RATIO>1f) // If in landscape mode borderCoord.x = borderCoord.x*BUFFER_ASPECT_RATIO+(1f-BUFFER_ASPECT_RATIO); else if (BUFFER_ASPECT_RATIO<1f) // If in portrait mode borderCoord.y = borderCoord.y*BUFFER_RCP_ASPECT_RATIO+(1f-BUFFER_RCP_ASPECT_RATIO); // Generate scaled coordinates borderCoord = max(borderCoord+(BorderCorner-1f), 0f)/BorderCorner; // Round corner return aastep(glength(BorderGContinuity, borderCoord)-1f); // with G1 to G3 continuity } else // just sharp corner, G0 return aastep(glength(0u, borderCoord)-1f); } /*--------------. | :: Shaders :: | '--------------*/ // Vertex shader generating a triangle covering the entire screen void LensDistortVS( in uint id : SV_VertexID, out float4 position : SV_Position, out float2 viewCoord : TEXCOORD ) { // Define vertex position const float2 vertexPos[3] = { float2(-1f, 1f), // Top left float2(-1f,-3f), // Bottom left float2( 3f, 1f) // Top right }; // Export screen centered texture coordinates viewCoord.x = vertexPos[id].x; viewCoord.y = -vertexPos[id].y; // Correct aspect ratio, normalized to the corner viewCoord *= normalize(BUFFER_SCREEN_SIZE); // Export vertex position position = float4(vertexPos[id], 0f, 1f); } #if PARALLAX_ABERRATION // Parallax aberration pixel shader void ParallaxPS( float4 pixelPos : SV_Position, float2 viewCoord : TEXCOORD, out float3 color : SV_Target ) { if (all(Kp==0f)) // bypass parallax aberration { color = tex2Dfetch(BackBuffer, uint2(pixelPos.xy)).rgb; return; } // Get aspect-ratio transform vector for the view coordinates const float2 aspectScalar = 0.5/normalize(BUFFER_SCREEN_SIZE); // Transform view coordinates to texture coordinates float2 texCoord = viewCoord*aspectScalar+0.5; // Get radius at increasing even powers float4 R; R[0] = dot(viewCoord, viewCoord); // r² R[1] = R[0]*R[0]; // r⁴ R[2] = R[1]*R[0]; // r⁶ R[3] = R[2]*R[0]; // r⁸ // Parallax aberration amount float2 centerCoord = texCoord-0.5; centerCoord *= rcp(1f+dot(Kp, R))-1f; // Get maximum allowed number of steps uint maxStepAmount = clamp(ParallaxSamples, 2u, PARRALLAX_ABERRATION_MAX_SAMPLES); // Initialize float offset; // Found offset value float stepSize = rcp(maxStepAmount-1u); for (int counter = maxStepAmount-1u; counter >= 0; counter--) { offset = counter*stepSize; float reverseDepth = 1f-ReShade::GetLinearizedDepth(texCoord-centerCoord*offset); if (offset <= reverseDepth) { float prevOffset = (counter+3u)*stepSize; float prevDifference = prevOffset-1f+ReShade::GetLinearizedDepth(texCoord-centerCoord*prevOffset); // Interpolate offset offset = lerp(prevOffset, offset, prevDifference/(prevDifference+reverseDepth-offset)); break; } } // Apply parallax offset texCoord -= centerCoord*offset; color = tex2D(BackBuffer, texCoord).rgb; } #endif // Lens distortion pixel shader void LensDistortPS( float4 pixelPos : SV_Position, float2 viewCoord : TEXCOORD, out float3 color : SV_Target ) { // Bypass all effects #if PATNOMORPHIC_LENS_MODE==0 if (!ShowGrid && all(K==0f) && all(P==0f) && all(Q==0f)) #else if (!ShowGrid && all(Kx==0f) && all(Ky==0f) && all(P==0f) && all(Q==0f)) #endif { color = tex2Dfetch(BackBuffer, uint2(pixelPos.xy)).rgb; return; } // Get aspect-ratio transform vector for the view coordinates const float2 aspectScalar = 0.5/normalize(BUFFER_SCREEN_SIZE); #if PATNOMORPHIC_LENS_MODE==0 bool isDistorted = any(K!=0f) || any(P!=0f) || any(Q!=0f); #else bool isDistorted = any(Kx!=0f) || any(Ky!=0f) || any(P!=0f) || any(Q!=0f); #endif if (isDistorted) // distort coordinates { viewCoord -= C; // Cardinal offset (a) #if PATNOMORPHIC_LENS_MODE==0 // Get anamorphic coordinates float2 anamViewCoord = viewCoord; anamViewCoord.y /= S; // Get radius at increasing even powers float4 anamorphR; anamorphR[0] = dot(anamViewCoord, anamViewCoord); // Anamorphic r² anamorphR[1] = anamorphR[0]*anamorphR[0]; // Anamorphic r⁴ anamorphR[2] = anamorphR[1]*anamorphR[0]; // Anamorphic r⁶ anamorphR[3] = anamorphR[2]*anamorphR[0]; // Anamorphic r⁸ float R2 = dot(viewCoord, viewCoord); // r² // Primary distortion viewCoord *= rcp(1f+dot(K, anamorphR)) // Radial distortion + dot(viewCoord, P); // Decentering // Secondary distortion viewCoord += R2*Q // Thin prism + C; // Cardinal offset (b) #else // Get radius at increasing even powers float4 R; R[0] = dot(viewCoord, viewCoord); // r² R[1] = R[0]*R[0]; // r⁴ R[2] = R[1]*R[0]; // r⁶ R[3] = R[2]*R[0]; // r⁸ // Get pantomorphic interpolation weight float2 phiWeight = viewCoord*viewCoord/R[0]; // Pantomorphic distortion viewCoord *= rcp(1f+dot(Kx, R))*phiWeight.x // Horizontal radial distortion + rcp(1f+dot(Ky, R))*phiWeight.y // Vertical radial distortion + dot(viewCoord, P); // Decentering // Secondary distortion viewCoord += R[0]*Q // Thin prism + C; // Cardinal offset (b) #endif } // Transform view coordinates to texture coordinates float2 texCoord = viewCoord*aspectScalar+0.5; if (isDistorted && T!=0f && !ShowGrid) // generate chromatic aberration { // Get unaltered texture coordinates float2 orygTexCoord = (pixelPos.xy+0.5)*BUFFER_PIXEL_SIZE; // Get distortion offset vector float2 distortion = texCoord-orygTexCoord; // Get maximum number of samples allowed uint evenSampleCount = min(ChromaticSamplesLimit-ChromaticSamplesLimit%2u, CHROMATIC_ABERRATION_MAX_SAMPLES); // Clamp value // Get total offset in pixels for automatic sample amount uint totalPixelOffset = uint(ceil(length(T*(distortion*BUFFER_SCREEN_SIZE)))); // Set dynamic even number sample count, limited in range evenSampleCount = clamp(totalPixelOffset+totalPixelOffset%2u, 4u, evenSampleCount); // Sample background with multiple color filters at multiple offsets color = 0f; // initialize color for (uint i=0u; i { #if PARALLAX_ABERRATION pass Parallax { VertexShader = LensDistortVS; PixelShader = ParallaxPS; } #endif pass Distort { VertexShader = LensDistortVS; PixelShader = LensDistortPS; } }