Visible color change lines

Hi, i hope you can see in the pictures, i see strange contour like patterns of color gradation. Anyone has any idea how to solve it or even how i can correct it via any post-process effect. I have seen similar patterns in the walls and floors because of the point light. The smoke you see is generated via raymarching using a 3D noise texture as a 3d density map. It could be because of the resolution of the 3d texture which is generated by a multiple levels of worley noise, since it has a resolution of 128x64x64 and the whole room is 90x40x60. Basically the whole texture is been used in the same scale of the room, however i used gaussian blur so those color patterns should have been minimized at least.


I assume you’re talking about these (contrast increased to make more visible for people who can’t see it):

You’re not doing anything wrong per se, but you are running into the limitations of 24 bit color and an optical illusion. If you take your above image into Photoshop or Gimp and use the color picker to look at the color values you’ll find each “step” only differs by steps of 1/255. Basically you’ve hit the limitations of color representation. For example around the middle of those lines one band of color is RGB 47, 42, 41, and the next band down is RGB 47, 42, 40. The optical illusion I mentioned was the human brain will accentuate the edge between bands of color even more strongly once you notice them making them even more obvious than before you noticed them.

The solution is to use dithering. If you have your camera set to render in HDR, then using the post processing stack should fix this by automatically dithering the HDR render target to the LDR display color depth. Alternatively you could dither the output from your shaders, though you have to be careful if you’re doing a screen space Gaussian blur as the dithering noise will also be blurred away leaving you exactly where you are now, meaning you have to dither the Gaussian pass(es) too.

1 Like

Ok one update, this seems to be the result of a gaussian blur i have applied. Since i am doing raymarching in VR i have limited amount of raymarch steps. So i used a blue Noise to have for jittering the ray start position. I got rid of visible low-step raymarching artifacts in cost of having blue noise in my screen (1024 resolution blue noise texture). I would not mind though because i figured i will just blur a little bit the image and i will lose the high frequencies. I managed to render my smoke in a different rendertexture via command buffers so i can blur only the noise and not the surrounding geometry. Yes those lines were present before the blur but not so much(maybe my eye was catching the blue noise “splatters” instead of the color grading), but after the gaussian blur , yes i got rid of blue noise but those line are much more visible and annoying.

Do the blur passes into ARGBHalf render textures, and then when compositing back into the scene use the blue noise dithering again. Also, if you’re using linear space color, make sure you convert the color values into gamma space before applying the dither (and then convert back to linear).

My pipeline is something like this: render the opaque geometry and save it to a rendertexture(all RTs have the screen resolution), then when i am about to go alphaforward change the rendertarget, clear it. So at OnRenderImage i have 2 textures one with my geometry and one with only the smoke. I use a 2 pass gaussian blur(vertical and horizontal) and after that i take that texture and blend it with the geometry texture and that’s it

Well i dont specify any format, so i guess it has to be the default.I only specify the resolution to match my screen

I use float4 as a return type instead of fixed4. I dont know if that would make any difference

If you don’t specify a format, it’ll depend on if you’re using an HDR camera or not. The default will be ARGBHalf for HDR, otherwise it’ll be ARGB32 and each pass will be banded unless you dither.

On desktop, GPUs don’t support anything but 32 bit floating point values, so fixed, half, and float are all the same.

Well according to documentation, default format is ARGB32

5203508--517568--upload_2019-11-22_20-47-44.png

I am using the camera from steamVR since i am using HTC vive headset

ok so right now having described what i do and the pipeline, what should I do? You say dithering. So like have a specific dithering patter or a texture? And wouldnt that create small pattern artifacts in the final image. I mean whats the point, I already had some small splatter anyway from blue noise. Maybe if those artifacts will be uniform and small enough

It says it’s “typically” ARGB32, depending on frame buffer format (which is controlled by the HDR setting on the camera, and the HDR mode graphics settings). The HDR default is ARGBHalf.

Proper dithering is a bit of an art. You wan to do as little as possible so it’s not obvious noise, but enough to hide the banding. Personally for color rendering like this I’ll just use interleaved gradient noise.

// Interleaved gradient function from Jimenez 2014 http://goo.gl/eomGso
float GradientNoise(float2 uv)
{
    uv = floor(uv * _ScreenParams.xy);
    float f = dot(float2(0.06711056f, 0.00583715f), uv);
    return frac(52.9829189f * frac(f));
}

That returns a value between 0.0 and 1.0 for each screen pixel (assuming the uvs you pass in are screen space uvs). You’ll want to scale that by 2 times the target color precision and offset by -0.5. Then you want to ensure you’re doing the dithering in the target color space. So for that you can do:

#ifndef UNITY_COLORSPACE_GAMMA
color.rgb = LinearToGammaSpace(color.rgb);
#endif
float dither = GradientNoise(screenUV) * (2.0/255.0) - (0.5/255.0);
color += dither;
#ifndef UNITY_COLORSPACE_GAMMA
color.rgb = GammaToLinearSpace(color.rgb);
#endif

Here’s an example image of what something like the above code produces (technically this is using a chromatic dither rather than greyscale):
5204252--517655--upload_2019-11-22_12-52-22.png
The middle bar is a gradient between two colors with no dithering, using colors grabbed from your image.
The top bar is dithering w/o gamma space correction, bottom bar is with. Without gamma correction looks mostly fine here, but if you go to even darker colors it’ll be obviously too bright. If I adjust the contrast on the above image it’s more obvious that the top is too bright.
5204252--517703--upload_2019-11-22_14-25-27.png
You’ll also notice that even with the banding made quite extreme from the contrast increase, the dither does a really good job of hiding it.

Thank you so much for the help, you are by far the only one that “really” helps in these forums

I tried the code and i didn’t make any difference
5206031--518021--upload_2019-11-23_18-35-23.jpg

Also HDR is off i just checked

Ok I think I did it or I guess improved it. I just change the format to ARBHalf. It is still visible in some outlines of the smoke but not that crazy like before. I guess you cant get rid of it entirely. If I increase the dithering, would that remove it completely (of course I mean without seeing the dithering patterns). Also could you please explain why it is called half instead of you know something indicating that it gives more bits per channel. I might be stupid because up until know i thought half would mean less precision

Dithering an already quantized (ARGB32) source won’t help, it’ll just add noise on top. The idea with dithering is that you’re able to retain some of the higher precision data that would otherwise have been lost.

Dithering and blending are a really hard problem. The amount of dithering needed depends on the final blended color, which you do not know in the shader. So usually you have to push the amount of noise in the alpha to overcome that. I usually end up using blue noise for alpha because of this, since stronger blue noise is less obvious than other options.

Try replacing this line:
color += dither;

with:
color += dither * float2(1,1,1,4);

Change the 4 until the banding goes away, then reduce it enough that the noise doesn’t bother you.

That is what it’s indicating. Half refers to a half precision float, which is a 16 bit floating point number. RGB24 and ARGB32 both refer to 8bit unorm or sRGB values.