4
$\begingroup$

Here's an image of a red plane I rendered using Eevee, along with its material nodes.

red plane

node setup

The color-management sequencer is set to Non-Color, the sample count is set to 1024, and no other special settings are modified. Despite this, the image still contains noise. Here, I have edited the image and mapped #FF0000 to black and #FF0101 to white:

image noise

The image contains #FF0000, #FF0101, and #FE0000 despite the material only outputting #FF0000. This still occurs when the sample count is set to 1, when the rendering engine is set to Cycles, when the color is pure black (#000000) or pure white (#FFFFFF), when an emission shader is used instead of a direct RGB input, and when the color output values are specifically floored to be multiples of 1/256.

In the Blender image editor, I can click and drag on an image to view the raw color data, which reveals that the image is a uniform, expected (1.0, 0.0, 0.0) color, indicating that there exists some problem in converting to 8-bit color.

To circumvent the noise, I can save the image as an OpenEXR file and use external software (e.g. Photoshop) to convert it to a PNG or BMP file, but this is suboptimal. Is there any way to bypass this step and get precise 8-bit color output from Blender alone?

$\endgroup$
6
  • $\begingroup$ Umm, that is really weird. Graphics card? $\endgroup$
    – TheLabCat
    May 24 at 2:40
  • $\begingroup$ GTX 980 Ti, though this happens regardless of if I use CPU or GPU, or the 980 Ti or the integrated Intel graphics. $\endgroup$
    – Poyo
    May 24 at 3:44
  • $\begingroup$ Oh! I did not understand the image. That’s straight red minus the plane render mapped to 0 and 1? $\endgroup$
    – TheLabCat
    May 24 at 7:35
  • 1
    $\begingroup$ @ZargulTheWizard Correct, sorry if that wasn't clear. I've edited the post to make that less confusing. $\endgroup$
    – Poyo
    May 24 at 16:09
  • 3
    $\begingroup$ Output Properties tab, Post Processing section, try decreasing Dither to 0.0. Also please share your .blend file, so we can see if we get the same results. $\endgroup$ May 25 at 15:50
6
+100
$\begingroup$

As in comments, go to Output Properties tab, Post Processing section, decrease Dither to 0.0:

Searching for dither on SE should yield multiple questions where dithering was the solution (instead of the problem). The problem dithering solves is banding. In short, there's only 255 steps from #000000 black to #FF0000 red, so a horizontal gradient with more than 256 pixel width will assign some colors to multiple columns, making those steps possibly perceivable as the bands.

Ideally*, dithering would be implemented as a 16 bit value scaled down to 8 bits by rounding down or up, depending on the pixel's position. This way an integer red component like in your color #FF0000 (255 red) wouldn't be affected by dithering.

However, this is not how Blender does it, it doesn't round up or down, it just adds noise and then rounds:

source/blender/imbuf/intern/divers.c

MINLINE void ushort_to_byte_dither_v4(
    uchar b[4], const unsigned short us[4], DitherContext *di, float s, float t)
{
#define USHORTTOFLOAT(val) ((float)val / 65535.0f)
  float dither_value = dither_random_value(s, t) * 0.0033f * di->dither;

  b[0] = ftochar(dither_value + USHORTTOFLOAT(us[0]));
  b[1] = ftochar(dither_value + USHORTTOFLOAT(us[1]));
  b[2] = ftochar(dither_value + USHORTTOFLOAT(us[2]));
  b[3] = unit_ushort_to_uchar(us[3]);

#undef USHORTTOFLOAT
}

di->dither is the setting in the interface. It's multiplied by 0.0033 (1/303.(03)) and then by the result of a function dither_random_value(s, t). s and t are x and y coordinates. The result of the function is in range [0..1) - it's implemented slightly differently throughout the code, but always based on "A faster triangle noise"

So it will add noise to every color, even if that color perfectly fits the target color space without rounding.

Custom dithering

First setup the color management as in the question:

Now make a plane with a gradient that has two "flat" areas on either side:

The color ramp is set to two colors: #FF0000 (255 red) and #F50000 (245 red). Keep in mind you can't just input #F50000 as hex, because Blender will assume it's sRGB and convert it to linear space that it uses. So what you have to do is to divide 245/255 in a calculator and copy-paste the result to the red value of the color.

Now the compositor:

I'm only dealing with red here, so that the node setup is as simple as possible. The noise texture I used is Blender Original Distorted Noise with size:0.0 and brightness:1.5:

I didn't calculate those settings in some clever way, I just tinkered with them until the preview of "Less Than" node was giving me a result with almost completely white one side of a band and almost completely black other side (and smooth transition between them):

So if we blacked every 2nd shade of red, it would look like this:

Comparison

Standard dither 1.0, compositing turned off

Half dither 0.5, compositing still off

Zero dither (compositing still off)

Custom dither (dither setting 0.00 but compositing enabled)

The top red bar of each pair is the original output for testing with a color-picker. The other bars have increased contrast in Photoshop by setting the midtone level to 0.03 in RGB Histogram. The idea behind 0.5 dithering is that 0.5*0.0033 < 1/255/2 (0.00165 < 0.00196) so I thought maybe a value will never be increased by half of the step to the next 8bit value, but I must be missing something, as there's still some noise.

--

*- according to my tiny brain, but I might be missing something; for example, the reasoning of Blender developers could be that you should avoid getting pure black and pure white colors, as those are problems with overexposure or details completely lost in darkness (low/high end clipping). Once you avoid that, the likelihood for any color to be an integer in the chosen color space (that is, before being cast to a lower precision in that format) is very small, and it also doesn't make much sense to treat a set of arbitrary colors differently than the rest. In the case of NPR, you can just disable dithering, export to a format with 16 bits per channel and do some custom dithering if needed.

$\endgroup$
1
  • $\begingroup$ Wish I could UV this twice. $\endgroup$ May 26 at 6:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.