Game Development Reference
//Return the missing chrominance (Co or Cg) of a pixel:
//a1-a4 are the four neighbors of the center pixel a0.
float filter(vec2 a0, vec2 a1, vec2 a2, vec2 a3, vec2 a4)
vec4 lum = vec4(a1.x, a2.x , a3.x, a4.x);
vec4 w = 1.0-step(THRESH, abs(lum - a0.x));
float W = w.x + w.y + w.z + w.w;
//Handle the special case where all the weights are zero.
W = (W==0.0)? W : 1.0/W;
Listing 4.2. GLSL implementation of the edge-directed reconstruction filter.
ages, since the luminance is always correct, but they can become more pronounced
when motion is involved.
To eliminate these reconstruction artifacts, we have designed a simple and
ecient edge-directed filter, where the weights of the four nearest chrominance
samples are calculated based on the luminance gradient towards that sample. If
the gradient has a value greater than a specific threshold, indicating an edge, then
the corresponding chrominance sample has zero weight. Otherwise the weight is
one. This is expressed compactly in the following equation:
w i C i ,
i =1 . 0
step ( T
L i −
L 0 |
where C i and L i are respectively the chrominance (C o or C g ) and luminance
of pixel i . In our notation, zero denotes the center pixel, while the next four
values denote the four neighbors. The step function returns one on positive
values and zero otherwise. T is the gradient threshold. Our experiments with
framebuffers from real games indicate that our algorithm is not very sensitive to
this threshold, and values around 30/255 give similar quality in terms of PSNR.
However we have observed that this particular value (30/255) tends to minimize
the maximum difference between the original and the compressed framebuffer.
The gradient can be computed as a simple horizontal and vertical difference
of the luminance values, as shown in Listing 4.2. The strategy we follow when all
the weights are zero, which happens when we cannot find a neighbor with similar
luminance, is to set the chrominance to zero. Furthermore, to avoid handling a
special case at the edges of the framebuffer, where only pixels inside the frame
boundaries should be considered, we are using a “mirrored repeat” wrapping
mode when sampling the framebuffer pixels, which is supported natively by the
texture hardware. It is also worth noting that the implementation of this filter
is using conditional assignments, which are significantly faster than branches on
most GPU architectures.