glsl-smaa
SMAA (Enhanced Subpixel Morphological Antialiasing) post-processing; WebGL (OpenGL ES 2.0) implementation with no fluff. All credit goes to the incredible work here (iryoku) and there (beakbeak).
Looking for WebGPU? See wgsl-smaa, which ships the same passes in WGSL.
Installation
npm install glsl-smaa
Usage
Three passes are required to apply the effect:
|input|------------------·
v |
[ SMAA*EdgeDetection ] |
v |
|edgesTex| |
v |
[ SMAABlendingWeightCalculation ] |
v |
|blendTex| |
v |
[ SMAANeighborhoodBlending ] <------·
v
|output|
You would typically set this up as part of a post-processing chain with framebuffers. The edges and weights targets must be cleared to [0, 0, 0, 0] every frame, and sampled with a linear filter.
See the example and its source for a setup with pex-context.
import {
SMAATextures,
PRESETS,
SMAA_EDGES_VERT,
SMAA_EDGES_FRAG,
} from "glsl-smaa";
const frag = /* glsl */ `#define SMAA_PRESET_HIGH
#define SMAA_EDGES_LUMA
${PRESETS}
${SMAA_EDGES_FRAG}`;
Each shader also ships as a plain file (eg. glsl-smaa/smaa-edges.frag) for bundlers that load shaders as raw text.
Lookup textures
SMAATextures.area and SMAATextures.search are the reference's precomputed textures as PNG data URIs. Upload them as is (no flipY), with linear filtering and no mipmaps.
Before 4.0.0, the search texture was stored flipped and had to be uploaded with
flipY: true. It now matches the reference data: remove the flip.
Attributes
Attributes required for all vertex shaders:
| Name | Type | Description |
|---|---|---|
| aPosition | vec2 | Screen vertices (eg. a fullscreen triangle/quad) |
Uniforms and defines
#define can be prepended to the shaders to override default settings. Typically:
const frag = /* glsl */ `#define SMAA_PRESET_HIGH
${PRESETS}
${SMAA_WEIGHTS_FRAG}
`;
smaa-edges.(vert|frag)
You can use one of the 3 following edge detection method:
Depth: the fastest but it may miss some edges.
Luma: more expensive than depth edge detection, but catches visible edges that depth edge detection can miss.
Color: the most expensive one but catches chroma-only edges.

Defines:
# One of:
#define SMAA_EDGES_DEPTH
#define SMAA_EDGES_LUMA
#define SMAA_EDGES_COLOR
#define SMAA_THRESHOLD 0.1
#define SMAA_DEPTH_THRESHOLD (0.1 * SMAA_THRESHOLD)
#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0
#define SMAA_PREDICATION 0
#define SMAA_PREDICATION_THRESHOLD 0.01
#define SMAA_PREDICATION_SCALE 2.0
#define SMAA_PREDICATION_STRENGTH 0.4
# Optional:
#define SMAA_SRGB_INPUT
| Name | Default [Range] | Description |
|---|---|---|
| SMAA_THRESHOLD | 0.1 [0, 0.5] | Specifies the threshold or sensitivity to edges. Lowering this value you will be able to detect more edges at the expense of performance. 0.1 is a reasonable value, and allows to catch most visible edges. 0.05 is a rather overkill value, that allows to catch 'em all. |
| SMAA_DEPTH_THRESHOLD | 0.1 * SMAA_THRESHOLD | Specific threshold for depth edge detection. |
| SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR | 2.0 | If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times bigger contrast than current edge, current edge will be discarded. This allows to eliminate spurious crossing edges, and is based on the fact that, if there is too much contrast in a direction, that will hide perceptually contrast in the other neighbors. |
| SMAA_PREDICATION | 0 | Locally decreases the luma or color threshold if an edge is found in the depth buffer (so the global threshold can be higher). |
| SMAA_PREDICATION_THRESHOLD | 0.01 | Threshold used in the depth buffer. Depends on its content. |
| SMAA_PREDICATION_SCALE | 2.0 [1, 5] | How much to scale the global threshold used for luma or color edge detection when using predication. |
| SMAA_PREDICATION_STRENGTH | 0.4 [0, 1] | How much to locally decrease the threshold. |
| SMAA_SRGB_INPUT | undefined | Luma and color edge detection expect gamma-encoded input. Define it when sampling an sRGB texture, which decodes to linear, to re-encode the color before thresholding. |
Uniforms:
| Name | Type | Description |
|---|---|---|
| uTexelSize | vec2 | 1 / framebuffer size. |
| uColorTexture | sampler2D | The input color framebuffer. Luma and color edge detection only. |
| uDepthTexture | sampler2D | The input depth framebuffer. Depth edge detection and predication. |
smaa-weights.(vert|frag)
Defines from presets.glsl:
# Use presets:
#define SMAA_PRESET_LOW
#define SMAA_PRESET_MEDIUM
#define SMAA_PRESET_HIGH
#define SMAA_PRESET_ULTRA
# or individually setting:
#define SMAA_THRESHOLD 0.1
#define SMAA_MAX_SEARCH_STEPS 16
#define SMAA_MAX_SEARCH_STEPS_DIAG 8
#define SMAA_CORNER_ROUNDING 25
# and optionally disable diagonal and corner detection:
#define SMAA_DISABLE_DIAG_DETECTION
#define SMAA_DISABLE_CORNER_DETECTION
| Name | Default [Range] | Description |
|---|---|---|
| SMAA_THRESHOLD | 0.1 [0, 0.5] | Should be the same as for the edge pass. |
| SMAA_MAX_SEARCH_STEPS | 16 [0, 112] | Specifies the maximum steps performed in the horizontal/vertical pattern searches, at each side of the pixel. In number of pixels, it's actually the double. So the maximum line length perfectly handled by, for example 16, is 64 (by perfectly, we meant that longer lines won't look as good, but still antialiased. |
| SMAA_MAX_SEARCH_STEPS_DIAG | 8 [0, 20] | Specifies the maximum steps performed in the diagonal pattern searches, at each side of the pixel. In this case we jump one pixel at time, instead of two. |
| SMAA_CORNER_ROUNDING | 25 [0, 100] | Specifies how much sharp corners will be rounded. |
Uniforms:
| Name | Type | Description |
|---|---|---|
| uTexelSize | vec2 | 1 / framebuffer size. |
| uViewportSize | vec2 | The framebuffer size. |
| uSubsampleIndices | vec4 | [0, 0, 0, 0] for SMAA 1x. See Temporal supersampling. |
| uEdgesTexture | sampler2D | The framebuffer with edges. |
| uAreaTexture | sampler2D | SMAATextures.area. |
| uSearchTexture | sampler2D | SMAATextures.search. |
smaa-blend.(vert|frag)
Defines:
#define SMAA_REPROJECTION 0
#define SMAA_DECODE_VELOCITY(sample) sample.rg
| Name | Default | Description |
|---|---|---|
| SMAA_REPROJECTION | 0 | Packs the antialiased velocity into the output alpha, for the temporal resolve. |
| SMAA_DECODE_VELOCITY | sample.rg | Decodes the velocity texture. Velocity is in texture coordinates, from previous to current frame. |
Uniforms:
| Name | Type | Description |
|---|---|---|
| uTexelSize | vec2 | 1 / framebuffer size. |
| uColorTexture | sampler2D | The input color framebuffer. |
| uBlendTexture | sampler2D | The framebuffer with weights. |
| uVelocityTexture | sampler2D | The velocity framebuffer. SMAA_REPROJECTION only. |
smaa-resolve.(vert|frag)
Optional temporal resolve pass, blending the current and previous frames.
Defines: SMAA_REPROJECTION, SMAA_DECODE_VELOCITY as above, and SMAA_REPROJECTION_WEIGHT_SCALE (30.0 [0, 80]): the higher, the sooner a moving pixel stops blending with its history.
| Name | Type | Description |
|---|---|---|
| uCurrentColorTexture | sampler2D | Output of the current frame's blend pass. |
| uPreviousColorTexture | sampler2D | Output of the previous frame's blend pass. |
| uVelocityTexture | sampler2D | The velocity framebuffer. SMAA_REPROJECTION only. |
Temporal supersampling (T2x)
- Jitter the camera projection by a subpixel offset alternating every frame.
- Run the three passes on each frame, setting
uSubsampleIndicesto the index matching the frame's jitter. - Resolve the current and previous blend outputs with
smaa-resolve. WithSMAA_REPROJECTION, both the blend and the resolve passes read a velocity texture, and the previous frame is reprojected.
| Frame | Camera jitter (pixels, y up) | uSubsampleIndices |
|---|---|---|
| 0 | (0.25, 0.25) |
[1, 1, 1, 0] |
| 1 | (-0.25, -0.25) |
[2, 2, 2, 0] |
WebGL's bottom-left texture origin runs the passes on a vertically mirrored image, so the jitter's y is the opposite of the reference table (see @SUBSAMPLE_INDICES in SMAA.hlsl).
License
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)
MIT. See license file.