From 8662ce0a86d08cb40e25c8345aeb259b81aae271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?= Date: Mon, 23 Aug 2021 01:05:41 +0200 Subject: [PATCH] HDR: Add SMAA as the default antialiasing technique --- Compositor/HDR/hdr.xml | 101 +++- Compositor/HDR/smaa-pass.xml | 11 + Effects/HDR/{display.eff => fxaa.eff} | 8 +- .../HDR/smaa-blending-weight-calculation.eff | 67 +++ Effects/HDR/smaa-edge-detection.eff | 17 + Effects/HDR/smaa-neighborhood-blending.eff | 22 + Shaders/HDR/{display.frag => fxaa.frag} | 36 +- Shaders/HDR/{display.vert => fxaa.vert} | 4 +- .../HDR/smaa-blending-weight-calculation.frag | 491 ++++++++++++++++++ .../HDR/smaa-blending-weight-calculation.vert | 70 +++ Shaders/HDR/smaa-edge-detection.frag | 98 ++++ Shaders/HDR/smaa-edge-detection.vert | 59 +++ Shaders/HDR/smaa-neighborhood-blending.frag | 101 ++++ Shaders/HDR/smaa-neighborhood-blending.vert | 57 ++ Textures/SMAA/area_tex.dds | Bin 0 -> 268928 bytes Textures/SMAA/search_tex.dds | Bin 0 -> 1152 bytes defaults.xml | 1 + 17 files changed, 1112 insertions(+), 31 deletions(-) create mode 100644 Compositor/HDR/smaa-pass.xml rename Effects/HDR/{display.eff => fxaa.eff} (57%) create mode 100644 Effects/HDR/smaa-blending-weight-calculation.eff create mode 100644 Effects/HDR/smaa-edge-detection.eff create mode 100644 Effects/HDR/smaa-neighborhood-blending.eff rename Shaders/HDR/{display.frag => fxaa.frag} (59%) rename Shaders/HDR/{display.vert => fxaa.vert} (84%) create mode 100644 Shaders/HDR/smaa-blending-weight-calculation.frag create mode 100644 Shaders/HDR/smaa-blending-weight-calculation.vert create mode 100644 Shaders/HDR/smaa-edge-detection.frag create mode 100644 Shaders/HDR/smaa-edge-detection.vert create mode 100644 Shaders/HDR/smaa-neighborhood-blending.frag create mode 100644 Shaders/HDR/smaa-neighborhood-blending.vert create mode 100644 Textures/SMAA/area_tex.dds create mode 100644 Textures/SMAA/search_tex.dds diff --git a/Compositor/HDR/hdr.xml b/Compositor/HDR/hdr.xml index df2eb8bbe..69ef3e307 100644 --- a/Compositor/HDR/hdr.xml +++ b/Compositor/HDR/hdr.xml @@ -234,6 +234,30 @@ 0.0625 + + + smaa-edges + 2d + screen + screen + rgba8 + clamp + clamp + linear-mipmap-linear + linear + + + smaa-blend + 2d + screen + screen + rgba8 + clamp + clamp + linear-mipmap-linear + linear + + + - display + no-aa-display quad - Effects/HDR/display + Effects/HDR/trivial + + + /sim/rendering/hdr/antialiasing-technique + 0 + + 0 final + + + fxaa + quad + Effects/HDR/fxaa + + + /sim/rendering/hdr/antialiasing-technique + 1 + + + + 0 + final + + + + + + smaa-edge-detection + Effects/HDR/smaa-edge-detection + color + 0.0 0.0 0.0 0.0 + + 0 + final + + + color0 + smaa-edges + + + + smaa-blending-weight-calculation + Effects/HDR/smaa-blending-weight-calculation + color + 0.0 0.0 0.0 0.0 + + 0 + smaa-edges + + + color0 + smaa-blend + + + + smaa-neighborhood-blending + Effects/HDR/smaa-neighborhood-blending + + 0 + final + + + 1 + smaa-blend + + + debug-albedo diff --git a/Compositor/HDR/smaa-pass.xml b/Compositor/HDR/smaa-pass.xml new file mode 100644 index 000000000..1193d8d17 --- /dev/null +++ b/Compositor/HDR/smaa-pass.xml @@ -0,0 +1,11 @@ + + + + quad + + + /sim/rendering/hdr/antialiasing-technique + 2 + + + diff --git a/Effects/HDR/display.eff b/Effects/HDR/fxaa.eff similarity index 57% rename from Effects/HDR/display.eff rename to Effects/HDR/fxaa.eff index 551650ec6..60f5711c0 100644 --- a/Effects/HDR/display.eff +++ b/Effects/HDR/fxaa.eff @@ -1,14 +1,14 @@ - Effects/HDR/display + Effects/HDR/fxaa - Shaders/HDR/display.vert - Shaders/HDR/display.frag + Shaders/HDR/fxaa.vert + Shaders/HDR/fxaa.frag - tex + color_tex sampler-2d 0 diff --git a/Effects/HDR/smaa-blending-weight-calculation.eff b/Effects/HDR/smaa-blending-weight-calculation.eff new file mode 100644 index 000000000..60d66c702 --- /dev/null +++ b/Effects/HDR/smaa-blending-weight-calculation.eff @@ -0,0 +1,67 @@ + + + Effects/HDR/smaa-blending-weight-calculation + + + Textures/SMAA/area_tex.dds + 2d + linear-mipmap-linear + linear + clamp + clamp + normalized + + + Textures/SMAA/search_tex.dds + 2d + linear-mipmap-linear + linear + clamp + clamp + normalized + + + + + + 1 + texture[1]/image + texture[1]/type + texture[1]/filter + texture[1]/mag-filter + texture[1]/wrap-s + texture[1]/wrap-t + texture[1]/internal-format + + + 2 + texture[2]/image + texture[2]/type + texture[2]/filter + texture[2]/mag-filter + texture[2]/wrap-s + texture[2]/wrap-t + texture[2]/internal-format + + + Shaders/HDR/smaa-blending-weight-calculation.vert + Shaders/HDR/smaa-blending-weight-calculation.frag + + + edges_tex + sampler-2d + 0 + + + area_tex + sampler-2d + 1 + + + search_tex + sampler-2d + 2 + + + + diff --git a/Effects/HDR/smaa-edge-detection.eff b/Effects/HDR/smaa-edge-detection.eff new file mode 100644 index 000000000..4397b1b9f --- /dev/null +++ b/Effects/HDR/smaa-edge-detection.eff @@ -0,0 +1,17 @@ + + + Effects/HDR/smaa-edge-detection + + + + Shaders/HDR/smaa-edge-detection.vert + Shaders/HDR/smaa-edge-detection.frag + + + color_tex + sampler-2d + 0 + + + + diff --git a/Effects/HDR/smaa-neighborhood-blending.eff b/Effects/HDR/smaa-neighborhood-blending.eff new file mode 100644 index 000000000..5e48b5dd8 --- /dev/null +++ b/Effects/HDR/smaa-neighborhood-blending.eff @@ -0,0 +1,22 @@ + + + Effects/HDR/smaa-neighborhood-blending + + + + Shaders/HDR/smaa-neighborhood-blending.vert + Shaders/HDR/smaa-neighborhood-blending.frag + + + color_tex + sampler-2d + 0 + + + blend_tex + sampler-2d + 1 + + + + diff --git a/Shaders/HDR/display.frag b/Shaders/HDR/fxaa.frag similarity index 59% rename from Shaders/HDR/display.frag rename to Shaders/HDR/fxaa.frag index 189b034c1..cb169765a 100644 --- a/Shaders/HDR/display.frag +++ b/Shaders/HDR/fxaa.frag @@ -5,23 +5,21 @@ out vec4 fragColor; in vec2 texCoord; in vec4 posPos; -uniform vec2 fg_BufferSize; - -uniform sampler2D tex; +uniform sampler2D color_tex; const float FXAA_SPAN_MAX = 8.0; const float FXAA_REDUCE_MUL = 1.0/8.0; const float FXAA_REDUCE_MIN = 1.0/128.0; -vec3 fxaa() +void main() { - vec2 rcpFrame = 1.0 / textureSize(tex, 0); + vec2 rcpFrame = 1.0 / textureSize(color_tex, 0); - vec3 rgbNW = textureLod(tex, posPos.zw, 0.0).xyz; - vec3 rgbNE = textureLodOffset(tex, posPos.zw, 0.0, ivec2(1,0)).xyz; - vec3 rgbSW = textureLodOffset(tex, posPos.zw, 0.0, ivec2(0,1)).xyz; - vec3 rgbSE = textureLodOffset(tex, posPos.zw, 0.0, ivec2(1,1)).xyz; - vec3 rgbM = textureLod(tex, posPos.xy, 0.0).xyz; + vec3 rgbNW = textureLod(color_tex, posPos.zw, 0.0).xyz; + vec3 rgbNE = textureLodOffset(color_tex, posPos.zw, 0.0, ivec2(1,0)).xyz; + vec3 rgbSW = textureLodOffset(color_tex, posPos.zw, 0.0, ivec2(0,1)).xyz; + vec3 rgbSE = textureLodOffset(color_tex, posPos.zw, 0.0, ivec2(1,1)).xyz; + vec3 rgbM = textureLod(color_tex, posPos.xy, 0.0).xyz; const vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); @@ -45,19 +43,15 @@ vec3 fxaa() dir * rcpDirMin)) * rcpFrame.xy; vec3 rgbA = 0.5 * ( - textureLod(tex, posPos.xy + dir * (1.0/3.0 - 0.5), 0.0).xyz + - textureLod(tex, posPos.xy + dir * (2.0/3.0 - 0.5), 0.0).xyz); + textureLod(color_tex, posPos.xy + dir * (1.0/3.0 - 0.5), 0.0).xyz + + textureLod(color_tex, posPos.xy + dir * (2.0/3.0 - 0.5), 0.0).xyz); vec3 rgbB = rgbA * 0.5 + 0.25 * ( - textureLod(tex, posPos.xy + dir * (0.0/3.0 - 0.5), 0.0).xyz + - textureLod(tex, posPos.xy + dir * (3.0/3.0 - 0.5), 0.0).xyz); + textureLod(color_tex, posPos.xy + dir * (0.0/3.0 - 0.5), 0.0).xyz + + textureLod(color_tex, posPos.xy + dir * (3.0/3.0 - 0.5), 0.0).xyz); float lumaB = dot(rgbB, luma); if((lumaB < lumaMin) || (lumaB > lumaMax)) - return rgbA; - return rgbB; -} - -void main() -{ - fragColor = vec4(fxaa(), 1.0); + fragColor = vec4(rgbA, 1.0); + else + fragColor = vec4(rgbB, 1.0); } diff --git a/Shaders/HDR/display.vert b/Shaders/HDR/fxaa.vert similarity index 84% rename from Shaders/HDR/display.vert rename to Shaders/HDR/fxaa.vert index 53aab4f63..8fa6357b8 100644 --- a/Shaders/HDR/display.vert +++ b/Shaders/HDR/fxaa.vert @@ -8,7 +8,7 @@ out vec4 posPos; uniform mat4 osg_ModelViewProjectionMatrix; -uniform sampler2D tex; +uniform sampler2D color_tex; const float FXAA_SUBPIX_SHIFT = 1.0/4.0; @@ -16,7 +16,7 @@ void main() { gl_Position = osg_ModelViewProjectionMatrix * pos; texCoord = multiTexCoord0.st; - vec2 rcpFrame = 1.0 / textureSize(tex, 0); + vec2 rcpFrame = 1.0 / textureSize(color_tex, 0); posPos.xy = multiTexCoord0.xy; posPos.zw = multiTexCoord0.xy - (rcpFrame * (0.5 + FXAA_SUBPIX_SHIFT)); } diff --git a/Shaders/HDR/smaa-blending-weight-calculation.frag b/Shaders/HDR/smaa-blending-weight-calculation.frag new file mode 100644 index 000000000..4a9ab020d --- /dev/null +++ b/Shaders/HDR/smaa-blending-weight-calculation.frag @@ -0,0 +1,491 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define SMAA_THRESHOLD 0.1 +#define SMAA_MAX_SEARCH_STEPS_DIAG 8 +#define SMAA_CORNER_ROUNDING 25 + +#define SMAA_AREATEX_MAX_DISTANCE 16 +#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20 +#define SMAA_AREATEX_PIXEL_SIZE (1.0 / vec2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE (1.0 / 7.0) +#define SMAA_SEARCHTEX_SIZE vec2(66.0, 33.0) +#define SMAA_SEARCHTEX_PACKED_SIZE vec2(64.0, 16.0) +#define SMAA_CORNER_ROUNDING_NORM (float(SMAA_CORNER_ROUNDING) / 100.0) + +#define SMAA_AREATEX_SELECT(sample) sample.rg +#define SMAA_SEARCHTEX_SELECT(sample) sample.r + +out vec4 fragColor; + +in vec2 texCoord; +in vec2 pixCoord; +in vec4 vOffset[3]; + +uniform sampler2D edges_tex; +uniform sampler2D area_tex; +uniform sampler2D search_tex; + +uniform vec4 fg_Viewport; + +#define SMAA_RT_METRICS vec4(1.0 / fg_Viewport.z, 1.0 / fg_Viewport.w, fg_Viewport.z, fg_Viewport.w) +#define mad(a, b, c) (a * b + c) +#define saturate(a) clamp(a, 0.0, 1.0) +#define round(v) floor(v + 0.5) +#define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) + +/** + * Conditional move: + */ +void SMAAMovc(bvec2 cond, inout vec2 variable, vec2 value) { + if (cond.x) variable.x = value.x; + if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bvec4 cond, inout vec4 variable, vec4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +vec2 SMAADecodeDiagBilinearAccess(vec2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +vec4 SMAADecodeDiagBilinearAccess(vec4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +vec2 SMAASearchDiag1(sampler2D edgesTex, vec2 texcoord, vec2 dir, out vec2 e) { + vec4 coord = vec4(texcoord, -1.0, 1.0); + vec3 t = vec3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, vec3(dir, 1.0), coord.xyz); + e = textureLod(edgesTex, coord.xy, 0.0).rg; + coord.w = dot(e, vec2(0.5, 0.5)); + } + return coord.zw; +} + +vec2 SMAASearchDiag2(sampler2D edgesTex, vec2 texcoord, vec2 dir, out vec2 e) { + vec4 coord = vec4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + vec3 t = vec3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, vec3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = textureLod(edgesTex, coord.xy, 0.0).rg; + e = SMAADecodeDiagBilinearAccess(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, vec2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +vec2 SMAAAreaDiag(sampler2D areaTex, vec2 dist, vec2 e, float offset) { + vec2 texcoord = mad(vec2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(textureLod(areaTex, texcoord, 0.0)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +vec2 SMAACalculateDiagWeights(sampler2D edgesTex, sampler2D areaTex, vec2 texcoord, vec2 e, vec4 subsampleIndices) { + vec2 weights = vec2(0.0, 0.0); + + // Search for the line ends: + vec4 d; + vec2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(edgesTex, texcoord, vec2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = vec2(0.0, 0.0); + d.yw = SMAASearchDiag1(edgesTex, texcoord, vec2(1.0, -1.0), end); + + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + vec4 coords = mad(vec4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + vec4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, ivec2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, ivec2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // vec4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // vec4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, ivec2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, ivec2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, ivec2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, ivec2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + vec2 cc = mad(vec2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bvec2(step(0.9, d.zw)), cc, vec2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(areaTex, d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(edgesTex, texcoord, vec2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, ivec2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(edgesTex, texcoord, vec2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = vec2(0.0, 0.0); + + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + vec4 coords = mad(vec4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + vec4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, ivec2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, ivec2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, ivec2( 1, 0)).gr; + vec2 cc = mad(vec2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bvec2(step(0.9, d.zw)), cc, vec2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(areaTex, d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(sampler2D searchTex, vec2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + vec2 scale = SMAA_SEARCHTEX_SIZE * vec2(0.5, -1.0); + vec2 bias = SMAA_SEARCHTEX_SIZE * vec2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += vec2(-1.0, 1.0); + bias += vec2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(textureLod(searchTex, mad(scale, e, bias), 0.0)); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + vec2 e = vec2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = textureLod(edgesTex, texcoord, 0.0).rg; + texcoord = mad(-vec2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(searchTex, e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end) { + vec2 e = vec2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = textureLod(edgesTex, texcoord, 0.0).rg; + texcoord = mad(vec2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(searchTex, e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end) { + vec2 e = vec2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = textureLod(edgesTex, texcoord, 0.0).rg; + texcoord = mad(-vec2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(searchTex, e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(sampler2D edgesTex, sampler2D searchTex, vec2 texcoord, float end) { + vec2 e = vec2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = textureLod(edgesTex, texcoord, 0.0).rg; + texcoord = mad(vec2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(searchTex, e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +vec2 SMAAArea(sampler2D area_tex, vec2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + vec2 texcoord = mad(vec2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * vec2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(textureLod(area_tex, texcoord, 0.0)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(sampler2D edgesTex, inout vec2 weights, vec4 texcoord, vec2 d) { +#if !defined(SMAA_DISABLE_CORNER_DETECTION) + vec2 leftRight = step(d.xy, d.yx); + vec2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + vec2 factor = vec2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, ivec2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, ivec2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, ivec2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, ivec2(1, -2)).r; + + weights *= saturate(factor); +#endif +} + +void SMAADetectVerticalCornerPattern(sampler2D edgesTex, inout vec2 weights, vec4 texcoord, vec2 d) { +#if !defined(SMAA_DISABLE_CORNER_DETECTION) + vec2 leftRight = step(d.xy, d.yx); + vec2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + vec2 factor = vec2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, ivec2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, ivec2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, ivec2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, ivec2(-2, 1)).g; + + weights *= saturate(factor); +#endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +void main() +{ + vec4 subsampleIndices = vec4(0.0); // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + + vec4 weights = vec4(0.0, 0.0, 0.0, 0.0); + + vec2 e = texture(edges_tex, texCoord).rg; + + if (e.g > 0.0) { // Edge at north +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(edges_tex, area_tex, texCoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 +#endif + vec2 d; + + // Find the distance to the left: + vec3 coords; + coords.x = SMAASearchXLeft(edges_tex, search_tex, vOffset[0].xy, vOffset[2].x); + coords.y = vOffset[1].y; // vOffset[1].y = texCoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = textureLod(edges_tex, coords.xy, 0.0).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(edges_tex, search_tex, vOffset[0].zw, vOffset[2].y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixCoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + vec2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edges_tex, coords.zy, ivec2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(area_tex, sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texCoord.y; + SMAADetectHorizontalCornerPattern(edges_tex, weights.rg, coords.xyzy, d); + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + } else + e.r = 0.0; // Skip vertical processing. +#endif + } + + if (e.r > 0.0) { // Edge at west + vec2 d; + + // Find the distance to the top: + vec3 coords; + coords.y = SMAASearchYUp(edges_tex, search_tex, vOffset[1].xy, vOffset[2].z); + coords.x = vOffset[0].x; // vOffset[1].x = texCoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = textureLod(edges_tex, coords.xy, 0.0).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(edges_tex, search_tex, vOffset[1].zw, vOffset[2].w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixCoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + vec2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edges_tex, coords.xz, ivec2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(area_tex, sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texCoord.x; + SMAADetectVerticalCornerPattern(edges_tex, weights.ba, coords.xyxz, d); + } + + fragColor = weights; +} diff --git a/Shaders/HDR/smaa-blending-weight-calculation.vert b/Shaders/HDR/smaa-blending-weight-calculation.vert new file mode 100644 index 000000000..5c098859a --- /dev/null +++ b/Shaders/HDR/smaa-blending-weight-calculation.vert @@ -0,0 +1,70 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define SMAA_MAX_SEARCH_STEPS 16 + +#define mad(a, b, c) (a * b + c) + +layout(location = 0) in vec4 pos; +layout(location = 3) in vec4 multiTexCoord0; + +out vec2 texCoord; +out vec2 pixCoord; +out vec4 vOffset[3]; + +uniform mat4 osg_ModelViewProjectionMatrix; + +uniform vec4 fg_Viewport; + +#define SMAA_RT_METRICS vec4(1.0 / fg_Viewport.z, 1.0 / fg_Viewport.w, fg_Viewport.z, fg_Viewport.w) + +void main() +{ + gl_Position = osg_ModelViewProjectionMatrix * pos; + texCoord = multiTexCoord0.st; + + pixCoord = texCoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + vOffset[0] = mad(SMAA_RT_METRICS.xyxy, vec4(-0.25, -0.125, 1.25, -0.125), texCoord.xyxy); + vOffset[1] = mad(SMAA_RT_METRICS.xyxy, vec4(-0.125,-0.25, -0.125, 1.25 ), texCoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + vOffset[2] = mad(SMAA_RT_METRICS.xxyy, + vec4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + vec4(vOffset[0].xz, vOffset[1].yw)); +} diff --git a/Shaders/HDR/smaa-edge-detection.frag b/Shaders/HDR/smaa-edge-detection.frag new file mode 100644 index 000000000..ddbfa2465 --- /dev/null +++ b/Shaders/HDR/smaa-edge-detection.frag @@ -0,0 +1,98 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define SMAA_THRESHOLD 0.1 +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 + +out vec4 fragColor; + +in vec2 texCoord; +in vec4 vOffset[3]; + +uniform sampler2D color_tex; + +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +void main() +{ + vec2 threshold = vec2(SMAA_THRESHOLD); + + // Calculate lumas: + vec3 weights = vec3(0.2126, 0.7152, 0.0722); + float L = dot(texture(color_tex, texCoord).rgb, weights); + + float Lleft = dot(texture(color_tex, vOffset[0].xy).rgb, weights); + float Ltop = dot(texture(color_tex, vOffset[0].zw).rgb, weights); + + // We do the usual threshold: + vec4 delta; + delta.xy = abs(L - vec2(Lleft, Ltop)); + vec2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, vec2(1.0, 1.0)) == 0.0) + discard; + + // Calculate right and bottom deltas: + float Lright = dot(texture(color_tex, vOffset[1].xy).rgb, weights); + float Lbottom = dot(texture(color_tex, vOffset[1].zw).rgb, weights); + delta.zw = abs(L - vec2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + vec2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(texture(color_tex, vOffset[2].xy).rgb, weights); + float Ltoptop = dot(texture(color_tex, vOffset[2].zw).rgb, weights); + delta.zw = abs(vec2(Lleft, Ltop) - vec2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + fragColor = vec4(edges, 0.0, 1.0); +} diff --git a/Shaders/HDR/smaa-edge-detection.vert b/Shaders/HDR/smaa-edge-detection.vert new file mode 100644 index 000000000..745511c95 --- /dev/null +++ b/Shaders/HDR/smaa-edge-detection.vert @@ -0,0 +1,59 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define mad(a, b, c) (a * b + c) + +layout(location = 0) in vec4 pos; +layout(location = 3) in vec4 multiTexCoord0; + +out vec2 texCoord; +out vec4 vOffset[3]; + +uniform mat4 osg_ModelViewProjectionMatrix; +uniform vec4 fg_Viewport; + +#define SMAA_RT_METRICS vec4(1.0 / fg_Viewport.z, 1.0 / fg_Viewport.w, fg_Viewport.z, fg_Viewport.w) + +void main() +{ + gl_Position = osg_ModelViewProjectionMatrix * pos; + texCoord = multiTexCoord0.st; + + vOffset[0] = mad(SMAA_RT_METRICS.xyxy, vec4(-1.0, 0.0, 0.0, -1.0), texCoord.xyxy); + vOffset[1] = mad(SMAA_RT_METRICS.xyxy, vec4( 1.0, 0.0, 0.0, 1.0), texCoord.xyxy); + vOffset[2] = mad(SMAA_RT_METRICS.xyxy, vec4(-2.0, 0.0, 0.0, -2.0), texCoord.xyxy); +} diff --git a/Shaders/HDR/smaa-neighborhood-blending.frag b/Shaders/HDR/smaa-neighborhood-blending.frag new file mode 100644 index 000000000..2ffc0eccc --- /dev/null +++ b/Shaders/HDR/smaa-neighborhood-blending.frag @@ -0,0 +1,101 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define mad(a, b, c) (a * b + c) + +out vec4 fragColor; + +in vec2 texCoord; +in vec4 vOffset; + +uniform sampler2D color_tex; +uniform sampler2D blend_tex; + +uniform vec4 fg_Viewport; + +#define SMAA_RT_METRICS vec4(1.0 / fg_Viewport.z, 1.0 / fg_Viewport.w, fg_Viewport.z, fg_Viewport.w) + +/** + * Conditional move: + */ +void SMAAMovc(bvec2 cond, inout vec2 variable, vec2 value) { + if (cond.x) variable.x = value.x; + if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bvec4 cond, inout vec4 variable, vec4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +void main() +{ + vec4 color; + + // Fetch the blending weights for current pixel: + vec4 a; + a.x = texture(blend_tex, vOffset.xy).a; // Right + a.y = texture(blend_tex, vOffset.zw).g; // Top + a.wz = texture(blend_tex, texCoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + if (dot(a, vec4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + color = textureLod(color_tex, texCoord, 0.0); + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + vec4 blendingOffset = vec4(0.0, a.y, 0.0, a.w); + vec2 blendingWeight = a.yw; + SMAAMovc(bvec4(h, h, h, h), blendingOffset, vec4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(bvec2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, vec2(1.0, 1.0)); + + // Calculate the texture coordinates: + vec4 blendingCoord = mad(blendingOffset, vec4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texCoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + color = blendingWeight.x * textureLod(color_tex, blendingCoord.xy, 0.0); + color += blendingWeight.y * textureLod(color_tex, blendingCoord.zw, 0.0); + } + + fragColor = color; +} diff --git a/Shaders/HDR/smaa-neighborhood-blending.vert b/Shaders/HDR/smaa-neighborhood-blending.vert new file mode 100644 index 000000000..75b334023 --- /dev/null +++ b/Shaders/HDR/smaa-neighborhood-blending.vert @@ -0,0 +1,57 @@ +/** + * Adaptation of SMAA (Enhanced Subpixel Morphological Antialiasing) + * for FlightGear. + * See http://www.iryoku.com/smaa/ for details. + * Licensed under the MIT license, see below. + */ + +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#version 330 core + +#define mad(a, b, c) (a * b + c) + +layout(location = 0) in vec4 pos; +layout(location = 3) in vec4 multiTexCoord0; + +out vec2 texCoord; +out vec4 vOffset; + +uniform mat4 osg_ModelViewProjectionMatrix; +uniform vec4 fg_Viewport; + +#define SMAA_RT_METRICS vec4(1.0 / fg_Viewport.z, 1.0 / fg_Viewport.w, fg_Viewport.z, fg_Viewport.w) + +void main() +{ + gl_Position = osg_ModelViewProjectionMatrix * pos; + texCoord = multiTexCoord0.st; + + vOffset = mad(SMAA_RT_METRICS.xyxy, vec4(1.0, 0.0, 0.0, 1.0), texCoord.xyxy); +} diff --git a/Textures/SMAA/area_tex.dds b/Textures/SMAA/area_tex.dds new file mode 100644 index 0000000000000000000000000000000000000000..7a3b1f8d1c9b2b59fa71211faa3622d3b9dcc314 GIT binary patch literal 268928 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 literal 0 HcmV?d00001 diff --git a/Textures/SMAA/search_tex.dds b/Textures/SMAA/search_tex.dds new file mode 100644 index 0000000000000000000000000000000000000000..f48ec89dda75f9e6f61a3265c5b75fa6ee75639e GIT binary patch literal 1152 zcmZ>930A0KU|?Vu;9w8{(hfk(zycyj2MS;0|-ETY=Bbz*vuqFj8gk4)lZ6BX`vQ5s^I1$3qm6fSNtI>B}YG8519-~ N?Wa^fna&0.0 1.0 6.0 + 2 true