Search Unity

Adding hard lines to a gradient

Discussion in 'Shaders' started by PanicEnsues, Jun 24, 2020.

  1. PanicEnsues

    PanicEnsues

    Joined:
    Jul 17, 2014
    Posts:
    187
    I've created a shader that takes a greyscale heightmap and renders it like a contour map by breaking up the gradients into hard steps and adding lines between each step, like this:




    I'm mostly happy with it, except that the the line widths can vary, blowing out into fat chunks where the gradient becomes "flat" or level.

    First I chop the height value up into steps with this (where h is the float height value and _StepCount is the number of contour steps I want):

    float steppedDepth = ceil(h * _StepCount) / _StepCount;

    Works great! But then I add the lines with this hacky method (where _LineSize is half the desired line width in gradient values):

    float lineVal = fmod(h * -_StepCount - _LineSize, 1) + fmod(h * _StepCount - _LineSize, 1);

    It's not a very good solution, so I was wondering if any shader wizards could suggest a better way to add fixed-width lines between input values...
     
  2. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    what you need is to:
    - first step the line
    - do a sobel filter that discriminate the step you want (ie if you don't want it on brown to blue)
    - multiply the sobel onto the step colors
     
  3. PanicEnsues

    PanicEnsues

    Joined:
    Jul 17, 2014
    Posts:
    187
    TIL the term "sobel filter". That looks perfect, I'll give it a shot!

    Thanks!
     
  4. Namey5

    Namey5

    Joined:
    Jul 5, 2013
    Posts:
    188
    All you really need to do is scale the line width by the gradient of the height (which you can access through screen space derivative functions);

    Code (CSharp):
    1. float width = fwidth (h) * _LineSize
    2. float lineVal = fmod(h * -_StepCount - width, 1) + fmod(h * _StepCount - width, 1);
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    For something like this situation,
    fwidth()
    will produce poor results due to the derivatives not being constant. A proper sobel type filter will make much better looking line.
     
    Namey5 likes this.
  6. Namey5

    Namey5

    Joined:
    Jul 5, 2013
    Posts:
    188
    Good point. For that I made reference to a shader I wrote for UA that does this, but I now realise I remembered it poorly - the height wasn't sampled from the texture directly, rather the vertex coords were displaced and derivatives taken from that.
     
  7. PanicEnsues

    PanicEnsues

    Joined:
    Jul 17, 2014
    Posts:
    187
    I tried the fwidth() solution, and it actually worked really well unless I'm zoomed all the way in; at that point the lines get noisy and break up in an odd way.
    I think I can probably work around that though (by modifying the target width based on current zoom level), and avoid the cost overhead of the sobel solution.

    Thanks a lot for your help!
     
  8. Samuel_Herb

    Samuel_Herb

    Joined:
    Jun 23, 2020
    Posts:
    8
    You may not need a true Sobel. Just 4 samples in an X can look pretty good too!

    I'm interested in this fwidth() thing though. I've never messed with derivatives in shaders and it seems like it could be useful. Thanks, Namey5!