Monday, July 10, 2017

opengl - An efficient way for generating smooth circle


I'm looking for creating smooth circle. OpenGL supports point, line, and triangle. To create other primitives like circle, we utilize the preceding shapes. In my case, I've utilized points as follows:


float radius(0.5f);
for ( float angle(0); angle < glm::radians(360.0f); angle += glm::radians(0.5f) ){
Vertex vertices[] = { Vertex(glm::vec3( radius*cos(angle), radius*sin(angle), 0)) };
meshes.push_back( new Mesh(vertices, sizeof(vertices)/sizeof(vertices[0]), 'P') );
}


In rendering loop,


while( Window.isOpen() ){
Window.PollEvents();
Window.clear();
//================( Rendering )=========================
Shader.Use();

for ( int i(0); i < meshes.size(); ++i )
meshes[i]->draw();


//======================================================
Window.SwapBuffers();
}

The result is


enter image description here


Now to create smooth circle, basically I just increase the number of points. I don't like this approach since I need to create a lot of points for single shape. My question is is there an alternative yet efficient approach for this issue?



Answer



Here's one neat trick you can use to draw nice antialiased curves.





  1. Take a mesh (eg. a quad, or to reduce unnecessary overdraw far from the line, you can use an annulus-shaped mesh that approximates the circle more closely.




  2. Ensure the mesh's texture coordinates run from -1 at one edge of the circle to +1 at the other edge, to keep our math simple. Ensure there's a little more mesh past the -1...1 range so our circle doesn't run right into the hard polygon edge.


    Example of meshes enclosing the circle we want to draw.




  3. Use a fragment shader something a bit like this:





(I'm most familiar with HLSL syntax, so please let me know if I've made any errors translating this to GLSL)


// Compute this fragment's distance from the center of the circle in uv space.
float radius = length(textureCoordinate);

// Convert to a signed distance from the outline we want to draw.
// +ve: outside the disc, -ve: inside the disc.
float signedDistance = radius - 1.0f;


// Use screenspace derivatives to compute how quickly this value changes
// as we move one pixel left or right / up or down:
vec2 gradient = vec2(dFdx(signedDistance), dFdy(signedDistance));

// Compute (approximately) how far we are from the line in pixel coordinates.
float rangeFromLine = abs(signedDistance/length(gradient));

// Combine with our desired line thickness (in pixels) to get an opacity.
float lineWeight = clamp(THICKNESS - rangeFromLine, 0.0f, 1.0f);


return vec4(COLOR.rgb, lineWeight);

While just drawing a dense polygon with straight-line segments as suggested above by Theraot is likely to be more efficient, this shading technique is convenient because it automatically adapts to the scale of the circle / zoom of the camera to show a smooth curve, without needing to add more points as the camera gets closer. It also gives partial opacity along the edge of the line for a smooth antialiased look. It won't look good at extreme glancing angles though, since at the end of the day it's a flat sheet of geometry and has no thickness when viewed edge-on - you'd want to switch to a different shape if viewing the circle this way.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...