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();
}