0

I made a glsl geometry shader that rounds corners of 2D line strips. Shader inserts circular fillets at each corner.

There's a special case when two adjacent segments are colinear and fillet can't be created. In that case just a single original vertex is passed through.

This requires an if statement to be executed per each input vertex. Is there a clever way to avoid branching in this particular case? Is it even an issue if total number of processed vertices per frame is typically around couple hundred?

Here's complete shader code:

#version 400

layout(lines_adjacency) in;
layout(line_strip, max_vertices=25) out;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform float radius = 160.0;
uniform int steps = 10;

in VS_OUT {
    vec2 position;
} gsIn[];


bool arc(vec2 p0, vec2 p1, vec2 p2, out vec2 arcCenter, out vec2 arcStartPoint, out vec2 arcMidPoint, out vec2 arcEndPoint){
    vec2 t0 = normalize(p0 - p1);
    vec2 t1 = normalize(p2 - p1);   
    
    // segments are colinear, exit
    if(abs(dot(t0, t1)) > .9999f){
        return false;
    }

    vec2 h = normalize((t0 + t1) /2.0);
    float cosa = abs(dot(h, vec2(-t0.y, t0.x)));
    float hlen = radius/cosa;

    arcCenter = p1 + h*hlen;     
    
    float d = sqrt(hlen*hlen - radius*radius);  

    arcStartPoint  = p1 + t0 * d;
    arcEndPoint = p1 + t1 *d;
    arcMidPoint = arcCenter - h * radius;
    return true;
} 

float stepangle(vec2 center, vec2 s, vec2 e){
    vec2 rv1 = s-center;
    vec2 rv2 = e-center;
    float angle = acos( dot(normalize(rv1), normalize(rv2)) ) / steps;
    if( dot(rv1, vec2(-rv2.y, rv2.x))<0 ){
        angle = -angle;
    }
    return angle;
}

mat2 rotationMatrix(float angle){
    float cosa = cos(angle);
    float sina = sin(angle);
    return mat2(cosa, -sina, sina, cosa);
}

void emitFillet(vec2 center, vec2 arcStartPoint, vec2 arcEndPoint, mat4 mvpMatrix){
    float a = stepangle(center, arcStartPoint, arcEndPoint);
    mat2 rotMat = rotationMatrix(a);
    vec2 radVec = arcStartPoint-center;

    for(int i=0; i <=steps ; ++i){
        gl_Position = mvpMatrix * vec4(center + radVec, 0.0, 1.0);
        EmitVertex();
        radVec = rotMat * radVec;
    }
}

void emitSingleVertex(vec2 vert, mat4 mvpMatrix){
    gl_Position = mvpMatrix * vec4(vert, 0.0, 1.0);
    EmitVertex();
}

void main(){    

    mat4 mvMatrix = viewMatrix * modelMatrix;
    mat4 mvpMatrix = projectionMatrix * mvMatrix;

    vec2 p0 = gsIn[0].position;
    vec2 p1 = gsIn[1].position;
    vec2 p2 = gsIn[2].position;
    vec2 p3 = gsIn[3].position;

    vec2 center, s, m, e;
    mat2 rotMat;
    vec2 radVec;
    float a;
    bool canMakeFillet;

    // first corner half-fillet
    canMakeFillet = arc(p0, p1, p2, center, s, m, e);
    if(canMakeFillet){
        emitFillet(center, m, e, mvpMatrix);
    }
    else{
        emitSingleVertex(p1, mvpMatrix);
    }
    
    // scond corner half-fillet
    canMakeFillet = arc(p1, p2, p3, center, s, m, e);
    if(canMakeFillet){
        emitFillet(center, s, m, mvpMatrix);
    }
    else{
        emitSingleVertex(p2, mvpMatrix);
    }
    
    EndPrimitive();
}
nj16
  • 63
  • 5
  • 1
    "*This requires an if statement to be executed per each input vertex.*" You've decided to use a Geometry shader to amplify geometry. You've already thrown away any hope of good performance; why do you think an `if` statement will matter here? – Nicol Bolas Jul 20 '21 at 13:59
  • I don't have much experience with geometry shaders, so any input is valuable. Are you suggesting that, performance-wise, it might be better to do this on cpu rather than use a geometry shader? – nj16 Jul 20 '21 at 14:14
  • 1
    That would depend on 1) how much geometry you're generating, and 2) how frequently it *changes*. If the CPU only has to compute the vertices once and you use them for hundreds or thousands of frames, it might work out. But then again, it may not. My overall point is that you shouldn't be micro-optimizing an `if` statement in the middle of an operation that's fundamentally slow. Not without benchmarking. – Nicol Bolas Jul 20 '21 at 14:16
  • Right. In my particular case the geometry is animated. So vertex buffer is updated every frame. My guess was that generating fillets in shader could be faster than calculating them on cpu and updating the vertex buffer with tenfold more vertex data each frame. As I stated in the question; there is not that much geometry. I expect a couple hundred vertices of input into GS each frame. I guess then that the real question here is not about branching but whether to do this in GS or on cpu. – nj16 Jul 20 '21 at 14:35

0 Answers0