Banding in Games: A Noisy Rant: Mikkel Gjøl, Playdead @pixelmager
Banding in Games: A Noisy Rant: Mikkel Gjøl, Playdead @pixelmager
Banding in Games: A Noisy Rant: Mikkel Gjøl, Playdead @pixelmager
p:/
/lo s
op ee a
it.d lso
k/p
ub
lica
tio
n s/
Banding in Games:
A Noisy Rant
(revision 5)
- not going to go into too much detail about the theoretical background, try to give
intuitive understanding and focus on issues
First a couple of example to motivate that this is actually still an issue in games.
amazing game, pixeljunk eden (if you haven’t played it, you should literally leave the
room and do so now!)
http://pixeljunk.jp/library/Eden/
so what we’re looking at here are the “abrupt changes between shades of the same
colour”
Skyrim, amazing do-whatever-you-want game… which means most will remember
skyrim looking something like this
http://www.elderscrolls.com/skyrim
I was working on banding at the time I played it, so I remember the menus
...so I remember it looking mostly like this :)
...this is another beautiful indie-game, kentucky route zero
http://kentuckyroutezero.com/
which on most high-quality monitors will have quite noticeable banding (but almost get
away with it due to their graphical style)
What happens to your precious pixels?
(monitor expects gamma-corrected srgb)
textures
shaders blending framebuffer
…
sRGB
When incorrect the monitor will still correct the signal as if the image was in
gamma-space, exposes all the horrible bands in the dark areas
If you use sRGB correctly, you’re doing pretty well - you will generally hardly notice
banding (though dark areas remain)
If you are not on a platform where it’s readily available, or you want to get rid of the
last issues, the rest of this presentation is for you
also http://www.poynton.com/GammaFAQ.html
banding yes
gone?
“Don’t you just dither?”
no
yes can afford
higher
precision?
no
yes
has sRGB?
no
yes prefer noise
to banding?
no
This is the happy face of Thomas Rued, a man of experience who knows his pixels!
...when all else fails, add noise
Areas affected by color-banding
(...anything that renders gradients on screen)
Lighting Particles
Fog Posteffects, e.g. glow,
Alpha Blending aa, screen fades
● additive (multipass
rendering)
(we will not go into
● subtractive
● multiplicative (lerp) undersampling and
● ...creative :p compression issues)
this talk is about the specific issues related to banding, where they occur and how to
remedy them
..most noticeable on anything creating gradients: fog, falloffs, glow, particle splotches,
from lights etc.
Noisy ‘signals’ (e.g. with a texture) “hides” most banding.
Dithering: The art of noise
a variety of ways to do dithering, ...they all have to do with how the eye blurs out small
changes (or integrates over an area). Squint a bit at the above image and you can
barely tell the difference...
https://www.shadertoy.com/view/MslGR8
...you should of course dither r,g,b separately for improved luminance
error diffusion based methods rely on knowledge of the surrounding area, something
that doesn’t map well to the GPU, which prefer doing a lot of unrelated things in
parallel
http://www.realtimerendering.com/blog/2011-color-and-imaging-conference-part-ii-cou
rses-a/
http://www.joesfer.com/?p=149
http://www.joesfer.com/?p=108
unity lightbuffer - already in a logarithmic colorspace to enable HDR lighting, so no
sense in additionally using srgb
modified Internal-PrePassLighting.shader to do (monochrome) random dithering
...in order to not create discontinuities at deferred light-edge (light fades out to edge),
we subtract from the intensity instead of adding…
..so slightly darker, but otherwise uniform (we vary noise over time as well, so with a
bad monitor, and/or eyesight, the result is quite good)
quantizing signal
by truncation
signal
floor(signal)
doing simple quantization (by truncating) is always darker than intended signal (by
0.5bit on avg)
quantizing signal, error
accumulated err 0.5LSB, max err 1LSB, 0.5LSB bias towards darker
signal
floor(signal)
abs(err)
signal
signal+0.5
floor(signal+0.5)
error rounding up
abs(err)
Though the average error is 0.25LSB, the accumulated error is 0 (intuitively as area
rounding up == area rounding down), but absolute error at any value is as much as
0.5*LSB
Signal does not lose ‘energi’ as opposed to trunc
Smaller err, smaller maxerr (half before),
now at least on average, it’s the same signal as the original. It is unbiased.
rounding+signed random
(white noise, uniformly distributed [-0.5;0.5[ LSB )
signal
signal+0.5
(signal+0.5) + [-0.5;0.5[
= signal + [0.0;1.0[
adding a uniform signed noise means we’re still not biasing the signal - sometimes
brighter, sometimes darker, so on average it does neither...
...property still valid that avg error is 0
This is exactly the same as adding a normalized [0;1[ to the signal
“area” of noise
accumulated error still 0, max error 1LSB, no bias
signal
floor(signal)
ceil(signal)
error rounding up
abs(err)
max err of course now larger as we just added noise (but still: no bias)
intuitively: on average quantizing as much to value above as value below, so end up
with the same signal… on average
- same as before, on average the positive/negative noise will cancel out, and we get
the original signal on average.
Error still varies (intuitively: When signal crosses the truncated value, the resulting
error is 0)
...we get slightly worse max error than from just rounding.. but….
magnitude of uniform noise
signal
floor(signal)
ceil(signal)
abs(err)
...if we look at the accumulated error for a single value (integrating over the red/blue
stippled line), the error will now cancel out as well as for entire signal, resulting in the
original signal for any arbitrary single value
This is a property of the noise being uniformly distributed
intuitively, since the noise-distribution is uniform, when integrating across the line
shown the length of the line corresponds to the probability that the value will either
round up or down
...integrating floor(f) / ceil(f) across this line, we’ll end up at the signal
...noise is uniformly distributed, but the resulting visual noise is NOT uniformly
distributed - almost no noise near “correct” values
Really no reason to add noise where signal -is- it’s truncated value
triangular noise distribution
floor(signal + hash(seed1)+hash(seed2)-0.5) => [-0.5;1.5[
nd, so
H T : G PUs rou ;1[
1
HINDSIG e should be [-
ng .0
dither-ra 1)+hash(s2)-1
a sh (s
i.e. h
references
http://en.wikipedia.org/wiki/Dither
http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_199
2.pdf
https://www.shadertoy.com/view/4ssXRX
blue H
nois INDSIG
e is H
less T:
various distributions noti
cea
ble
HINDSIGHT: this shows the absolute value of the error, abs(err), which makes it hard
to see that positive/negative errors “cancel each out” in the noisy areas. Remapping
the actual error to a [0;1] range gives a much more accurate depiction of the error.
Example of dithering using triangular noise [-0.5;1.0[ vs. uniform noise [0;1] -
triangular noise makes the noise more evident, but also more uniform - no areas with
little noise.
...the effect is certainly subtle, but better is better :) (and also more noisy :p )
subtracting noise
same properties, with an offset
signal
floor(signal)
ceil(signal)
just a quick note that subtracting noise of course results in a signal with the same
properties, but offset
Dithering after quantisation does not remove banding
(e.g. LDR film-grain at end of frame)
convert first, dither afterwards… dither in whatever space the color will be quantized
in.
the right color space
(manual logarithmic buffer, e.g. unity lightbuffer)
incorrect:
trunc(lin2srgb(f+rnd))
https://www.shadertoy.com/view/XsfXzf
menu of steam big picture (best thing that has happened to pc-gaming for a long time)
- has noise added, but interestingly in what appears to be the wrong color-space.
HI
ND
SIG
Triangular dithering at boundaries HT
It turns out, that due to clamping, the boundaries around black/white (0/1)
become wrong when using 2LSB triangular dithering.
https://computergraphics.stackexchange.com/questions/5904/whats-a-proper-way-to-
clamp-dither-noise/5952#5952
intuition: As the noise gets clamped, clamping destroys the symmetry in the noise -
and for that reason the average does not tend towards the actual signal
HI
ND
SIG
Triangular dithering at boundaries HT
https://computergraphics.stackexchange.com/questions/5904/whats-a-proper-way-to-
clamp-dither-noise/5952#5952
HI
ND
SIG
Triangular dithering at boundaries HT
vec2 rnd = hash22(seed);
float dithertri = (rnd.x + rnd.y - 1.0); //note: symmetric, triangular dither, [-1;1[
https://computergraphics.stackexchange.com/questions/5904/whats-a-proper-way-to-
clamp-dither-noise/5952#5952
alpha blending
blend-modes
● additive, c = c0+c1; //dither c0,c1
● subtractive, c = c0-c1; //dither c0,c1
● multiplicative, c = c0*c1; //… :(
● creative :p
adding / subtracting does not change the magnitude of LSB, so the dithering is fine.
You still end up adding 1LSB of noise for each input, which adds up...
multiplying ruins the whole thing - no way to determine the magnitude of a LSB is
after multiplication of “some value [0;1]” with a constant (accessing the
destination-buffer of course is even worse)
alphablending, non-constructive rnd
(blending multiple shaders to same pixel)
...if blending multiple objects with the same shader, using a different noise-value for
each layer helps reduce noise.
E.g. a particle-system with many layers could use the viewspace-z as a seed on top
of screenposition.
further blending issues
Anything that multiplies something onto dstcol
● do as much in shader as possible
○ don’t use blendunit for srcAlpha if you can do it
● 2pass: subtract from dst, then add color
(doesn’t band... but also doesn’t look the same)
● add moar noise to dst-alpha :(
...helps mask problem, but not a “solution”.
multiplicative blending
(dithered dstcolor) * srcalpha, can’t dither post-blend
signal
floor(signal)
floor(signal+noise)
honestly not entirely sure what is going on here… but seems vendor2 runs blending
at higher precision (or quantizes after blending). Fortunately, vendor2 can be found in
all major new consoles.
a * srccol + b * dstcol
-
Hindsight: This appears to be consistent with the d3d11 spec, which states that
blending only has to be performed at rendertarget-precision (e.g. rgba8 => 8bit
blending precision):
“When a RenderTarget has a fixed point format <snip> blending operations may be
performed at equal or more (e.g up to float32) precision/range than the output format.”
programmable blending
● nvidia opengl extension (also rsx on ps3)
GL_NV_texture_barrier
● intel dx11-extension, pixel shader ordering
● mobile tiled, deferred architectures,
opengles
GL_EXT_shader_framebuffer_fetch
● Currently no unified solution (dx12?)
way overkill - on platforms that support this, there are likely better alternatives: sRGB,
higher precision buffers etc.
rgb-weight
https://www.youtube.com/watch?v=h59LwyJbfzs
not something you hear about graphics algorithms every day :)
Thanks
Daniel Povlsen, @danielpovlsen
Mikkel Svendsen, @IkarosAV
Jakob Schmid, @jakobschmid
http://sandervanrossen.blogspot.dk/2012/02/hdr-dithering.html
http://www.realtimerendering.com/blog/2011-color-and-imaging-conference-part-ii-courses-a/
https://bartwronski.com/2016/10/30/dithering-part-one-simple-quantization/
http://www.opengl.org/registry/specs/ARB/framebuffer_sRGB.txt
HDR resolve
http://sandervanrossen.blogspot.dk/2012/02/hdr-dithering.html
Real World Assets
(it’s just noise, not magic)
http://www.sandraandwoo.com/comics/2012-11-19-0430-software-engineering-now-w
ith-cats.png
bug: Banding from
exponential lightbuffer
compression
content fix:
Brighten albedo
Less bright light