0

I have a fragment shader that can draw an arc based on a set of parameters. The idea was to make the shader resolution independent, so I pass the center of the arc and the bounding radii as pixel values on the screen. You can then just render the shader by setting your vertex positions in the shape of a square. This is the shader:

precision mediump float;

#define PI 3.14159265359
#define _2_PI 6.28318530718
#define PI_2 1.57079632679

// inputs
vec2 center = u_resolution / 2.;
vec2 R = vec2( 100., 80. );
float ang1 = 1.0 * PI;
float ang2 = 0.8 * PI;
vec3 color = vec3( 0., 1.0, 0. );

// prog vars
uniform vec2 u_resolution;
float smOOth = 1.3;
vec3 bkgd = vec3( 0.0 );    // will be a sampler

void main () {
    // get the dist from the current pixel to the coord.
    float r = distance( gl_FragCoord.xy, center );
    if ( r < R.x && r > R.y ) {

        // If we are in the radius, do some trig to find the angle and normalize
        // to 
        float theta = -( atan( gl_FragCoord.y - center.y, 
                    center.x - gl_FragCoord.x ) ) + PI;

        // This is to make sure the angles are clipped at 2 pi, but if you pass
        // the values already clipped, then you can safely delete this and make
        // the code more efficinent.
        ang1 = mod( ang1, _2_PI );
        ang2 = mod( ang2, _2_PI );

        float angSum = ang1 + ang2;
        bool thetaCond;
        vec2 thBound;   // short for theta bounds: used to calculate smoothing
                        // at the edges of the circle.

        if ( angSum > _2_PI ) {
            thBound = vec2( ang2, angSum - _2_PI );
            thetaCond = ( theta > ang2 && theta < _2_PI ) || 
                        ( theta < thetaBounds.y );
        } else {
            thBound = vec2( ang2, angSum );
            thetaCond = theta > ang2 && theta < angSum;
        }

        if ( thetaCond ) {
            float angOpMult = 10000. / ( R.x - R.y ) / smOOth;
            float opacity = smoothstep( 0.0, 1.0, ( R.x - r ) / smOOth ) - 
                            smoothstep( 1.0, 0.0, ( r - R.y ) / smOOth ) - 
                            smoothstep( 1.0, 0.0, ( theta - thBound.x ) 
                                                    * angOpMult ) - 
                            smoothstep( 1.0, 0.0, ( thBound.y - theta )
                                                    * angOpMult );
            gl_FragColor = vec4( mix( bkgd, color, opacity ), 1.0 );
        } else
            discard;
    } else
        discard;
}

I figured this way of drawing a circle would yield better quality circles and be less hassle than loading a bunch of vertices and drawing triangle fans, even though it probably isn't as efficient. This works fine, but I don't just want to draw one fixed circle. I want to draw any circle I would want on the screen. So I had an idea to set the 'inputs' to varyings and pass a buffer with parameters to each of the vertices of a given bounding square. So my vertex shader looks like this:

attribute vec2 a_square;
attribute vec2 a_center;
attribute vec2 a_R;
attribute float a_ang1;
attribute float a_ang2;
attribute vec3 a_color;

varying vec2 center;
varying vec2 R;
varying float ang1;
varying float ang2;
varying vec3 color;

void main () {
    gl_Position = vec4( a_square, 0.0, 1.0 );

    center = a_center;
    R = a_R;
    ang1 = a_ang1;
    ang2 = a_ang2;
    color = a_color;
}

'a_square' is just the vertex for the bounding square that the circle would sit in.

Next, I define a buffer for the inputs for one test circle (in JS). One of the problems with doing it this way is that the circle parameters have to be repeated for each vertex, and for a box, this means four times. 'pw' and 'ph' are the width and height of the canvas, respectively.

var circleData = new Float32Array( [
    pw / 2, ph / 2,
    440, 280,
    Math.PI * 1.2, Math.PI * 0.2,
    1000, 0, 0,

    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
] );

Then I simply load my data into a gl buffer (circleBuffer) and bind the appropriate attributes to it.

gl.bindBuffer( gl.ARRAY_BUFFER, bkgd.circleBuffer );
gl.vertexAttribPointer( bkgd.aCenter, 2, gl.FLOAT, false, 0 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aCenter );
gl.vertexAttribPointer( bkgd.aR, 2, gl.FLOAT, false, 2 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aR );
gl.vertexAttribPointer( bkgd.aAng1, 1, gl.FLOAT, false, 4 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng1 );
gl.vertexAttribPointer( bkgd.aAng2, 1, gl.FLOAT, false, 5 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng2 );
gl.vertexAttribPointer( bkgd.aColor, 3, gl.FLOAT, false, 6 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aColor );

When I load my page, I do see a circle, but it seems to me that the radii are the only attributes that are actually reflecting any type of responsiveness. The angles, center, and color are not reflecting the values they are supposed to be, and I have absolutely no idea why the radii are the only things that are actually working.

Nonetheless, this seems to be an inefficient way to load arguments into a fragment shader to draw a circle, as I have to reload the values for every vertex of the box, and then the GPU interpolates those values for no reason. Is there a better way to pass something like an attribute buffer to a fragment shader, or in general to use a fragment shader in this way? Or should I just use vertices to draw my circle instead?

BDL
  • 21,052
  • 22
  • 49
  • 55
  • If you want help debugging why your code is not working you're required to put enough code **in the question itself** so we can reproduce the issue. Suggest you put a **minimal** repo in a [snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/) – gman Jun 10 '20 at 01:32
  • @gman I can debug it myself, but the reason I asked the question more so was about my general approach to the problem, and seeing if there was a better way to accomplish it. Thanks anyway. Edit: looking on your answer to the question, that actually exactly fit what I needed; just a pointer in the right direction – LLAMASARECOOL Jun 10 '20 at 21:25

1 Answers1

0

If you're only drawing circles you can use instanced drawing to not repeat the info.

See this Q&A: what does instancing do in webgl

Or this article

Instancing lets you use some data per instance, as in per circle.

You can also use a texture to store the per circle data or all data. See this Q&A: How to do batching without UBOs?

Whether either are more or less efficient depends on the GPU/driver/OS/Browser. If you need to draw 1000s of circles this might be efficient. Most apps draw a variety of things so would chose a more generic solution unless they had special needs to draw 1000s of circles. Also it may not be efficient because you're still calling the fragment shader for every pixel that is in the square but not in the circle. That's 30% more calls to the fragment shader than using triangles and that assumes your code is drawing quads that fit the circles. It looks at a glance that your actual code is drawing full canvas quads which is terribly inefficient.

gman
  • 100,619
  • 31
  • 269
  • 393
  • The full canvas render box was just for debugging, so if the circle ended up outside the quad (it did) I would at least have a larger viewport and a greater chance of seeing where it actually went, in essence, getting conformation that the circle actually drew and I didn't have a value problem or something. But you're right: that would be inefficient. – LLAMASARECOOL Jun 10 '20 at 21:30