Search Unity

Only alter vertex if it's normal points in a certain direction (avoiding if statements).

Discussion in 'Shaders' started by jamesdoig, Feb 24, 2017.

  1. jamesdoig

    jamesdoig

    Joined:
    Jan 1, 2014
    Posts:
    12
    I'm new to shaders but do know I'm supposed to avoid branching logic, so could use some help with working this out please.

    In my vertex processing function I want to alter the IN.vertex.x only if the IN.normal.x points away from _Velocity.x (a variable I'm passing into the shader) ... same goes for the IN.vertex.y.

    Thanks,

    James.
     
  2. jamesdoig

    jamesdoig

    Joined:
    Jan 1, 2014
    Posts:
    12
    This seems to have done the job:
    Code (CSharp):
    1. float2 diff =  _Velocity.xy * ((normalize(v.normal.xy) - normalize(_Velocity.xy)) / 2);
    2. float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    3. worldPos.xy -= diff * _MotionOffset;
    4. v.vertex =  mul(unity_WorldToObject, worldPos);
    Though open to better/neater implementations :)
     
    Last edited: Feb 24, 2017
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Try: float2 diff = max(dot(normalize(v.normal.xy), _Velocity.xy), 0.0);

    However don't be afraid of if statements too much. There's a lot of unnecessary fear of them for shaders, and people often write strange code to try to avoid their use that can be even more expensive (and not actually avoiding the use of an "if").

    On desktop, if statements are wonderful and work way, way better than you might expect. Modern GPUs can do real branching and completely avoid parts of a shader, working basically just as well and efficiently as an if in C#. On mobile, or really old desktop GPUs (like pre DX10 level hardware), an if statement works by doing both sides of the "if" and then throwing away one based on the compare. If those statements are expensive, you're doing all of that work all of the time and that's probably bad. If they're not expensive then it's probably faster than the alternatives.

    Here's a very basic example:
    Code (CSharp):
    1. if (myValue > 0.5)
    2.   myValue = 1.0;
    3. else
    4.   myValue = 0.0;
    There are a few ways people try to avoid something like this, the most basic one is:
    Code (CSharp):
    1. myValue = step(0.5, myValue);
    Yay! No more if! ... Except the resulting compiled shader is identical, at least for DX9. DX11 they differ slightly, but that's because DX11 has actual branching and will try to treat the first example as a branch. If you change the first example to:
    Code (CSharp):
    1. myValue = myValue >= 0.5 ? 1.0 : 0.0;
    It compiles to identical shaders for all DirectX APIs.

    There are some cases where modern GPUs can't treat an if as a "branch", and that's when you're using an if to modify a variable that's later used in a derivative. The short version is if you use an if to modify the UVs of a texture it'll go back to being the "old" style of always doing all of both sides and then using one result, but there are ways around that.
     
    jamesdoig likes this.
  4. jamesdoig

    jamesdoig

    Joined:
    Jan 1, 2014
    Posts:
    12
    Thanks you so much :)
    That was very informative and also the dot product was totally the right thing to use here (rather than my messy /2 nonsense), thanks again.